TracCompleteUserPlugin: new version 0.5.0
@@ -1,64 +1,34 @@ | ||
1 | 1 | # -*- coding: utf-8 -*- |
2 | -from trac.core import * | |
3 | -from trac.config import Option, BoolOption, Configuration | |
4 | -from trac.web.chrome import add_script,add_stylesheet,ITemplateProvider | |
5 | -from trac.web.main import IRequestHandler, IRequestFilter | |
6 | -from trac.web.api import ITemplateStreamFilter | |
7 | -from trac.util.html import Markup | |
8 | 2 | |
9 | -from genshi.filters.transform import Transformer | |
10 | -from genshi.template import MarkupTemplate | |
11 | -from json.encoder import JSONEncoder | |
12 | 3 | import re |
13 | 4 | |
5 | +from trac.config import BoolOption, FloatOption, ListOption | |
6 | +from trac.core import Component, implements | |
7 | +from trac.util.presentation import to_json | |
8 | +from trac.web.chrome import ITemplateProvider, add_script, add_script_data, \ | |
9 | + add_stylesheet | |
10 | +from trac.web.main import IRequestHandler, IRequestFilter | |
14 | 11 | |
12 | + | |
15 | 13 | class CompleteUserWeb(Component): |
16 | - implements(IRequestHandler, IRequestFilter, ITemplateProvider, ITemplateStreamFilter) | |
17 | 14 | |
18 | - single_fields = Option('complete-user','single_fields',['owner','reporter'], u"""単一ユーザを補完するフィールド設定します。"""); | |
19 | - composite_fields = Option('complete-user','composite_fields','cc', u"""複数ユーザを補完するフィールド設定します。"""); | |
20 | - name_check = BoolOption('complete-user','name_check', 'true', u"""ユーザを検索する際にNameが設定されているユーザのみを検索するかどうかを設定します。"""); | |
21 | - popup_delay = Option('complete-user','popup_delay','1.5', | |
22 | - u"""補完ウィンドウのポップアップ表示までの遅延時間を設定します。 | |
23 | - | |
24 | - 単位は秒で小数点も利用することができます | |
25 | - """) | |
15 | + implements(IRequestHandler, IRequestFilter, ITemplateProvider) | |
26 | 16 | |
17 | + single_fields = ListOption('complete-user', 'single_fields', 'owner,reporter', | |
18 | + doc=u"""単一ユーザを補完するフィールド設定します。""") | |
19 | + composite_fields = ListOption('complete-user', 'composite_fields', 'cc', | |
20 | + doc=u"""複数ユーザを補完するフィールド設定します。""") | |
21 | + name_check = BoolOption('complete-user', 'name_check', 'true', | |
22 | + doc=u"""ユーザを検索する際にNameが設定されているユーザのみを検索するかどうかを設定します。""") | |
23 | + popup_delay = FloatOption('complete-user', 'popup_delay', '1.5', | |
24 | + doc=u"""補完ウィンドウのポップアップ表示までの遅延時間を設定します。 | |
27 | 25 | |
28 | - def filter_stream(self, req, method, filename, stream, data): | |
29 | - if re.match(r'^/(ticket|newticket|admin|query)', req.path_info): | |
30 | - single_fields = Configuration.getlist(self.config,"complete-user","single_fields",['owner','reporter']); | |
31 | - composite_fields = Configuration.getlist(self.config,"complete-user","composite_fields","cc"); | |
32 | - popup_delay = Configuration.get(self.config,"complete-user","popup_delay","1.5"); | |
33 | - name_check = Configuration.getbool(self.config,"complete-user","name_check", "true"); | |
26 | + 単位は秒で小数点も利用することができます""") | |
34 | 27 | |
35 | - script = "<script type='text/javascript'>\n" | |
36 | - script = script+"(function($){\n" | |
37 | - script = script+"$(function(){\n" | |
38 | - script = script+" ultimania.trac.POPUP_DELAY = " + popup_delay + ";\n" | |
28 | + # IRequestFilter methods | |
39 | 29 | |
40 | - script = script+" ultimania.trac.setAutoComplete('action_reassign_reassign_owner','" + req.base_path + "/completeuser',false);\n" | |
41 | - script = script+" ultimania.trac.setAutoComplete('gp_subject','" + req.base_path + "/completeuser',false);\n" | |
42 | - script = script+" ultimania.trac.setAutoComplete('sg_subject','" + req.base_path + "/completeuser',false);\n" | |
43 | - script = script+" ultimania.trac.setAutoComplete('bmod_value_owner','" + req.base_path + "/completeuser',false);\n" | |
44 | - script = script+" ultimania.trac.setAutoComplete('bmod_value_cc','" + req.base_path + "/completeuser',true);\n" | |
45 | - for field in single_fields: | |
46 | - script = script+" ultimania.trac.setAutoComplete('field-" + field + "','" + req.base_path + "/completeuser',false);\n" | |
47 | - for field in composite_fields: | |
48 | - script = script+" ultimania.trac.setAutoComplete('field-" + field + "','" + req.base_path + "/completeuser',true);\n" | |
49 | - script = script+"})\n" | |
50 | - script = script+"})(jQuery);\n" | |
51 | - script = script+"</script>" | |
52 | - return stream | Transformer('//div[@id="footer"]').before(MarkupTemplate(script).generate()) | |
53 | - else: | |
54 | - return stream | |
55 | - | |
56 | - # IRequestFilter methods | |
57 | 30 | def pre_process_request(self, req, handler): |
58 | 31 | return handler |
59 | - | |
60 | - def post_process_request(self, req, template, content_type): | |
61 | - return (template, content_type) | |
62 | 32 | |
63 | 33 | def post_process_request(self, req, template, data, content_type): |
64 | 34 | if re.match(r'^/(ticket|newticket|admin|query)', req.path_info): |
@@ -67,47 +37,43 @@ | ||
67 | 37 | add_script(req, 'completeuser/js/yui/utilities/utilities.js') |
68 | 38 | add_script(req, 'completeuser/js/yui/autocomplete/autocomplete-min.js') |
69 | 39 | add_script(req, 'completeuser/js/trac-completeuser.js') |
70 | - return (template, data, content_type) | |
40 | + script_data = {'base_path': req.href(), | |
41 | + 'single_fields': self.single_fields, | |
42 | + 'composite_fields': self.composite_fields, | |
43 | + 'popup_delay': self.popup_delay} | |
44 | + add_script_data(req, {'completeuser': script_data}) | |
45 | + return template, data, content_type | |
71 | 46 | |
72 | 47 | # IRequestHandler methods |
48 | + | |
73 | 49 | def match_request(self, req): |
74 | - if req.path_info in ('/login/completeuser', '/completeuser'): | |
75 | - self.log.debug("%s matches %s" % (req.path_info, True)) | |
76 | - return True | |
77 | - return False | |
50 | + return req.path_info in ('/login/completeuser', '/completeuser') | |
78 | 51 | |
79 | 52 | def process_request(self, req): |
80 | - name_check = Configuration.getbool(self.config,"complete-user","name_check", "True"); | |
81 | 53 | query = req.args.get('query') |
82 | - db = self.env.get_db_cnx() | |
83 | - cursor = db.cursor(); | |
84 | - join = "" | |
85 | - where = "" | |
86 | - | |
87 | - if name_check: | |
88 | - join = "JOIN" | |
54 | + db = self.env.get_read_db() | |
55 | + cursor = db.cursor() | |
56 | + | |
57 | + join = ("LEFT OUTER JOIN", "JOIN")[self.name_check] | |
58 | + sql = ("SELECT s.sid, sa.value FROM session s " + | |
59 | + "%s session_attribute sa ON s.sid=sa.sid AND sa.name='name' " + | |
60 | + "WHERE s.authenticated=1") % join | |
61 | + if query: | |
62 | + like = db.like() | |
63 | + sql += " AND (s.sid %s OR sa.value %s)" % (like, like) | |
64 | + args = ['%' + db.like_escape(query) + '%'] * 2 | |
89 | 65 | else: |
90 | - join = "LEFT OUTER JOIN" | |
91 | - if query != None: | |
92 | - where = ("AND (s.sid LIKE '%%%s%%' or sa.value LIKE '%%%s%%')") % (query,query) | |
93 | - | |
94 | - sql = ("SELECT s.sid, sa.value FROM session s %s session_attribute sa ON s.sid = sa.sid AND sa.name = 'name' WHERE s.authenticated = 1 %s") % (join,where) | |
66 | + args = [] | |
95 | 67 | |
96 | - cursor.execute(sql) | |
97 | - userlist = [] | |
98 | - for sid, value in cursor: | |
99 | - userlist.append({"Id":sid, "Name": value}); | |
100 | - response = JSONEncoder().encode({"UserList":userlist}).encode('utf-8') | |
101 | - self.log.debug("%s matches %s" % (req.path_info, True)) | |
68 | + cursor.execute(sql, args) | |
69 | + userlist = [{'Id': sid, 'Name': value} for sid, value in cursor] | |
70 | + response = to_json({'UserList': userlist}) | |
71 | + if isinstance(response, unicode): | |
72 | + response = response.encode('utf-8') | |
73 | + req.send(response, 'application/json') | |
102 | 74 | |
103 | - req.send_response(200) | |
104 | - req.send_header('Content-Type', 'application/json; charset=utf-8') | |
105 | - req.send_header('Content-Length', len(response.encode("utf-8"))) | |
75 | + # ITemplateProvider methods | |
106 | 76 | |
107 | - req.end_headers() | |
108 | - req.write(response) | |
109 | - | |
110 | - # ITemplateProvider methods | |
111 | 77 | def get_templates_dirs(self): |
112 | 78 | return [] |
113 | 79 |
@@ -46,3 +46,23 @@ | ||
46 | 46 | ); |
47 | 47 | */ |
48 | 48 | } |
49 | +jQuery(document).ready(function($){ | |
50 | + var data = window.completeuser; | |
51 | + if (data === undefined) | |
52 | + return; | |
53 | + var url = data.base_path + '/completeuser'; | |
54 | + var single_fields = data.single_fields || []; | |
55 | + var composite_fields = data.composite_fields || []; | |
56 | + ultimania.trac.POPUP_DELAY = data.popup_delay || 0.5; | |
57 | + ultimania.trac.setAutoComplete('action_reassign_reassign_owner', url, false); | |
58 | + ultimania.trac.setAutoComplete('gp_subject', url, false); | |
59 | + ultimania.trac.setAutoComplete('sg_subject', url, false); | |
60 | + ultimania.trac.setAutoComplete('bmod_value_owner', url, false); | |
61 | + ultimania.trac.setAutoComplete('bmod_value_cc', url, true); | |
62 | + $.each(single_fields, function(idx, field) { | |
63 | + ultimania.trac.setAutoComplete('field-' + field, url, false); | |
64 | + }); | |
65 | + $.each(composite_fields, function(idx, field) { | |
66 | + ultimania.trac.setAutoComplete('field-' + field, url, true); | |
67 | + }); | |
68 | +}); |
@@ -5,7 +5,7 @@ | ||
5 | 5 | |
6 | 6 | setup( |
7 | 7 | name='TracCompleteUserPlugin', |
8 | - version='0.4', | |
8 | + version='0.5.0', | |
9 | 9 | zip_safe = True, |
10 | 10 | packages=find_packages(exclude=['*.tests*']), |
11 | 11 |
@@ -20,11 +20,13 @@ | ||
20 | 20 | 'completeuser.web_ui = completeuser.web_ui', |
21 | 21 | ] |
22 | 22 | }, |
23 | - package_data={'completeuser': [ | |
24 | -'htdocs/js/trac-completeuser.js', | |
25 | -'htdocs/js/yui/autocomplete/autocomplete-min.js', | |
26 | -'htdocs/js/yui/utilities/utilities.js', | |
27 | -'htdocs/css/trac-completeuser.css', | |
28 | -'htdocs/js/yui/autocomplete/assets/skins/sam/autocomplete.css']}, | |
29 | - | |
30 | -) | |
\ No newline at end of file | ||
23 | + package_data={ | |
24 | + 'completeuser': [ | |
25 | + 'htdocs/js/trac-completeuser.js', | |
26 | + 'htdocs/js/yui/autocomplete/autocomplete-min.js', | |
27 | + 'htdocs/js/yui/utilities/utilities.js', | |
28 | + 'htdocs/css/trac-completeuser.css', | |
29 | + 'htdocs/js/yui/autocomplete/assets/skins/sam/autocomplete.css', | |
30 | + ], | |
31 | + }, | |
32 | +) |