チケットの表示ページで(ユーザとか)DBからSELECTできるようにするプラグイン
★はじめに
Trac使っていてよく言われていたのが次のに挙げた二つです。いまさらになってしまうんですが作ってみました。ただし、TracのDBの中身とかSQLがわからない人にはちょっと使うのは難しいとおもいます。私自身それほどのレベルではないですが(^_^A;
- ユーザ名からアカウント名の頭文字がわからん人がいる(ZガンダムがMSZ-006だってすぐにはわからんようなもの?)
- カスタムフィールドに設定できないようなフィールドをDBのクエリで選択可能にしたい(数が多いがほかのフィールドやユーザでフィルタがかけられるとか)
結局やることはDBから持ってくるってことをプラグイン化すればいいだけですが、SQLを頑張って書けばいろいろなことができます。
★どういうことができる
確認のために下の方につけた設定では次の4つのことができます。
ユーザ選択の時にマネージャのみを選択できるようにしたり、直前のownerををデフォルトで選択しておくとかの例を載せています。SQLを頑張って書けばいろいろ使えると思います。
★インストール方法
- 最後に添付してあるファイルをダウンロードし展開する
- そのなかのフォルダでpython setup.py install
- 管理画面からプラグインを有効にする。
- 次を参考にtrac.iniに設定を追加する。
★設定方法
trac.iniを直接書き換えます。
適切な位置に
[dbqueryfield]
を追加する。
その下の中身のデータはよくある次の形式で書いていきます。
グループ名.データ名 = 値
※ グループ名は適当でかまいませんがフィールド名を入れておくとわかりやすいかも
データ名は次の4つがあります
- operation
- field
- type
- sql
※ operationがreadonlyの場合はsqlは設定しません
※ typeは省略するとすべてのtypeに対して処理が行われます
※ typeは一つの値しか設定できません
1 operationに設定できるのは次の4つです
- readonly フィールドを変更できないように(隣のフィールドのSQLで参照したいときとかに参照される側にreadonlyにする)
- header ヘッダの表示の次の行にクエリ結果を表示
- field フィールドをクエリ結果で選択可能に
- action アクションのオーナ変更を選択できるように
2 fieldは通常はカスタムフィールド名
operationがaction:"アクションのオーナ変更"の場合は"アクション名_次のステータス"になります。わからない場合はソース表示で確認してみてください"id=action_アクション名_次のステータス_owner"となっているところがあるのでその中を抜いていただければOkです。
3 typeを指定した場合はこの処理をするtypeを指定します
"バグ"とか"タスク"とかですね
4 sqlデータを取得するためのSQLを指定する
SQLにはチケットのフィールド名を埋め込むことができます。pythonのフォーマットの%(~~)型のような書き方ですがconfigの値に"%("が含まれるとエラーになるので%を$に置き換えてください。
・operationがheaderの場合はSELECTの結果は表示名一つです。ユーザフィールドの値を元にユーザ名を持ってくる場合は次のようになります
SELECT s.value FROM session_attribute s WHERE s.sid='$(user)s' AND s.name='name'
・operationがfieldまたはactionの場合はSELECTの結果はフィールドの値、表示名、デフォルトかどうか(1:Default)です。ユーザフィールドを選択にする場合のSQLは次のようになります。
SELECT s.value FROM session_attribute s WHERE s.sid='$(user)s' AND s.name='name'
・長いSQLも大丈夫みたいです(これは直前のownerが選択された状態で表示されます)
SELECT s.sid, s.sid || ':' || s.value, '1' FROM session_attribute s WHERE s.name='name' AND s.sid=(SELECT tc.oldvalue FROM ticket_change tc WHERE tc.ticket='$(id)d' AND tc.field='owner' AND tc.time=(SELECT tc1.time FROM ticket_change tc1 WHERE tc1.ticket='$(id)d' AND field='owner' ORDER BY tc1.time DESC LIMIT 1) ORDER BY tc.time DESC LIMIT 1) UNION SELECT s.sid, s.sid || ':' || s.value, '0' FROM session_attribute s WHERE s.name='name' AND s.sid<>(SELECT tc.oldvalue FROM ticket_change tc WHERE tc.ticket='$(id)d' AND tc.field='owner' AND tc.time=(SELECT tc1.time FROM ticket_change tc1 WHERE tc1.ticket='$(id)d' AND field='owner' ORDER BY tc1.time DESC LIMIT 1) ORDER BY tc.time DESC LIMIT 1)
★サンプルの設定
trac.iniのこのプラグインの設定
trac.iniのカスタムフィールドはユーザの選択を確認できるように追加[dbqueryfield]manager_assign_reassign.field = manager_assign_reassignmanager_assign_reassign.operation = actionmanager_assign_reassign.sql = SELECT s.sid, s.sid || ':' || s1.value, 0 FROM session_attribute s LEFT JOIN session_attribute s1 on s.sid=s1.sid AND s1.name='name' WHERE s.authenticated='1' AND s.name='emp_type' AND s.value='1'reassign_reassign.field = reassign_reassignreassign_reassign.operation = actionreassign_reassign.sql = SELECT s.sid, s.sid || ':' || s.value, 0 FROM session_attribute s WHERE s.authenticated='1' AND s.name='name'reject_reassign.field = reject_reassignreject_reassign.operation = actionreject_reassign.sql = SELECT s.sid, s.sid || ':' || s.value, '1' FROM session_attribute s WHERE s.name='name' AND s.sid=(SELECT tc.oldvalue FROM ticket_change tc WHERE tc.ticket='$(id)d' AND tc.field='owner' AND tc.time=(SELECT tc1.time FROM ticket_change tc1 WHERE tc1.ticket='$(id)d' AND field='owner' ORDER BY tc1.time DESC LIMIT 1) ORDER BY tc.time DESC LIMIT 1) UNION SELECT s.sid, s.sid || ':' || s.value, '0' FROM session_attribute s WHERE s.name='name' AND s.sid<>(SELECT tc.oldvalue FROM ticket_change tc WHERE tc.ticket='$(id)d' AND tc.field='owner' AND tc.time=(SELECT tc1.time FROM ticket_change tc1 WHERE tc1.ticket='$(id)d' AND field='owner' ORDER BY tc1.time DESC LIMIT 1) ORDER BY tc.time DESC LIMIT 1)test.field = testtest.operation = readonlytype.field = typetype.operation = readonlyuser_f.field = useruser_f.operation = fielduser_f.sql = SELECT s.sid, s.sid || ':' || s.value, CASE WHEN s.sid='$(user)s' THEN '1' ELSE '0' END FROM session_attribute s WHERE s.name='name'user_f.type = タスクuser_h.field = useruser_h.operation = headeruser_h.sql = SELECT s.value FROM session_attribute s WHERE s.sid='$(user)s' AND s.name='name'
[ticket-custom]complete = textcomplete.label = 進捗率(%)complete.order = 2due_assign = textdue_assign.date = truedue_assign.date_empty = ondue_assign.label = 開始予定日due_assign.order = 0due_close = textdue_close.date = truedue_close.date_empty = ondue_close.label = 終了予定日due_close.order = 1parents = textparents.label = 親チケットuser = textuser.format = plainuser.label = ユーザuser.options =user.order = 4user.value =
trac.iniのワークフローのところは"承認者へ回送","差し戻し","担当者変更"の三つを作うぃ少し簡単にするように修正
[ticket-workflow]leave = * -> *leave.default = 1leave.name = 変更しないleave.operations = leave_statusmanager_assign = assigned -> manager_assignedmanager_assign.name = 承認者へ回送manager_assign.operations = set_ownermanager_assign.permissions = TICKET_MODIFYreassign = new,assigned,reopened -> assignedreassign.name = 担当者変更reassign.operations = set_ownerreassign.permissions = TICKET_MODIFYreject = manager_assigned -> assignedreject.name = 差し戻しreject.operations = set_ownerreject.permissions = TICKET_MODIFYreopen = closed -> reopenedreopen.name = 差し戻すreopen.operations = del_resolutionreopen.permissions = TICKET_CREATEresolve = manager_assigned -> closedresolve.name = 承認するresolve.operations = set_resolutionresolve.permissions = TICKET_MODIFYresolve.set_resolution = 対応済resolve2 = assigned -> closedresolve2.name = 対応せずに解決にするresolve2.operations = set_resolutionresolve2.permissions = TICKET_MODIFYresolve2.set_resolution = 不正,対応しない,重複,再現しない
ユーザプロファイルにemp_typeを作って偉い人は1を設定する
プラグインの本体ticket_filter.py(ライセンスはBSD)を挙げておきます
# -*- coding: utf-8 -*-from genshi.builder import tagfrom genshi.filters.transform import Transformerfrom trac.core import Componentfrom trac.core import implementsfrom trac.web.api import IRequestFilterfrom trac.web.api import ITemplateStreamFilterclass TicketDBQueryFieldFilter(Component):"""チケット表示画面でDBにqueryして表示を変更するfilter"""implements(IRequestFilter, ITemplateStreamFilter)FIELDS_INFO = {}def __init__(self):options = self.config.options('dbqueryfield')for _option, _value in options:self.log.debug("option = ")o = _option.split('.')self.log.debug(o)field = o[0]option = o[1]try:opt = self.FIELDS_INFO[field]except:self.FIELDS_INFO[field]={}opt = self.FIELDS_INFO[field]opt['type'] = ''opt[option] = _value.replace("$(","%(")for key in self.FIELDS_INFO.keys():v = self.FIELDS_INFO[key]operation = v['operation']k = v['field']if operation=='readonly': #ReadOnlyにするv['id'] = 'field-%s' % kv['name'] = 'field_%s' % kif v['field'] in ('type', 'priority', 'milestone', 'component', 'version', 'severity'):v['xpath'] = '//select[@id="field-%s"]' % kelse:v['xpath'] = '//input[@id="field-%s"]' % kelif operation=='header':v['xpath'] = '//td[@headers="h_%s"]/text()' % kelif operation=='field':v['id'] = 'field-%s'v['name'] = 'field_%s' % kv['xpath'] = '//input[@id="field-%s"]' % kelif operation=='action':v['id'] = 'action_%s_owner' % kv['name'] = 'action_%s_owner' % kv['xpath'] = '//input[@id="action_%s_owner"]' % kv['operation'] = 'field'else:self.log.error('Unknown operation(%s).' % operation)# IRequestFilter methodsdef pre_process_request(self, req, handler):return handlerdef post_process_request(self, req, template, data, content_type):if req.path_info.startswith('/ticket/'):field_values = []tkt = data['ticket']tkt['id'] = tkt.idtkt['authuser'] = req.authnamecursor = self.env.get_db_cnx().cursor()for key in self.FIELDS_INFO.keys():v = self.FIELDS_INFO[key]if v['type'] and v['type'] != tkt['type']:continueoperation = v['operation']k = v['field']try:field_value = tkt[k]except:field_value = ''value = Nonexpath = v['xpath']if operation=='readonly':value = tag.input(id=v['id'], name=v['name'], value=tkt[k], readonly=1)elif operation=='header':if tkt[k]: #カスタムフィールドに値が入っている場合sel_data = []cursor.execute(v['sql'] % tkt)sel_data.append(field_value)for row in cursor:sel_data.append(tag.br())sel_data.append(row[0])breakvalue = tag.span(*sel_data)elif operation=='field':sel_data = []cursor.execute(v['sql'] % tkt)options=[]for row in cursor:if str(row[2]) == '1':options.append(tag.option(row[1], value = row[0], selected = True))else:options.append(tag.option(row[1], value = row[0]))value = tag.select(options, id=v['id'], name=v['name'])if value:field_values.append({'key':k, 'value':value, 'xpath':xpath})data['filter'] = field_valuesreturn template, data, content_type# ITemplateStreamFilter methodsdef filter_stream(self, req, method, filename, stream, data):if 'filter' in data:for r in data['filter']:path = r['xpath']stream |= Transformer(path).replace(r['value'])return stream
プラグインのファイル「TicketDBQueryFieldPlugin.zip」をダウンロード
| 固定リンク
| コメント (4)
| トラックバック (0)
最近のコメント