Attachment 'TaskTable.py'
Download 1 """
2 MoinMoin - TaskTable Macro
3 Version 0.9
4
5 This macro is the corresponding user interface for the TaskPlanner action.
6
7 Usage:
8 [[TaskTable]]
9 Display tasks for current page
10
11 [[TaskTable(page=SomeWikiPage)]]
12 Display tasks for page 'SomeWikiPage'
13
14 [[TaskTable(assign=UserName)]]
15 Display tasks assigned to 'UserName'
16
17 [[TaskTable(page=SomeWikiPage, assign=UserName)]]
18 Display tasks assigned to 'UserName' for page 'SomeWikiPage'
19
20 [[TaskTable(sort=status)]]
21 [[TaskTable(sort=date)]]
22 Display tasks sorted by given criteria
23
24 [[TaskTable(readonly=1, closed=1)]]
25
26 args:
27 @page=string: display tasks appended to an other page (default=CurrentPage)
28 @assign=string: display only tasks for a specific user (default=None)
29 @sort=string: sort task by either by status or date (default=sort by prio, then date)
30 @closed=bool: display the table in a closed/open state overriding the default cfg value 'task_table_compact'
31 @readonly=bool: display the table without the 'remove task' option
32
33 TaskTable is heavily based on AttachTable.
34 AttachTable
35 @copyright: 2006 Erick Martin
36
37 TaskTable
38 @copyright: 2007 Oliver Siemoneit
39 @license: GNU GPL, see COPYING for details.
40
41 """
42
43 import os, random
44 from MoinMoin import config, wikiutil
45 from MoinMoin.Page import Page
46
47
48 def _get_tasks(request, pagename, assign):
49 tasks = []
50 databaseFile = os.path.join(request.cfg.data_dir, "task-db")
51 if os.path.isfile(databaseFile):
52 for line in open(databaseFile).readlines():
53 tmp = line.split("\t")
54 # we have a pagename to search for
55 if pagename:
56 if tmp[12].find(pagename) != -1:
57 # do we also have some restrictions concerning assign?
58 if assign:
59 if tmp[6].find(assign) != -1:
60 tasks.append(tmp)
61 else:
62 tasks.append(tmp)
63 # we only have an assign to search for
64 elif assign:
65 if tmp[6].find(assign) != -1:
66 tasks.append(tmp)
67 return tasks
68
69 return []
70
71
72 def _sort_tasks_default(task1, task2):
73
74 result = _sort_tasks_by_prio(task1, task2)
75
76 # if tasks are not equal, return result
77 if result != 0:
78 return result
79 # tasks are equal, sort them by date
80 return _sort_tasks_by_date(task1, task2)
81
82
83 def _sort_tasks_by_prio(task1, task2):
84 prio = { 'none' : 4,
85 'low' : 3,
86 'medium' : 2,
87 'high' : 1,
88 'critical' : 0 }
89
90 task1_prio_str = task1[8]
91 if task1_prio_str == '':
92 task1_prio_str = 'none'
93 task1_prio = prio[task1_prio_str]
94
95 task2_prio_str = task2[8]
96 if task2_prio_str == '':
97 task2_prio_str = 'none'
98 task2_prio = prio[task2_prio_str]
99
100 return cmp(task1_prio, task2_prio)
101
102
103 def _sort_tasks_by_date(task1, task2):
104 from time import strptime
105 time1 = task1[7].strip(' ')
106 if time1 != "":
107 try:
108 time_struct_1 = strptime(time1, "%d.%m.%y")
109 except ValueError:
110 try:
111 time_struct_1 = strptime(time1, "%d.%m.%y %H:%M")
112 except ValueError:
113 pass # this should never happen since only valid datetime objects are saved in the database
114 else:
115 return 1
116
117 time2 = task2[7].strip(' ')
118 if time2 != "":
119 try:
120 time_struct_2 = strptime(time2, "%d.%m.%y")
121 except ValueError:
122 try:
123 time_struct_2 = strptime(time2, "%d.%m.%y %H:%M")
124 except ValueError:
125 pass # this should never happen since only valid datetime objects are saved in the database
126 else:
127 return -1
128
129 return cmp(time_struct_1, time_struct_2)
130
131
132 def _sort_tasks_by_status(task1, task2):
133 status = { 'to do' : 0,
134 'in progress' : 1,
135 'pending' : 2,
136 'done' : 3,
137 'failed' : 4,
138 'closed' : 5,
139 'remove me' : 6 }
140
141 task1_status_str = task1[9]
142 task1_status = status[task1_status_str]
143
144 task2_status_str = task2[9]
145 task2_status = status[task2_status_str]
146
147 return cmp(task1_status, task2_status)
148
149
150 def _table_row(d):
151 row = (' <tr style="display:">\n'
152 ' <td> %(data_1)s </td>\n'
153 ' <td> %(data_2)s </td>\n'
154 ' <td> %(data_3)s </td>\n'
155 ' <td> %(data_4)s </td>\n'
156 ' <td> %(data_5)s </td>\n'
157 ' <td> %(data_6)s </td>\n'
158 ' <td> %(data_7)s </td>\n'
159 ' </tr>\n') % d
160 return row
161
162
163 def file_table(request, pagename, assign, sort_by=None, display_table_closed=0, readonly=0):
164 _ = request.getText
165 action = 'TaskPlanner'
166 page_url = wikiutil.quoteWikinameURL(request.page.page_name)
167
168 # if we have an assign or a page different than the current page forward this information
169 # to the task planner user interface
170 url_ext_page = url_ext_assign = ""
171 if pagename and (request.page.page_name != pagename):
172 pagename = wikiutil.escape(pagename, 1)
173 pagename = pagename.replace(' ', '%20')
174 url_ext_page = "&page=%s" % pagename
175 if assign:
176 assign = wikiutil.escape(assign, 1)
177 assign = assign.replace(' ', '%20')
178 url_ext_assign = "&assign=%s" % assign
179
180 manage_url = '<a href="%(baseurl)s/%(page_url)s?action=%(action)s%(ext1)s%(ext2)s" title="%(title)s" alt="%(title)s">%(text)s</a>' % {
181 'baseurl': request.getScriptname(),
182 'page_url': page_url,
183 'action': action,
184 'ext1': url_ext_page,
185 'ext2': url_ext_assign,
186 'title': _('Add/Manage tasks'),
187 'text': _('Add/Manage'), }
188
189 tasks = _get_tasks(request, pagename, assign)
190
191 page_text = assign_text = ""
192 if pagename:
193 page_text = " " + _("for the '''%(pagename)s''' page") % {'pagename': pagename, }
194 if assign:
195 assign_text = " " + _("for user '''%(assign)s'''") % {'assign': assign, }
196
197 if not tasks:
198 if pagename and Page(request, pagename).exists() == False:
199 return _("\n%(icon)s %(manage_url)s tasks %(assign_text)s%(page_text)s %(error)s\n ") % {
200 'icon': request.theme.make_icon('table-null'),
201 'manage_url': manage_url,
202 'assign_text': assign_text,
203 'page_text': page_text,
204 'error': _("''[No such page]''"), }
205 else:
206 return _("\n%(icon)s %(manage_url)s tasks %(assign_text)s %(page_text)s \n") % {
207 'icon': request.theme.make_icon('table-null'),
208 'manage_url': manage_url,
209 'assign_text': assign_text,
210 'page_text': page_text, }
211
212
213 table_closed_file_name = 'table-open.png'
214 table_open_file_name = 'table-close.png'
215
216 open_image = request.theme.img_url(table_open_file_name)
217 closed_image = request.theme.img_url(table_closed_file_name)
218
219 javascript_function = """
220 // Toggle display of a folder's contents.
221 function showHideContents ( idnum ) {
222 the_table = document.getElementById( 't' + idnum );
223
224 arrowObj = document.getElementById( 'a' + idnum );
225 // we use the image to determine if the table is open or closed
226 if (arrowObj.src.indexOf('%(table_open_file_name)s') > -1) {
227 arrowObj.src = '%(closed_image)s';
228 display = 'none';
229 } else {
230 arrowObj.src = '%(open_image)s';
231 display = '';
232 }
233
234 // change the open/closed state of the rows for the table
235 for (var i = 0; i < the_table.rows.length; i++) {
236 the_table.rows[i].style.display = display;
237 }
238 }
239 """ % {'closed_image': closed_image,
240 'open_image': open_image,
241 'table_open_file_name' : table_open_file_name, }
242
243 html = ("\n"
244 '<script type="text/javascript">' "\n"
245 "<!--\n"
246 "%(javascript_function)s\n"
247 "--> </script>\n"
248 "\n" ) % { 'javascript_function' : javascript_function, }
249
250 file_id = random.randint(1, 99999999999)
251
252 html = html + '\n<div class="attachmentTable" >\n'
253
254 table_caption1 = ('<a onClick="showHideContents(%(file_id)s);" title="%(alt_title)s">'
255 '<img id="a%(file_id)s" align="middle" border=0 src="%(open_image)s"'
256 'alt="%(alt_title)s"></a>\n') % {
257 'file_id': file_id,
258 'alt_title': _("Click to open/close table"),
259 'open_image': open_image, }
260
261 table_caption2 = _("%(manage_url)s tasks%(assign_text)s%(page_text)s ''[%(num_files)s task(s)]''") % {
262 'num_files': len(tasks),
263 'page_text': page_text,
264 'assign_text': assign_text,
265 'manage_url': manage_url, }
266
267 table_caption = table_caption1 + table_caption2
268
269 html = html + ('<br>\n'
270 '%(table_caption)s\n'
271 '<table id="t%(file_id)s" >\n'
272 ' <tr style="display:">\n'
273 ' <th>%(id)s</th>\n'
274 ' <th>%(name)s</th>\n'
275 ' <th>%(assign)s</th>\n'
276 ' <th>%(priority)s</th>\n'
277 ' <th>%(status)s</th>\n'
278 ' <th>%(date)s</th>\n'
279 ' <th>%(action)s</th>\n'
280 ' </tr>\n') % {
281 'file_id': file_id,
282 'table_caption': table_caption,
283 'id': _('ID'),
284 'name': _('Name'),
285 'assign': _('Assign'),
286 'priority': _('Priority'),
287 'status': _('Status'),
288 'date': _('Date'),
289 'action': _('Information'), }
290
291 # if user is not superuser, force readonly mode
292 if not request.user.isSuperUser():
293 readonly = 1
294
295 # sort tasks
296 if sort_by == 'date':
297 tasks.sort(_sort_tasks_by_date)
298 elif sort_by == 'status':
299 tasks.sort(_sort_tasks_by_status)
300 else:
301 tasks.sort(_sort_tasks_default)
302
303
304 # output tasks
305 for task in tasks:
306 # build icon
307 task_prio_status = "task-%(prio)s-%(status)s" % {'prio': task[8].lower().replace(' ', ''),
308 'status': task[9].lower().replace(' ', ''), }
309 if task_prio_status in request.theme.icons:
310 image = request.theme.make_icon(task_prio_status)
311 else:
312 if task[9] == "closed":
313 image = request.theme.make_icon('task-closed')
314 elif task[9] == "remove me":
315 image = request.theme.make_icon('task-removeme')
316 else:
317 image = request.theme.make_icon('task--inprogress')
318
319 # create link to task's homepage
320 if task[11].replace(' ', '') != "":
321 task_homepage = request.formatter.pagelink(1, task[11]) + request.formatter.text(task[0]) + request.formatter.pagelink(0, task[11])
322 else:
323 task_homepage = "<strong>%(id)s<strong>" % {'id': task[0], }
324
325 # create 'edit task' link
326 ticket = wikiutil.createTicket(request)
327 edit_link = '<a class="smallText" href="%(baseurl)s/%(page_url)s?action=TaskPlanner&edit=%(task_id)s&ticket=%(ticket)s">%(text)s</a>' % {
328 'baseurl': request.getScriptname(),
329 'page_url': page_url,
330 'task_id': task[0],
331 'ticket': ticket,
332 'text': _('[Edit task]'), }
333
334 # create 'remove task' link
335 if readonly == 0:
336 remove_link = '<a class="smallText" href="%(baseurl)s/%(page_url)s?action=TaskPlanner&remove=%(task_id)s&ticket=%(ticket)s">%(text)s</a>' % {
337 'baseurl': request.getScriptname(),
338 'page_url': page_url,
339 'task_id': task[0],
340 'ticket': ticket,
341 'text': _('[Remove task]'), }
342 else:
343 remove_link=""
344
345 data_1 = "<strong>%(image)s %(task_id)s</strong>" % {'image': image, 'task_id': task_homepage, }
346 data_2 = "%(task_name)s" % {'task_name': task[5], }
347 data_3 = "%(assign)s" % {'assign': task[6], }
348 data_4 = "%(priority)s" % {'priority': _(task[8]), }
349 data_5 = "%(status)s" % {'status': _(task[9]), }
350 data_6 = "%(time_frame)s" % {'time_frame': task[7], }
351 data_7 = "%(desc)s <p>" % { 'desc': task[10], }
352 if task[3] == 'unknown':
353 data_7 += '<p class="smallText"> %(label1)s %(created_by)s %(label2)s</p><p>' % {'label1': _('Task created by'),
354 'created_by': task[1],
355 'label2': _('on'), }
356 data_7 += '<p class="smallText">%(created_at)s</p><p>' % {'created_at': task[2], }
357 else:
358 data_7 += '<p class="smallText"> %(label1)s %(closed_by)s %(label2)s</p><p>' % {'label1': _('Task closed by'),
359 'closed_by': task[3],
360 'label2': _('on'), }
361 data_7 += '<p class="smallText">%(closed_at)s</p><p>' % {'closed_at': task[4], }
362 data_7 += '<p class="smallText">%(edit)s %(remove)s</p>' % {'edit': edit_link, 'remove': remove_link, }
363
364 html = html + _table_row( {
365 'data_1' : data_1,
366 'data_2' : data_2,
367 'data_3' : data_3,
368 'data_4' : data_4,
369 'data_5' : data_5,
370 'data_6' : data_6,
371 'data_7' : data_7,
372 } )
373
374 html = html + '\n</table>\n</div>\n'
375
376 if display_table_closed:
377 close_str = ('\n<script type="text/javascript">\n'
378 '<!--\nshowHideContents(%(file_id)s);\n-->\n'
379 '</script>\n' ) % {'file_id': file_id, }
380 html = html + close_str
381
382 return html
383
384
385 def getArgs(given_arguments, allowed_arguments):
386 if not given_arguments:
387 return {}
388 args = {}
389 for s in given_arguments.split(','):
390 if s and s.find('=') > 0:
391 key, value = s.split('=', 1)
392 if key and value:
393 key = key.strip()
394 if key in allowed_arguments:
395 args[key] = value.strip()
396 return args
397
398
399 def execute(macro, options):
400 request = macro.request
401
402 arguments = ['page', 'assign', 'sort', 'closed', 'readonly']
403 args = getArgs(options, arguments)
404
405 assign = args.get('assign', None)
406
407 pagename = args.get('page', None)
408 if not pagename and not assign:
409 pagename = request.page.page_name
410
411 default_table_closed = 0
412 if hasattr(request.cfg, 'task_table_compact'):
413 default_table_closed = request.cfg.task_table_compact
414
415 table_closed = int(args.get('closed', default_table_closed))
416 table_readonly = int(args.get('readonly', 0))
417
418 sort_by = args.get('sort', None)
419 if sort_by not in ['date', 'status']:
420 sort_by = None
421
422 return file_table(request, pagename, assign, sort_by, table_closed, table_readonly)
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.