Subject: PATCH: PGADMIN 4 - FTS templates node

From: Sanket Mehta <[email-masked]>
Date: Thu, 28 Jan 2016 19:21:37 +0530

Hi,

I have created patch for "FTS Template" node in pgadmin4.
Please do review it and let me know in case anything is missing.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/__init__.py
new file mode 100644
index 0000000..a571470
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/__init__.py
@@ -0,0 +1,430 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+import json
+from flask import render_template, make_response, current_app, 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.schemas as schemas
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+from html import escape
+
+
+class FtsTemplateModule(CollectionNodeModule):
+    NODE_TYPE = 'fts_template'
+    COLLECTION_LABEL = gettext('Fts Templates')
+
+    def __init__(self, *args, **kwargs):
+        self.min_ver = None
+        self.max_ver = None
+        super(FtsTemplateModule, self).__init__(*args, **kwargs)
+
+    def get_nodes(self, gid, sid, did, scid):
+        """
+        Generate the collection node
+        """
+        yield self.generate_browser_collection_node(scid)
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for fts template, when any of the schema node is
+        initialized.
+        """
+        return schemas.SchemaModule.NODE_TYPE
+
+
+blueprint = FtsTemplateModule(__name__)
+
+
+class FtsTemplateView(NodeView):
+    node_type = blueprint.node_type
+
+    parent_ids = [
+            {'type': 'int', 'id': 'gid'},
+            {'type': 'int', 'id': 'sid'},
+            {'type': 'int', 'id': 'did'},
+            {'type': 'int', 'id': 'scid'}
+            ]
+    ids = [
+            {'type': 'int', 'id': 'tid'}
+            ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'children': [{
+            'get': 'children'
+        }],
+        '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'}],
+        'getlexize': [{'get': 'getlexize'}, {'get': 'getlexize'}],
+        'getinit': [{'get': 'getinit'}, {'get': 'getinit'}],
+    })
+
+    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(
+                    "fts_template/js/fts_templates.js",
+                    _=gettext
+                    ),
+                200, {'Content-Type': 'application/x-javascript'}
+                )
+
+    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'])
+            # If DB not connected then return error to browser
+            if not self.conn.connected():
+                return precondition_required(
+                    gettext(
+                            "Connection to the server has been lost!"
+                    )
+                )
+            self.template_path = 'fts_template/sql/'
+            return f(*args, **kwargs)
+
+        return wrap
+
+
+    @check_precondition
+    def list(self, gid, sid, did, scid):
+        SQL = render_template(
+            "/".join([self.template_path, 'properties.sql']),
+            scid=scid
+            )
+        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, scid):
+        res = []
+        SQL = render_template(
+            "/".join([self.template_path, 'properties.sql']),
+            scid=scid
+            )
+        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['oid'],
+                        row['tmplname'],
+                        icon="icon-fts_template"
+                    ))
+
+        return make_json_response(
+                data=res,
+                status=200
+                )
+
+    @check_precondition
+    def properties(self, gid, sid, did, scid, tid):
+        SQL = render_template(
+            "/".join([self.template_path, 'properties.sql']),
+            scid=scid,
+            tid=tid
+            )
+        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, scid):
+        """
+        This function will creates new the fts_template object
+        """
+
+        required_args = [
+            'tmpllexize',
+            'schema',
+            'tmplname'
+        ]
+
+        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
+                    )
+                )
+        try:
+            SQL = render_template("/".join([self.template_path, 'create.sql']),
+                                  data=data,
+                                  conn=self.conn,
+                                )
+            status, res = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            # we need oid to to add object in tree at browser, below sql will gives the same
+            SQL = render_template("/".join([self.template_path, 'properties.sql']),
+                                  name=data['tmplname']
+                                )
+            status, tid = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=tid)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    tid,
+                    data['tmplname'],
+                    icon="icon-fts_template"
+                )
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+
+    @check_precondition
+    def update(self, gid, sid, did, scid, tid):
+        """
+        This function will update text search template object
+        """
+        data = request.form if request.form else json.loads(request.data.decode())
+        SQL = self.getSQL(gid, sid, did, scid, data, tid)
+        try:
+            if SQL and SQL.strip('\n') and SQL.strip(' '):
+                status, res = self.conn.execute_scalar(SQL)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                return make_json_response(
+                    success=1,
+                    info="Cast updated",
+                    data={
+                        'id': tid,
+                        'sid': sid,
+                        'gid': gid,
+                        'did': did,
+                        'scid': scid
+                    }
+                )
+            else:
+                return make_json_response(
+                    success=1,
+                    info="Nothing to update",
+                    data={
+                        'id': tid,
+                        'sid': sid,
+                        'gid': gid,
+                        'did': did,
+                        'scid': scid
+                    }
+                )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def delete(self, gid, sid, did, scid, tid):
+        """
+        This function will drop the fts_template object
+        """
+        # Below will decide if it's simple drop or drop with cascade call
+        if self.cmd == 'delete':
+            # This is a cascade operation
+            cascade = True
+        else:
+            cascade = False
+
+        try:
+            # Get name for template from tid
+            SQL = render_template("/".join([self.template_path, 'delete.sql']),
+                                  tid=tid)
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            # Drop fts template
+            result = res['rows'][0]
+            SQL = render_template("/".join([self.template_path, 'delete.sql']),
+                                  name=result['name'],
+                                  schema=result['schema'],
+                                  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("Fts template dropped"),
+                data={
+                    'id': tid,
+                    'sid': sid,
+                    'gid': gid,
+                    'did': did,
+                    'scid': scid
+                }
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, scid, tid=None):
+        """
+         This function returns modified SQL
+        """
+        data = request.args
+        SQL = self.getSQL(gid, sid, did, scid, data, tid)
+        if isinstance(SQL, str) and SQL 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, did, scid, data, tid=None):
+        """
+        This function will return SQL for model data
+        """
+        try:
+            if tid is not None:
+                SQL = render_template("/".join([self.template_path, 'properties.sql']),
+                                      tid=tid,
+                                      scid=scid
+                                      )
+                status, res = self.conn.execute_dict(SQL)
+
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                old_data = res['rows'][0]
+                SQL = render_template(
+                    "/".join([self.template_path, 'update.sql']),
+                    data=data, o_data=old_data
+                    )
+            else:
+                if 'tmpllexize' in data and 'tmplname' in data and 'schema' in data:
+                    SQL = render_template("/".join([self.template_path, 'create.sql']), data=data, conn=self.conn)
+                else:
+                    SQL = "-- incomplete definition"
+            return SQL
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def getlexize(self, gid, sid, did, scid, cid=None):
+        data = request.args
+        SQL = render_template("/".join([self.template_path, 'functions.sql']),
+                              lexize=True)
+        status, rset = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        res = [{'label': '', 'value': ''}]
+        # TODO: add schemaprefix to proname before adding it to value in res
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                       'value': row['proname']})
+        return make_json_response(
+                    data=res,
+                    status=200
+                    )
+
+    @check_precondition
+    def getinit(self, gid, sid, did, scid, cid=None):
+        data = request.args
+        SQL = render_template("/".join([self.template_path, 'functions.sql']),
+                              init=True)
+        status, rset = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # We have added this to map against '-' which is coming from properties
+        res = [{'label': '', 'value': '-'}]
+        # TODO: add schemaprefix to proname before adding it to value in res
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                       'value': row['proname']})
+        return make_json_response(
+                    data=res,
+                    status=200
+                    )
+
+    @check_precondition
+    def sql(self, gid, sid, did, scid, tid):
+        """
+        This function will generate sql for sql panel
+        """
+        try:
+            SQL = render_template(
+                "/".join([self.template_path, 'properties.sql']),
+                tid=tid,
+                scid=scid
+                )
+            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,
+                                  is_sql=True
+                                  )
+            return ajax_response(response=SQL)
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+FtsTemplateView.register_node_view(blueprint)
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/img/coll-fts_template.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/img/coll-fts_template.png
new file mode 100644
index 0000000000000000000000000000000000000000..fabd47da7ae27b04b0bfd38a7b1ee9f6c1a74d2c
GIT binary patch
literal 601
zcmY+9Ye-W86vyuj!!5?7zDS=7f?QnJEZv8iwXIEZ*mjEw%H7=uILpipL87b$mOaoY
zG^59ICOS;*K|&8hv}qE{A&!ucGBAp8clWjXUN`;JhyVGV1Lp_lv{kzg=yjWQ008J+
zPMb$%;lC8DQ|mUP!K;F=J_y-WCa0Pc@5*zzR4zBCD6@+4NtUN2X;PBj=W_GWZkwVc
zWch<AzUKL-I1VYwXB<By%TpNUmZUKhtrUb892b%08Ci}?(zqzT6~$3Oc+T^K9M{LP
zw=t|r6yJ#AD?u3H`54DNK@cm;cGGmLAiNZWVV)o0xM()}fMvTF<`%=;K+*ji*OSfO
zXW34M>7eOret#)NwPdsRSoRLXw9|AOMO~(-3nbZ`$%Op=GMc^u!)A&)55r(46ZH9t
z38IdoE|KIpl5EOk8VRDFAOh($o=&3+LQOz@UK8@Ty?}N33$6~@2IsM$O6O`EAM5t0
zO_$470Ts<5J~XUxtOtF59LHe{`S}eE>l)5RWAIXicVhB-Uu&D8O228Z^XA1!$H>+G
zyJb#mUa!MbdE9ZteNwX}Qs9a94)(<Qh8}A-&gKmyQ_1<c-Jm&72b|DB$SHpSuZL^%
zpB44T7vibJ;&@V940wM0S^B-a^8HtSaIAB*efUv})mI1s6I0UwKrb31UlSXe)DyRc
z>_<Z=9KtL}Bc=i{fhC}^#AF0ZAQNaY@3fdpw}Yh?5PZ0@Ble$g`V3l&H?Ij``Kq99
Yn=Rn3HDRXf@SrLHF1y<{w9gm*3#c75djJ3c

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/img/fts_template.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/img/fts_template.png
new file mode 100644
index 0000000000000000000000000000000000000000..eb780bf0bb0f5d777e8a72720b5ed0e2ad48bc45
GIT binary patch
literal 696
zcmY+CS4>j@6ozkcq>wQ5!GLk#CLzU3S)oQ!Yr$eFU<-t$cHKq-h(bd^Q2~h-MIjMo
zBO)RJ1T=0%>WGXeVM%<^_+&IudiTD!Ug61y|NQ4q&iC@?)TAUG@by{m0|0<;qE2g&
zJm8<?E2J5s@HYa0>`;n6HC~D{V`Wn#u^!KY$1^XAv!XaHik}2wQV_;?{=MCv=JCwg
z?M6|Yf}q2KFb+Y<JpTrQ^c?p>6lX;7qaaKO!Uvvz%k!fg_mbnDvg{+49p?FWJn!PT
zR~+|@WgoNb1BSV8vuSC%o99P3?m5doVcCZaGsrM~G<}z*Z&Oq|NjBSTI+h(`n0}h>
zq3JG)>Y%6=lDuiLs0iXJ!wfLYJ(|8lQMV|nl_VQU@;X6W!|}^Fei6f}Xu8vCjU&k>
zl58M|I)b=@<Ck#!0)|zX%}Nw4CCM8EQBM%HIPS#pY7DEyu(K#yhN2}1Qj8#lI9_A1
zM4{+uvl%p*6fj(1GKIqMaSS_;qGu4qfgq<4<OB@o!EmnIZFjqEZnt$+xwl3tROT=w
z83FZ=FLo(-pVVa*ORif|!bD!2v~(qE_oW8TSxqjWRxfwS*P1M=z1F2im-r=x?Q{Y$
z{<}AiR&>@6%eCr;WPOgta4;(&-EXUFOUO`XZ}&h~Uyt9KfZ6H!*@c<Ub1`7Gd^|9y
zwW*~!J0mM||H}OQKfgC_Xm2}`^EKFOaZ|_9Z*i~BiL%Oo%Fgfg)5EZHy!U6IQ=Q+I
ztuEI%EQTT<ZE%zV+`BCzbkDAsI`95tav4ziD`;WBxc%P;?Qq0rIBaGIq_P%4k^xFE
z98`oW6<~O(5>!P-s3N06!6+37b}WYV{Rb2j+H&n>%K#X=1W4DBDlmE(m~2cNkN_Yt
LK1n;MF;)Eq048yN

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/js/fts_templates.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/js/fts_templates.js
new file mode 100644
index 0000000..7cc6ec5
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/js/fts_templates.js
@@ -0,0 +1,109 @@
+define(
+        ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser, alertify) {
+
+  if (!pgBrowser.Nodes['coll-fts_template']) {
+    var fts_templates = pgAdmin.Browser.Nodes['coll-fts_template'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'fts_template',
+        label: '{{ _('FTS Templates') }}',
+        type: 'coll-fts_template'
+      });
+  };
+
+  if (!pgBrowser.Nodes['fts_template']) {
+    pgAdmin.Browser.Nodes['fts_template'] = pgAdmin.Browser.Node.extend({
+      parent_type: 'schema',
+      type: 'fts_template',
+      canDrop: true,
+      canDropCascade: true,
+      label: '{{ _('FTS Templates') }}',
+      hasSQL: true,
+      Init: function() {
+        /* Avoid mulitple registration of menus */
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        pgBrowser.add_menus([{
+          name: 'create_fts_template_on_schema', node: 'schema', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('FTS Template...') }}',
+          icon: 'wcTabIcon icon-fts_template', data: {action: 'create'}
+          },{
+          name: 'create_fts_template_on_coll', node: 'coll-fts_template', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('FTS Template...') }}',
+          icon: 'wcTabIcon icon-fts_template', data: {action: 'create'}
+          },{
+          name: 'create_fts_template', node: 'fts_template', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('FTS Template...') }}',
+          icon: 'wcTabIcon icon-fts_template', data: {action: 'create'}
+          }]);
+
+      },
+      model: pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          tmplname: undefined,
+          descrption: undefined,
+          schema: undefined,
+          tmplinit: undefined,
+          tmpllexize: undefined
+        },
+        schema: [{
+          id: 'tmplname', label: '{{ _('Name') }}', cell: 'string', group: 'Definition',
+          type: 'text',
+        },{
+          id: 'oid', label:'{{ _('Oid') }}', cell: 'string', group: 'Definition',
+          editable: false, type: 'text', disabled: true
+        },{
+          id: 'description', label:'{{ _('Comment') }}', cell: 'string', group: 'Properties',
+          type: 'multiline'
+        },{
+          id: 'tmplinit', label: '{{ _('Init')}}', group: 'Definition',
+          type: 'text', disabled: function(m) { return !m.isNew(); },
+          control: 'node-ajax-options', url: 'getinit'
+        },{
+          id: 'tmpllexize', label: '{{ _('Lexize')}}', group: 'Definition',
+          type: 'text', disabled: function(m) { return !m.isNew(); },
+          control: 'node-ajax-options', url: 'getlexize'
+        },{
+          id: 'schema', label: '{{ _('Schema')}}', cell: 'string', group: 'Definition',
+          type: 'text', mode: ['create','edit'], node: 'schema',
+          control: 'node-list-by-name'
+        }],
+        validate: function(keys){
+          var name = this.get('tmplname');
+          var lexize = this.get('tmpllexize');
+          var schema = this.get('schema');
+          if (_.isUndefined(name) || _.isNull(name) || String(name).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Name must be specified!') }}';
+            this.errorModel.set('name', msg);
+            return msg;
+          }
+          else if (_.isUndefined(lexize) || _.isNull(lexize) || String(lexize).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Lexize function must be selected!') }}';
+            this.errorModel.set('tmpllexize', msg);
+            return msg;
+          }
+          else if (_.isUndefined(schema) || _.isNull(schema) || String(schema).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Schema must be selected!') }}';
+            this.errorModel.set('schema', msg);
+            return msg;
+          }
+          else
+          {
+            this.errorModel.unset('tmpllexize');
+            this.errorModel.unset('schema');
+          }
+          this.trigger('on-status-clear');
+          return null;
+        }
+      })
+    });
+  }
+
+return pgBrowser.Nodes['coll-fts_template'];
+});
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/create.sql
new file mode 100644
index 0000000..462d5f8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/create.sql
@@ -0,0 +1,19 @@
+
+
+{# CREATE TEXT SEARCH TEMPLATE Statement #}
+{% if data and data.schema and data.tmplname and data.tmpllexize %}
+{% if is_sql %}
+-- Text Search Template: {{conn|qtIdent(data.schema)}}.{{conn|qtIdent(data.tmplname)}}
+
+-- DROP TEXT SEARCH TEMPLATE {{conn|qtIdent(data.schema)}}.{{conn|qtIdent(data.tmplname)}}
+{% endif %}
+
+CREATE TEXT SEARCH TEMPLATE {{ conn|qtIdent(data.schema) }}.{{ conn|qtIdent(data.tmplname) }} (
+{% if data.tmplinit and data.tmplinit != '-'%}  INIT = {{data.tmplinit}}{% endif %},
+  LEXIZE = {{data.tmpllexize}}
+);
+{# Description for TEXT SEARCH TEMPLATE #}
+{% if data.description %}
+COMMENT ON TEXT SEARCH TEMPLATE {{ conn|qtIdent(data.schema) }}.{{ conn|qtIdent(data.tmplname) }}
+      IS {{ data.description|qtLiteral }};
+{% endif %}{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/delete.sql
new file mode 100644
index 0000000..ac11ac9
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/delete.sql
@@ -0,0 +1,20 @@
+{# FETCH TEXT SEARCH TEMPLATE NAME Statement #}
+{% if tid %}
+  SELECT
+    t.tmplname AS name,
+    (SELECT
+        nspname
+     FROM
+        pg_namespace
+     WHERE
+        oid = t.tmplnamespace) as schema
+  FROM
+    pg_ts_template t LEFT JOIN pg_description d
+  ON d.objoid=t.oid AND d.classoid='pg_ts_template'::regclass
+  WHERE
+    t.oid = {{tid}}::OID;
+{% endif %}
+{# DROP TEXT SEARCH TEMPLATE Statement #}
+{% if schema and name %}
+DROP TEXT SEARCH TEMPLATE {{conn|qtIdent(schema)}}.{{conn|qtIdent(name)}} {% if cascade %}CASCADE{%endif%};
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/functions.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/functions.sql
new file mode 100644
index 0000000..73ce9e2
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/functions.sql
@@ -0,0 +1,22 @@
+{# FETCH lexize functions for TEXT SEARCH TEMPLATE #}
+{% if lexize %}
+SELECT
+    proname, nspname
+FROM
+    pg_proc JOIN pg_namespace n ON n.oid=pronamespace
+WHERE
+    prorettype=2281
+    AND proargtypes='2281 2281 2281 2281'
+ORDER BY proname;
+{% endif %}
+
+{# FETCH init functions for TEXT SEARCH TEMPLATE #}
+{% if init %}
+SELECT
+    proname, nspname
+FROM
+    pg_proc JOIN pg_namespace n ON n.oid=pronamespace
+WHERE
+    prorettype=2281 and proargtypes='2281'
+ORDER BY proname;
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/properties.sql
new file mode 100644
index 0000000..bcc11f0
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/properties.sql
@@ -0,0 +1,37 @@
+{# Get OID for FTS TEMPLATE #}
+{% if name %}
+SELECT
+    tmpl.oid
+FROM
+    pg_ts_template tmpl
+WHERE
+    tmpl.tmplname = {{name|qtLiteral}}
+{# FETCH properties for FTS TEMPLATE #}
+{% else %}
+
+SELECT
+    tmpl.oid,
+    tmpl.tmplname,
+    tmpl.tmplinit,
+    tmpl.tmpllexize,
+    description,
+    (SELECT
+        nspname
+     FROM
+        pg_namespace
+     WHERE
+        oid = tmpl.tmplnamespace
+    ) AS schema
+FROM
+    pg_ts_template tmpl LEFT OUTER JOIN pg_description des
+ON
+    (des.objoid=tmpl.oid AND des.classoid='pg_ts_template'::regclass)
+WHERE
+    tmpl.tmplnamespace = {{scid}}::OID
+{% if tid %}
+    AND tmpl.oid={{tid}}::OID
+{% endif %}
+ORDER BY
+tmpl.tmplname
+
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/update.sql
new file mode 100644
index 0000000..d2fdc45
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/update.sql
@@ -0,0 +1,15 @@
+{# UPDATE NAME for TEXT SEARCH TEMPLATE statement #}
+{% if data %}
+{% if data.tmplname and data.tmplname != o_data.tmplname %}
+  ALTER TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.tmplname)}}
+    RENAME TO {{data.tmplname}};
+{% endif %}
+{% if data.schema and data.schema != o_data.schema %}
+  ALTER TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.tmplname)}}
+    SET SCHEMA {{data.schema}};
+{% endif %}
+{% if data.description and data.description != o_data.description %}
+  COMMENT ON TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.tmplname)}}
+    IS {{ data.description|qtLiteral }};
+{% endif %}
+{% endif %}
\ No newline at end of file

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

---

From: Sanket Mehta <[email-masked]>
Date: Tue, 23 Feb 2016 19:14:26 +0530

Hi,

PFA the revised patch for fts template node which includes dependency and
dependent route handling code.
Please do review it and if found proper please do commit it.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Thu, Jan 28, 2016 at 7:21 PM, Sanket Mehta <[email-masked]
> wrote:

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/__init__.py
new file mode 100644
index 0000000..0535040
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/__init__.py
@@ -0,0 +1,700 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Defines views for management of Fts Template node"""
+
+import json
+from flask import render_template, make_response, current_app, 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.schemas as schemas
+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 FtsTemplateModule(CollectionNodeModule):
+    """
+     class FtsTemplateModule(CollectionNodeModule)
+
+        A module class for FTS Template node derived from CollectionNodeModule.
+
+    Methods:
+    -------
+    * __init__(*args, **kwargs)
+      - Method is used to initialize the FtsTemplateModule and it's base module.
+
+    * get_nodes(gid, sid, did, scid)
+      - Method is used to generate the browser collection node.
+
+    * node_inode()
+      - Method is overridden from its base class to make the node as leaf node.
+
+    * script_load()
+      - Load the module script for FTS Template, when any of the schema node is
+        initialized.
+    """
+    NODE_TYPE = 'fts_template'
+    COLLECTION_LABEL = gettext('Fts Templates')
+
+    def __init__(self, *args, **kwargs):
+        self.min_ver = None
+        self.max_ver = None
+        super(FtsTemplateModule, self).__init__(*args, **kwargs)
+
+    def get_nodes(self, gid, sid, did, scid):
+        """
+        Generate the collection node
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+        yield self.generate_browser_collection_node(scid)
+
+    @property
+    def node_inode(self):
+        """
+        Override the property to make the node as leaf node
+        """
+        return False
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for fts template, when any of the schema node is
+        initialized.
+        """
+        return schemas.SchemaModule.NODE_TYPE
+
+
+blueprint = FtsTemplateModule(__name__)
+
+
+class FtsTemplateView(PGChildNodeView):
+    """
+    class FtsTemplateView(PGChildNodeView)
+
+        A view class for FTS Tempalte node derived from PGChildNodeView. This class is
+        responsible for all the stuff related to view like create/update/delete
+        FTS template, showing properties of node, showing sql in sql pane.
+
+    Methods:
+    -------
+    * __init__(**kwargs)
+      - Method is used to initialize the FtsTemplateView and it's base view.
+
+    * module_js()
+      - This property defines (if javascript) exists for this node.
+        Override this property for your own logic
+
+    * check_precondition()
+      - 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
+
+    * list()
+      - This function is used to list all the  nodes within that collection.
+
+    * nodes()
+      - This function will used to create all the child node within that collection.
+        Here it will create all the FTS Template nodes.
+
+    * properties(gid, sid, did, rg_id)
+      - This function will show the properties of the selected FTS Template node
+
+    * create(gid, sid, did, rg_id)
+      - This function will create the new FTS Template object
+
+    * update(gid, sid, did, rg_id)
+      - This function will update the data for the selected FTS Template node
+
+    * delete(self, gid, sid, rg_id):
+      - This function will drop the FTS Template object
+
+    * msql(gid, sid, did, rg_id)
+      - This function is used to return modified SQL for the selected FTS Template node
+
+    * get_sql(data, rg_id)
+      - This function will generate sql from model data
+
+    * sql(gid, sid, did, rg_id):
+      - This function will generate sql to show it in sql pane for the selected FTS Template node.
+
+    * get_type():
+      - This function will fetch all the types for source and target types select control.
+
+    * get_functions():
+      - This function will fetch associated functions list depending on selected source
+        and target types while creating a new FTS Template node.
+
+    * dependents(gid, sid, did, lid):
+      - This function get the dependents and return ajax response for the Fts Tempalte node.
+
+    * dependencies(self, gid, sid, did, lid):
+      - This function get the dependencies and return ajax response for the FTS Tempalte node.
+
+    """
+
+    node_type = blueprint.node_type
+
+    parent_ids = [
+        {'type': 'int', 'id': 'gid'},
+        {'type': 'int', 'id': 'sid'},
+        {'type': 'int', 'id': 'did'},
+        {'type': 'int', 'id': 'scid'}
+    ]
+    ids = [
+        {'type': 'int', 'id': 'tid'}
+    ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'children': [{
+            'get': 'children'
+        }],
+        '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'}],
+        'get_lexize': [{'get': 'get_lexize'}, {'get': 'get_lexize'}],
+        'get_init': [{'get': 'get_init'}, {'get': 'get_init'}],
+    })
+
+    def _init_(self, **kwargs):
+        self.conn = None
+        self.template_path = None
+        self.manager = None
+        super(FtsTemplateView, self).__init__(**kwargs)
+
+    def module_js(self):
+        """
+        This property defines whether javascript exists for this node.
+        """
+        return make_response(
+            render_template(
+                "fts_template/js/fts_templates.js",
+                _=gettext
+            ),
+            200, {'Content-Type': 'application/x-javascript'}
+        )
+
+    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'])
+            # If DB not connected then return error to browser
+            if not self.conn.connected():
+                return precondition_required(
+                    gettext(
+                        "Connection to the server has been lost!"
+                    )
+                )
+            # we will set template path for sql scripts depending upon server version
+            ver = self.manager.version
+            if ver >= 90100:
+                self.template_path = 'fts_template/sql/9.1_plus'
+            return f(*args, **kwargs)
+
+        return wrap
+
+    @check_precondition
+    def list(self, gid, sid, did, scid):
+        sql = render_template(
+            "/".join([self.template_path, 'properties.sql']),
+            scid=scid
+        )
+        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, scid):
+        res = []
+        sql = render_template(
+            "/".join([self.template_path, 'properties.sql']),
+            scid=scid
+        )
+        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['oid'],
+                    did,
+                    row['tmplname'],
+                    icon="icon-fts_template"
+                ))
+
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def properties(self, gid, sid, did, scid, tid):
+        sql = render_template(
+            "/".join([self.template_path, 'properties.sql']),
+            scid=scid,
+            tid=tid
+        )
+        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, scid):
+        """
+        This function will creates new the fts_template object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        """
+
+        # Mandatory fields to create a new fts template
+        required_args = [
+            'tmpllexize',
+            'schema',
+            'tmplname'
+        ]
+
+        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
+                    )
+                )
+        try:
+            # Fetch schema name from schema oid
+            sql = render_template("/".join([self.template_path, 'schema.sql']),
+                                  data=data,
+                                  conn=self.conn,
+                                  )
+
+            status, schema = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=schema)
+
+            # replace schema oid with schema name before passing to create.sql
+            # to generate proper sql query
+            new_data = data.copy()
+            new_data['schema'] = schema
+            sql = render_template("/".join([self.template_path, 'create.sql']),
+                                  data=new_data,
+                                  conn=self.conn,
+                                  )
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            # we need fts_template id to to add object in tree at browser,
+            # below sql will give the same
+            sql = render_template(
+                "/".join([self.template_path, 'properties.sql']),
+                name=data['tmplname']
+            )
+            status, tid = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(errormsg=tid)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    tid,
+                    did,
+                    data['tmplname'],
+                    icon="icon-fts_template"
+                )
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def update(self, gid, sid, did, scid, tid):
+        """
+        This function will update text search template object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param tid: fts tempate id
+        """
+        data = request.form if request.form else json.loads(
+            request.data.decode())
+
+        # Fetch sql query to update fts template
+        sql = self.get_sql(gid, sid, did, scid, data, tid)
+        try:
+            if sql and sql.strip('\n') and sql.strip(' '):
+                status, res = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                return make_json_response(
+                    success=1,
+                    info="FTS Template updated",
+                    data={
+                        'id': tid,
+                        'sid': sid,
+                        'gid': gid,
+                        'did': did,
+                        'scid': scid
+                    }
+                )
+            else:
+                return make_json_response(
+                    success=1,
+                    info="Nothing to update",
+                    data={
+                        'id': tid,
+                        'sid': sid,
+                        'gid': gid,
+                        'did': did,
+                        'scid': scid
+                    }
+                )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def delete(self, gid, sid, did, scid, tid):
+        """
+        This function will drop the fts_template object
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param tid: fts tempate id
+        """
+        # Below will decide if it's simple drop or drop with cascade call
+        if self.cmd == 'delete':
+            # This is a cascade operation
+            cascade = True
+        else:
+            cascade = False
+
+        try:
+            # Get name for template from tid
+            sql = render_template("/".join([self.template_path, 'delete.sql']),
+                                  tid=tid)
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            # Drop fts template
+            result = res['rows'][0]
+            sql = render_template("/".join([self.template_path, 'delete.sql']),
+                                  name=result['name'],
+                                  schema=result['schema'],
+                                  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("Fts template dropped"),
+                data={
+                    'id': tid,
+                    'sid': sid,
+                    'gid': gid,
+                    'did': did,
+                    'scid': scid
+                }
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, scid, tid=None):
+        """
+        This function returns modified SQL
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param tid: fts tempate id
+        """
+        data = request.args
+
+        # Fetch sql query for modified data
+        sql = self.get_sql(gid, sid, did, scid, data, tid)
+
+        if isinstance(sql, str) and sql 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 get_sql(self, gid, sid, did, scid, data, tid=None):
+        """
+        This function will return SQL for model data
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param tid: fts tempate id
+        """
+        try:
+            # Fetch sql for update
+            if tid is not None:
+                sql = render_template(
+                    "/".join([self.template_path, 'properties.sql']),
+                    tid=tid,
+                    scid=scid
+                )
+
+                status, res = self.conn.execute_dict(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                old_data = res['rows'][0]
+
+                # If user has changed the schema then fetch new schema directly
+                # using its oid otherwise fetch old schema name using fts template oid
+                sql = render_template(
+                    "/".join([self.template_path, 'schema.sql']),
+                    data=data)
+
+                status, new_schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=new_schema)
+
+                # Replace schema oid with schema name
+                new_data = data.copy()
+                if 'schema' in new_data:
+                    new_data['schema'] = new_schema
+
+                # Fetch old schema name using old schema oid
+                sql = render_template(
+                    "/".join([self.template_path, 'schema.sql']),
+                    data=old_data)
+
+                status, old_schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=old_schema)
+
+                # Replace old schema oid with old schema name
+                old_data['schema'] = old_schema
+
+                sql = render_template(
+                    "/".join([self.template_path, 'update.sql']),
+                    data=new_data, o_data=old_data
+                )
+                # Fetch sql query for modified data
+            else:
+                # Fetch schema name from schema oid
+                sql = render_template("/".join([self.template_path, 'schema.sql']),
+                                      data=data)
+
+                status, schema = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=schema)
+
+                # Replace schema oid with schema name
+                new_data = data.copy()
+                new_data['schema'] = schema
+
+                if 'tmpllexize' in new_data and \
+                                'tmplname' in new_data and \
+                                'schema' in new_data:
+                    sql = render_template("/".join([self.template_path,
+                                                    'create.sql']),
+                                          data=new_data,
+                                          conn=self.conn
+                                          )
+                else:
+                    sql = "-- incomplete definition"
+            return sql
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def get_lexize(self, gid, sid, did, scid, tid=None):
+        """
+        This function will return lexize functions list for fts template
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param tid: fts tempate id
+        """
+        data = request.args
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              lexize=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # Empty set is added before actual list as initially it will be visible
+        # at lexize select control while creating a new fts template
+        res = [{'label': '', 'value': ''}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def get_init(self, gid, sid, did, scid, tid=None):
+        """
+        This function will return init functions list for fts template
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param tid: fts tempate id
+        """
+        data = request.args
+        sql = render_template("/".join([self.template_path, 'functions.sql']),
+                              init=True)
+        status, rset = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        # We have added this to map against '-' which is coming from server
+        res = [{'label': '', 'value': '-'}]
+        for row in rset['rows']:
+            res.append({'label': row['proname'],
+                        'value': row['proname']})
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def sql(self, gid, sid, did, scid, tid):
+        """
+        This function will reverse generate sql for sql panel
+        :param gid: group id
+        :param sid: server id
+        :param did: database id
+        :param scid: schema id
+        :param tid: fts tempate id
+        """
+        try:
+            sql = render_template(
+                "/".join([self.template_path, 'sql.sql']),
+                tid=tid,
+                scid=scid,
+                conn=self.conn
+            )
+            status, res = self.conn.execute_scalar(sql)
+            if not status:
+                return internal_server_error(
+                    _(
+                        "ERROR: Couldn't generate reversed engineered Query for the fts template!\n{0}").format(
+                        res
+                    )
+                )
+
+            if res is None:
+                return gone(
+                    _(
+                        "ERROR: Couldn't generate reversed engineered Query for fts template node!")
+                )
+
+            return ajax_response(response=res)
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def dependents(self, gid, sid, did, scid, tid):
+        """
+        This function get the dependents and return ajax response
+        for the FTS Template node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            tid: FTS Template ID
+        """
+        dependents_result = self.get_dependents(self.conn, tid)
+        return ajax_response(
+                response=dependents_result,
+                status=200
+                )
+
+    @check_precondition
+    def dependencies(self, gid, sid, did, scid, tid):
+        """
+        This function get the dependencies and return ajax response
+        for the FTS Template node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            scid: Schema ID
+            tid: FTS Tempalte ID
+        """
+        dependencies_result = self.get_dependencies(self.conn, tid)
+        return ajax_response(
+                response=dependencies_result,
+                status=200
+                )
+
+FtsTemplateView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/img/coll-fts_template.png b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/static/img/coll-fts_template.png
new file mode 100644
index 0000000000000000000000000000000000000000..fabd47da7ae27b04b0bfd38a7b1ee9f6c1a74d2c
GIT binary patch
literal 601
zcmY+9Ye-W86vyuj!!5?7zDS=7f?QnJEZv8iwXIEZ*mjEw%H7=uILpipL87b$mOaoY
zG^59ICOS;*K|&8hv}qE{A&!ucGBAp8clWjXUN`;JhyVGV1Lp_lv{kzg=yjWQ008J+
zPMb$%;lC8DQ|mUP!K;F=J_y-WCa0Pc@5*zzR4zBCD6@+4NtUN2X;PBj=W_GWZkwVc
zWch<AzUKL-I1VYwXB<By%TpNUmZUKhtrUb892b%08Ci}?(zqzT6~$3Oc+T^K9M{LP
zw=t|r6yJ#AD?u3H`54DNK@cm;cGGmLAiNZWVV)o0xM()}fMvTF<`%=;K+*ji*OSfO
zXW34M>7eOret#)NwPdsRSoRLXw9|AOMO~(-3nbZ`$%Op=GMc^u!)A&)55r(46ZH9t
z38IdoE|KIpl5EOk8VRDFAOh($o=&3+LQOz@UK8@Ty?}N33$6~@2IsM$O6O`EAM5t0
zO_$470Ts<5J~XUxtOtF59LHe{`S}eE>l)5RWAIXicVhB-Uu&D8O228Z^XA1!$H>+G
zyJb#mUa!MbdE9ZteNwX}Qs9a94)(<Qh8}A-&gKmyQ_1<c-Jm&72b|DB$SHpSuZL^%
zpB44T7vibJ;&@V940wM0S^B-a^8HtSaIAB*efUv})mI1s6I0UwKrb31UlSXe)DyRc
z>_<Z=9KtL}Bc=i{fhC}^#AF0ZAQNaY@3fdpw}Yh?5PZ0@Ble$g`V3l&H?Ij``Kq99
Yn=Rn3HDRXf@SrLHF1y<{w9gm*3#c75djJ3c

literal 0
HcmV?d00001

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/js/fts_templates.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/js/fts_templates.js
new file mode 100644
index 0000000..509d75f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/js/fts_templates.js
@@ -0,0 +1,137 @@
+define(
+        ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser, alertify) {
+
+  // Extend the collection class for fts template
+  if (!pgBrowser.Nodes['coll-fts_template']) {
+    var fts_templates = pgAdmin.Browser.Nodes['coll-fts_template'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'fts_template',
+        label: '{{ _('FTS Templates') }}',
+        type: 'coll-fts_template',
+        columns: ['tmplname', 'description']
+      });
+  };
+
+  // Extend the node class for fts template
+  if (!pgBrowser.Nodes['fts_template']) {
+    pgAdmin.Browser.Nodes['fts_template'] = pgAdmin.Browser.Node.extend({
+      parent_type: 'schema',
+      type: 'fts_template',
+      canDrop: true,
+      canDropCascade: true,
+      label: '{{ _('FTS Templates') }}',
+      hasSQL: true,
+      hasDepends: true,
+      Init: function() {
+
+        // Avoid multiple registration of menus
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+        // Add context menus for fts template
+        pgBrowser.add_menus([{
+          name: 'create_fts_template_on_schema', node: 'schema', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('FTS Template...') }}',
+          icon: 'wcTabIcon icon-fts_template', data: {action: 'create'}
+          },{
+          name: 'create_fts_template_on_coll', node: 'coll-fts_template', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('FTS Template...') }}',
+          icon: 'wcTabIcon icon-fts_template', data: {action: 'create'}
+          },{
+          name: 'create_fts_template', node: 'fts_template', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('FTS Template...') }}',
+          icon: 'wcTabIcon icon-fts_template', data: {action: 'create'}
+          }]);
+
+      },
+
+      // Defining backform model for fts template node
+      model: pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          tmplname: undefined,      // Fts template name
+          description: undefined,   // Comment on template
+          schema: undefined,        // Schema name to which template belongs
+          tmplinit: undefined,      // Init function for fts template
+          tmpllexize: undefined     // Lexize function for fts template
+        },
+        initialize: function() {
+            pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
+            if (_.isUndefined(this.get('schema'))) {
+                this.set('schema', this.node_info.schema._id);
+            }
+        },
+        // Defining schema for fts template
+        schema: [{
+          id: 'tmplname', label: '{{ _('Name') }}', cell: 'string', group: 'Definition',
+          type: 'text', cellHeaderClasses: 'width_percent_50'
+        },{
+          id: 'oid', label:'{{ _('Oid') }}', cell: 'string', group: 'Definition',
+          editable: false, type: 'text', disabled: true
+        },{
+          id: 'description', label:'{{ _('Comment') }}', cell: 'string', group: 'Definition',
+          type: 'multiline', cellHeaderClasses: 'width_percent_50'
+        },{
+          id: 'tmplinit', label: '{{ _('Init')}}', group: 'Definition',
+          type: 'text', disabled: function(m) { return !m.isNew(); },
+          control: 'node-ajax-options', url: 'get_init'
+        },{
+          id: 'tmpllexize', label: '{{ _('Lexize')}}', group: 'Definition',
+          type: 'text', disabled: function(m) { return !m.isNew(); },
+          control: 'node-ajax-options', url: 'get_lexize'
+        },{
+          id: 'schema', label: '{{ _('Schema')}}', cell: 'string', group: 'Definition',
+          type: 'text', mode: ['create','edit'], node: 'schema',
+          control: 'node-list-by-id'
+        }],
+
+        /*
+         * Triggers control specific error messages for template name,
+         * lexize function and schema, if any one of them is not specified
+         * while creating new fts template
+         */
+        validate: function(keys){
+          var name = this.get('tmplname');
+          var lexize = this.get('tmpllexize');
+          var schema = this.get('schema');
+
+          // Validate fts template name
+          if (_.isUndefined(name) || _.isNull(name) || String(name).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Name must be specified!') }}';
+            this.errorModel.set('name', msg);
+            return msg;
+          }
+
+          // Validate lexize function control
+          else if (_.isUndefined(lexize) || _.isNull(lexize) || String(lexize).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Lexize function must be selected!') }}';
+            this.errorModel.set('tmpllexize', msg);
+            return msg;
+          }
+
+          // Validate schema for fts template
+          else if (_.isUndefined(schema) || _.isNull(schema) || String(schema).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Schema must be selected!') }}';
+            this.errorModel.set('schema', msg);
+            return msg;
+          }
+
+          else
+          {
+            this.errorModel.unset('tmpllexize');
+            this.errorModel.unset('schema');
+          }
+          this.trigger('on-status-clear');
+          return null;
+        }
+      })
+    });
+  }
+
+return pgBrowser.Nodes['coll-fts_template'];
+});
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/create.sql
new file mode 100644
index 0000000..de76132
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/create.sql
@@ -0,0 +1,12 @@
+{# CREATE TEXT SEARCH TEMPLATE Statement #}
+{% if data and data.schema and data.tmplname and data.tmpllexize %}
+
+CREATE TEXT SEARCH TEMPLATE {{ conn|qtIdent(data.schema, data.tmplname) }} (
+{% if data.tmplinit and data.tmplinit != '-'%}  INIT = {{data.tmplinit}},{% endif %}
+  LEXIZE = {{data.tmpllexize}}
+);
+{# Description for TEXT SEARCH TEMPLATE #}
+{% if data.description %}
+COMMENT ON TEXT SEARCH TEMPLATE {{ conn|qtIdent(data.schema, data.tmplname) }}
+      IS {{ data.description|qtLiteral }};
+{% endif %}{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/delete.sql
new file mode 100644
index 0000000..f76dec0
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/delete.sql
@@ -0,0 +1,21 @@
+{# FETCH TEXT SEARCH TEMPLATE NAME Statement #}
+{% if tid %}
+SELECT
+    t.tmplname AS name,
+    (SELECT
+        nspname
+    FROM
+        pg_namespace
+    WHERE
+        oid = t.tmplnamespace) as schema
+FROM
+    pg_ts_template t LEFT JOIN pg_description d
+    ON d.objoid=t.oid AND d.classoid='pg_ts_template'::regclass
+WHERE
+    t.oid = {{tid}}::OID;
+{% endif %}
+
+{# DROP TEXT SEARCH TEMPLATE Statement #}
+{% if schema and name %}
+DROP TEXT SEARCH TEMPLATE {{conn|qtIdent(schema)}}.{{conn|qtIdent(name)}} {% if cascade %}CASCADE{%endif%};
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/functions.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/functions.sql
new file mode 100644
index 0000000..73ce9e2
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/functions.sql
@@ -0,0 +1,22 @@
+{# FETCH lexize functions for TEXT SEARCH TEMPLATE #}
+{% if lexize %}
+SELECT
+    proname, nspname
+FROM
+    pg_proc JOIN pg_namespace n ON n.oid=pronamespace
+WHERE
+    prorettype=2281
+    AND proargtypes='2281 2281 2281 2281'
+ORDER BY proname;
+{% endif %}
+
+{# FETCH init functions for TEXT SEARCH TEMPLATE #}
+{% if init %}
+SELECT
+    proname, nspname
+FROM
+    pg_proc JOIN pg_namespace n ON n.oid=pronamespace
+WHERE
+    prorettype=2281 and proargtypes='2281'
+ORDER BY proname;
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/properties.sql
new file mode 100644
index 0000000..4d174b8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/properties.sql
@@ -0,0 +1,23 @@
+{# Get properties for FTS TEMPLATE #}
+SELECT
+    tmpl.oid,
+    tmpl.tmplname,
+    tmpl.tmplinit,
+    tmpl.tmpllexize,
+    description,
+    tmpl.tmplnamespace AS schema
+FROM
+    pg_ts_template tmpl LEFT OUTER JOIN pg_description des
+ON
+    (des.objoid=tmpl.oid AND des.classoid='pg_ts_template'::regclass)
+WHERE
+{% if scid %}
+    tmpl.tmplnamespace = {{scid}}::OID
+{% elif name %}
+    tmpl.tmplname = {{name|qtLiteral}}
+{% endif %}
+{% if tid %}
+    AND tmpl.oid = {{tid}}::OID
+{% endif %}
+ORDER BY
+tmpl.tmplname
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/schema.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/schema.sql
new file mode 100644
index 0000000..bbd20e6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/schema.sql
@@ -0,0 +1,18 @@
+{# SCHEMA name FETCH statement #}
+{% if data.schema %}
+SELECT
+    nspname
+FROM
+    pg_namespace
+WHERE
+    oid = {{data.schema}}::OID
+
+{% elif data.id %}
+SELECT
+    nspname
+FROM
+    pg_namespace nsp LEFT JOIN pg_ts_template ts
+    ON ts.tmplnamespace = nsp.oid
+WHERE
+    ts.oid = {{data.id}}::OID
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/sql.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/sql.sql
new file mode 100644
index 0000000..b959442
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/sql.sql
@@ -0,0 +1,39 @@
+{# Reverse engineered sql for FTS TEMPLATE #}
+SELECT
+    array_to_string(array_agg(sql), E'\n\n') as sql
+FROM
+(SELECT
+    E'-- Text Search Template: ' || nspname || E'.' ||
+    tmpl.tmplname ||
+    E'\n\n-- DROP TEXT SEARCH TEMPLATE ' || nspname || E'.' || tmpl.tmplname ||
+    E'\n\n  CREATE TEXT SEARCH TEMPLATE ' || nspname || E'.' ||  tmpl.tmplname || E'(\n' ||
+    CASE WHEN tmpl.tmplinit != '-'::regclass THEN
+    E'\tINIT = ' || tmpl.tmplinit || E',\n' ELSE '' END ||
+    E'\tLEXIZE = ' || tmpl.tmpllexize || E'\n);' ||
+    CASE WHEN a.description IS NOT NULL THEN
+        E'\n\nCOMMENT ON TEXT SEARCH TEMPLATE ' || nspname || E'.' || tmpl.tmplname ||
+        E' IS ' || pg_catalog.quote_literal(description) || E';'
+    ELSE ''  END as sql
+FROM
+    pg_ts_template tmpl
+    LEFT JOIN (
+        SELECT
+            des.description as description,
+            des.objoid as descoid
+        FROM
+            pg_description des
+        WHERE
+            des.objoid={{tid}}::OID AND des.classoid='pg_ts_template'::regclass
+    ) a ON (a.descoid = tmpl.oid)
+    LEFT JOIN (
+        SELECT
+            nspname,
+            nsp.oid as noid
+        FROM
+            pg_namespace nsp
+        WHERE
+            oid = {{scid}}::OID
+    ) b ON (b.noid = tmpl.tmplnamespace)
+WHERE
+    tmpl.oid={{tid}}::OID
+) as c;
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/update.sql
new file mode 100644
index 0000000..08df292
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/update.sql
@@ -0,0 +1,15 @@
+{# UPDATE statement for TEXT SEARCH TEMPLATE #}
+{% if data %}
+{% if data.tmplname and data.tmplname != o_data.tmplname %}
+  ALTER TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.tmplname)}}
+      RENAME TO {{data.tmplname}};
+{% endif %}
+{% if data.schema and data.schema != o_data.schema %}
+  ALTER TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.tmplname)}}
+      SET SCHEMA {{data.schema}};
+{% endif %}
+{% if data.description and data.description != o_data.description %}
+  COMMENT ON TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.tmplname)}}
+      IS {{ data.description|qtLiteral }};
+{% endif %}
+{% endif %}
\ No newline at end of file

---

From: Dave Page <[email-masked]>
Date: Thu, 25 Feb 2016 14:07:00 +0000

Hi

On Tue, Feb 23, 2016 at 1:44 PM, Sanket Mehta <[email-masked]

> Please do review it and if found proper please do commit it.

- When creating a new template, errors in the dialogue are not cleared -
e.g. the "Name must not be empty" doesn't clear, and save is not enabled,
even when a name is entered.

- "Fts Templates" should be "FTS Templates" on the treeview.

- The "Init" field should be labelled "Init Function"

- The "Lexize" field should be labelled "Lexize Function"

- Selecting a schema should be optional, with the default being the current
schema.

- 4 character indents are not consistently used in SQL templates

Thanks.

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

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

---

From: Sanket Mehta <[email-masked]>
Date: Tue, 1 Mar 2016 16:34:07 +0530

Hi Dave,

All comments are resolved.
PFA the revised patch.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Thu, Feb 25, 2016 at 7:37 PM, Dave Page <[email-masked]> wrote:

> Hi
>
> On Tue, Feb 23, 2016 at 1:44 PM, Sanket Mehta <
> [email-masked]> wrote:
>
>> Hi,
>>
> - Selecting a schema should be optional, with the default being the
> current schema.
>
index 0000000..7091c93
+    COLLECTION_LABEL = gettext('FTS Templates')

literal 0
HcmV?d00001

literal 0
HcmV?d00001

index 0000000..e46ffc3
@@ -0,0 +1,133 @@
+          id: 'tmplinit', label: '{{ _('Init Function')}}', group: 'Definition',
+          id: 'tmpllexize', label: '{{ _('Lexize Function')}}', group: 'Definition',
+          else this.errorModel.clear();
index 0000000..cca8dc4
+    (
+        oid = t.tmplnamespace
+    ) as schema
index 0000000..2fe9e02
+    proname;
index 0000000..8b2a70c
@@ -0,0 +1,27 @@
+    LEFT OUTER JOIN pg_description des
+    (
+    des.objoid=tmpl.oid
+    AND des.classoid='pg_ts_template'::regclass
+    tmpl.tmplname
index 0000000..bf7ddb3
+    LEFT JOIN pg_ts_template ts
index 0000000..103c7bf
@@ -0,0 +1,41 @@
+    (
+        E'-- Text Search Template: ' || nspname || E'.' || tmpl.tmplname ||
+        CASE
+            WHEN tmpl.tmplinit != '-'::regclass THEN E'\tINIT = ' || tmpl.tmplinit || E',\n'
+            ELSE '' END ||
+        CASE
+            WHEN a.description IS NOT NULL THEN
index 0000000..1faf08f
+ALTER TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.tmplname)}}
+ALTER TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.tmplname)}}
+COMMENT ON TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.tmplname)}}

---

From: Sanket Mehta <[email-masked]>
Date: Thu, 10 Mar 2016 11:58:12 +0530

Hi,

PFA the patch for fts template after *replacing collectionNodeModule with
SchemaChildModule* in __init__.py

Apart from that, there was an error while updating the name of fts template
which I have resolved in the same.

Please do review it and let me know in case of any changes.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

On Tue, Mar 1, 2016 at 4:56 PM, Sanket Mehta <[email-masked]>
wrote:

> Hi Dave,
>
> For your first comment,
>
>  "When creating a new template, errors in the dialogue are not cleared -
> even when a name is entered."
>
> This issue should not be handled only at FTS template level but the top
> level as it occurs in all nodes.
> I have created a bug node in kanban-chi regarding the same.
> We will take this issue as per priority and send the patch for the same
> later.
>
>
>
> Regards,
> Sanket Mehta
> Sr Software engineer
> Enterprisedb
>
> On Tue, Mar 1, 2016 at 4:34 PM, Sanket Mehta <
>>>> PFA the revised patch for fts template node which includes dependency
>>>> and dependent route handling code.
index 0000000..c266c85
+from pgadmin.browser.server_groups.servers.databases.schemas.utils import SchemaChildModule
+class FtsTemplateModule(SchemaChildModule):
+     class FtsTemplateModule(SchemaChildModule)
+        A module class for FTS Template node derived from SchemaChildModule.
+            return sql.strip('\n')

literal 0
HcmV?d00001

literal 0
HcmV?d00001

index 0000000..784f737
@@ -0,0 +1,11 @@
index 0000000..935c649
+{#in case of rename, use new fts template name #}
+{% set name = data.tmplname %}
+{% set name = o_data.tmplname %}
+ALTER TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}
+COMMENT ON TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(name)}}

---

From: Dave Page <[email-masked]>
Date: Fri, 11 Mar 2016 13:11:28 +0000

Hi

On Thu, Mar 10, 2016 at 6:28 AM, Sanket Mehta
<[email-masked]> wrote:
> Hi,
>
> PFA the patch for fts template after replacing collectionNodeModule with
> SchemaChildModule in __init__.py
>

I've updated the patch for consistency with others. Please find
attached. Aside from those changes, there are two issues:

- When creating or editing a template the SQL pane just says
"--modified SQL" even when all required options are specified. The
template is created correctly though.

- If a template is renamed, the treeview node is not updated.

Thanks.

---

From: Sanket Mehta <[email-masked]>
Date: Mon, 14 Mar 2016 19:22:44 +0530

Hi Dave,

I have incorporated your changes.
I have also tried to reproduce the Issues that you have mentioned.
find the response inline.

Also find revised patch having your suggested changes attached with this
mail.
Please do review it and let me know in case anything is missing.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

> template is created correctly though.
I have tried to reproduce this issue on my system but could not reproduce
it. PFA screen shorts explaining sql I got while creating and editing a
template.

> - If a template is renamed, the treeview node is not updated.
This is generic issue and needed to be resolved at higher level.

index 0000000..43e7457
+                info=gettext("FTS template dropped"),
+                        "ERROR: Couldn't generate reversed engineered Query for the FTS template!\n{0}").format(
+                        "ERROR: Couldn't generate reversed engineered Query for FTS template node!")

literal 0
HcmV?d00001

literal 0
HcmV?d00001

index 0000000..245e262
+          id: 'tmplname', label: '{{ _('Name') }}', cell: 'string',
+          id: 'oid', label:'{{ _('OID') }}', cell: 'string',
+          editable: false, type: 'text', disabled: true, mode:['properties']
+          id: 'schema', label: '{{ _('Schema')}}', cell: 'string',
+          id: 'description', label:'{{ _('Comment') }}', cell: 'string',
+          id: 'tmplinit', label: '{{ _('Init Function')}}', group: '{{ _('Definition') }}',
+          id: 'tmpllexize', label: '{{ _('Lexize Function')}}', group: '{{ _('Definition') }}',
index 0000000..3f74292
index 0000000..6661078
+        E'\n\nCREATE TEXT SEARCH TEMPLATE ' || nspname || E'.' ||  tmpl.tmplname || E' (\n' ||
+            WHEN tmpl.tmplinit != '-'::regclass THEN E'    INIT = ' || tmpl.tmplinit || E',\n'
+        E'    LEXIZE = ' || tmpl.tmpllexize || E'\n);' ||

---

From: Dave Page <[email-masked]>
Date: Mon, 14 Mar 2016 13:57:38 +0000

> This is generic issue and needed to be resolved at higher level.

No, I tested other nodes (database and sequence for example) and they
were properly updated. It's possible *some* are broken, but it's
certainly not a general issue.

---

From: Sanket Mehta <[email-masked]>
Date: Wed, 16 Mar 2016 12:40:16 +0530

Hi,

Sorry its misunderstanding from my side.
That node refresh on tree issue has been resolved.

Patch is attached with this mail.

Please do review it.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

index 0000000..0a05cad
@@ -0,0 +1,723 @@
+            "/".join([self.template_path, 'nodes.sql']),
+                    row['name'],
+    def node(self, gid, sid, did, scid, tid):
+            "/".join([self.template_path, 'nodes.sql']),
+                data=self.blueprint.generate_browser_node(
+                        row['name'],
+            'name'
+                name=data['name']
+                    data['name'],
+                                'name' in new_data and \

literal 0
HcmV?d00001

literal 0
HcmV?d00001

index 0000000..7961edf
+        columns: ['name', 'description']
+      parent_type: ['schema', 'catalog'],
+          name: undefined,      // Fts template name
+          id: 'name', label: '{{ _('Name') }}', cell: 'string',
+          var name = this.get('name');
index 0000000..3a290b3
+{% if data and data.schema and data.name and data.tmpllexize %}
+CREATE TEXT SEARCH TEMPLATE {{ conn|qtIdent(data.schema, data.name) }} (
+COMMENT ON TEXT SEARCH TEMPLATE {{ conn|qtIdent(data.schema, data.name) }}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/nodes.sql
index 0000000..850247e
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/fts_templates/templates/fts_template/sql/9.1_plus/nodes.sql
+    oid, tmplname as name
+{% elif tid %}
+    tmpl.oid = {{tid}}::OID
+ORDER BY name
index 0000000..2b5a95f
+    tmpl.tmplname as name,
+    name
index 0000000..e643d29
+{% if data.name and data.name != o_data.name %}
+ALTER TEXT SEARCH TEMPLATE {{conn|qtIdent(o_data.schema)}}.{{conn|qtIdent(o_data.name)}}
+    RENAME TO {{data.name}};
+{% if data.name and data.name != o_data.name %}
+{% set name = data.name %}
+{% set name = o_data.name %}

---

From: Dave Page <[email-masked]>
Date: Tue, 22 Mar 2016 14:41:37 +0000

Hi,

This seems to be fine, except:

- The icons were missing from the patch (I added them manually)

- The issue you couldn't reproduce that I reported previously still
exists. I created an FTS template per the attached screenshots, and
the SQL tab shows only "Modified SQL". The template is correctly
created though. When I edit the template, I can also successfully
rename it and update the comment, though the SQL tab still says
"Modified SQL".

---

From: Sanket Mehta <[email-masked]>
Date: Mon, 28 Mar 2016 14:31:32 +0530

Hi Dave,

I have found out the root cause of this issue.

This issue is occurring at your side as you are using python 2.7 so in that
case sql query returned from sql template is of type 'unicode'.
And in __init__.py file we have checked if the sql query is of string type
then only we would proceed further.

I was not getting this issue as I was using python 3.0

I have resolved this issue and some other typo errors in comments and patch
is attached with this mail.

Please do review it and let me know in case of any issues.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

index 0000000..ddd734b
@@ -0,0 +1,719 @@
+    * properties(gid, sid, did, scid, tid)
+    * create(gid, sid, did, scid)
+    * update(gid, sid, did, scid, tid)
+    * delete(self, gid, sid, did, scid, tid):
+    * msql(gid, sid, did, scid, tid)
+    * get_sql(data, tid)
+    * sql(gid, sid, did, scid,  tid):
+    * dependents(gid, sid, did, scid, tid):
+    * dependencies(self, gid, sid, did, scid, tid):
+            return str(sql.strip('\n'))

literal 0
HcmV?d00001

literal 0
HcmV?d00001

index 0000000..6209805
+{% if data.description != o_data.description %}

---

From: Dave Page <[email-masked]>
Date: Thu, 7 Apr 2016 10:09:54 +0100

Hi

> Please do review it and let me know in case of any issues.

Did you send the right patch? I'm still getting the error
(specifically, when rendering update.sql, render_template throws an
exception "can't adapt type 'Undefined'")

---

From: Sanket Mehta <[email-masked]>
Date: Thu, 7 Apr 2016 19:17:37 +0530

Hi,

Sorry about that issue,

Its resolved now.

PFA the right patch with this mail.

Regards,
Sanket Mehta
Sr Software engineer
Enterprisedb

index 0000000..bc80440
+from pgadmin.browser.server_groups.servers.databases import DatabaseModule
+        return DatabaseModule.NODE_TYPE

literal 0
HcmV?d00001

literal 0
HcmV?d00001

index 0000000..156defd
+{% if 'description' in data and data.description != o_data.description %}

---

From: Dave Page <[email-masked]>
Date: Thu, 7 Apr 2016 15:02:14 +0100

Thanks - committed.