comparison examples/trac/trac/wiki/web_ui.py @ 39:93b4dcbafd7b trunk

Copy Trac to main branch.
author cmlenz
date Mon, 03 Jul 2006 18:53:27 +0000
parents
children
comparison
equal deleted inserted replaced
38:ee669cb9cccc 39:93b4dcbafd7b
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2003-2006 Edgewall Software
4 # Copyright (C) 2003-2005 Jonas Borgström <jonas@edgewall.com>
5 # Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
6 # All rights reserved.
7 #
8 # This software is licensed as described in the file COPYING, which
9 # you should have received as part of this distribution. The terms
10 # are also available at http://trac.edgewall.com/license.html.
11 #
12 # This software consists of voluntary contributions made by many
13 # individuals. For the exact contribution history, see the revision
14 # history and logs, available at http://projects.edgewall.com/trac/.
15 #
16 # Author: Jonas Borgström <jonas@edgewall.com>
17 # Christopher Lenz <cmlenz@gmx.de>
18
19 import os
20 import re
21 import StringIO
22
23 from trac.attachment import attachments_to_hdf, Attachment, AttachmentModule
24 from trac.core import *
25 from trac.perm import IPermissionRequestor
26 from trac.Search import ISearchSource, search_to_sql, shorten_result
27 from trac.Timeline import ITimelineEventProvider
28 from trac.util import get_reporter_id
29 from trac.util.datefmt import format_datetime, pretty_timedelta
30 from trac.util.text import shorten_line
31 from trac.util.markup import html, Markup
32 from trac.versioncontrol.diff import get_diff_options, hdf_diff
33 from trac.web.chrome import add_link, add_stylesheet, INavigationContributor
34 from trac.web import HTTPNotFound, IRequestHandler
35 from trac.wiki.api import IWikiPageManipulator, WikiSystem
36 from trac.wiki.model import WikiPage
37 from trac.wiki.formatter import wiki_to_html, wiki_to_oneliner
38 from trac.mimeview.api import Mimeview, IContentConverter
39
40
41 class InvalidWikiPage(TracError):
42 """Exception raised when a Wiki page fails validation."""
43
44
45 class WikiModule(Component):
46
47 implements(INavigationContributor, IPermissionRequestor, IRequestHandler,
48 ITimelineEventProvider, ISearchSource, IContentConverter)
49
50 page_manipulators = ExtensionPoint(IWikiPageManipulator)
51
52 # IContentConverter methods
53 def get_supported_conversions(self):
54 yield ('txt', 'Plain Text', 'txt', 'text/x-trac-wiki', 'text/plain', 9)
55
56 def convert_content(self, req, mimetype, content, key):
57 return (content, 'text/plain;charset=utf-8')
58
59 # INavigationContributor methods
60
61 def get_active_navigation_item(self, req):
62 return 'wiki'
63
64 def get_navigation_items(self, req):
65 if not req.perm.has_permission('WIKI_VIEW'):
66 return
67 yield ('mainnav', 'wiki',
68 html.A('Wiki', href=req.href.wiki(), accesskey=1))
69 yield ('metanav', 'help',
70 html.A('Help/Guide', href=req.href.wiki('TracGuide'),
71 accesskey=6))
72
73 # IPermissionRequestor methods
74
75 def get_permission_actions(self):
76 actions = ['WIKI_CREATE', 'WIKI_DELETE', 'WIKI_MODIFY', 'WIKI_VIEW']
77 return actions + [('WIKI_ADMIN', actions)]
78
79 # IRequestHandler methods
80
81 def match_request(self, req):
82 match = re.match(r'^/wiki(?:/(.*))?', req.path_info)
83 if match:
84 if match.group(1):
85 req.args['page'] = match.group(1)
86 return 1
87
88 def process_request(self, req):
89 action = req.args.get('action', 'view')
90 pagename = req.args.get('page', 'WikiStart')
91 version = req.args.get('version')
92
93 if pagename.endswith('/'):
94 req.redirect(req.href.wiki(pagename.strip('/')))
95
96 db = self.env.get_db_cnx()
97 page = WikiPage(self.env, pagename, version, db)
98
99 add_stylesheet(req, 'common/css/wiki.css')
100
101 if req.method == 'POST':
102 if action == 'edit':
103 latest_version = WikiPage(self.env, pagename, None, db).version
104 if req.args.has_key('cancel'):
105 req.redirect(req.href.wiki(page.name))
106 elif int(version) != latest_version:
107 action = 'collision'
108 self._render_editor(req, db, page)
109 elif req.args.has_key('preview'):
110 action = 'preview'
111 self._render_editor(req, db, page, preview=True)
112 else:
113 self._do_save(req, db, page)
114 elif action == 'delete':
115 self._do_delete(req, db, page)
116 elif action == 'diff':
117 get_diff_options(req)
118 req.redirect(req.href.wiki(page.name, version=page.version,
119 action='diff'))
120 elif action == 'delete':
121 self._render_confirm(req, db, page)
122 elif action == 'edit':
123 self._render_editor(req, db, page)
124 elif action == 'diff':
125 self._render_diff(req, db, page)
126 elif action == 'history':
127 self._render_history(req, db, page)
128 else:
129 format = req.args.get('format')
130 if format:
131 Mimeview(self.env).send_converted(req, 'text/x-trac-wiki',
132 page.text, format, page.name)
133 self._render_view(req, db, page)
134
135 req.hdf['wiki.action'] = action
136 req.hdf['wiki.current_href'] = req.href.wiki(page.name)
137 return 'wiki.cs', None
138
139 # ITimelineEventProvider methods
140
141 def get_timeline_filters(self, req):
142 if req.perm.has_permission('WIKI_VIEW'):
143 yield ('wiki', 'Wiki changes')
144
145 def get_timeline_events(self, req, start, stop, filters):
146 if 'wiki' in filters:
147 wiki = WikiSystem(self.env)
148 format = req.args.get('format')
149 href = format == 'rss' and req.abs_href or req.href
150 db = self.env.get_db_cnx()
151 cursor = db.cursor()
152 cursor.execute("SELECT time,name,comment,author,version "
153 "FROM wiki WHERE time>=%s AND time<=%s",
154 (start, stop))
155 for t,name,comment,author,version in cursor:
156 title = Markup('<em>%s</em> edited by %s',
157 wiki.format_page_name(name), author)
158 diff_link = html.A('diff', href=href.wiki(name, action='diff',
159 version=version))
160 if format == 'rss':
161 comment = wiki_to_html(comment or '--', self.env, req, db,
162 absurls=True)
163 else:
164 comment = wiki_to_oneliner(comment, self.env, db,
165 shorten=True)
166 if version > 1:
167 comment = Markup('%s (%s)', comment, diff_link)
168 yield 'wiki', href.wiki(name), title, t, author, comment
169
170 # Attachments
171 def display(id):
172 return Markup('ticket ', html.EM('#', id))
173 att = AttachmentModule(self.env)
174 for event in att.get_timeline_events(req, db, 'wiki', format,
175 start, stop,
176 lambda id: html.EM(id)):
177 yield event
178
179 # Internal methods
180
181 def _set_title(self, req, page, action):
182 title = name = WikiSystem(self.env).format_page_name(page.name)
183 if action:
184 title += ' (%s)' % action
185 req.hdf['wiki.page_name'] = name
186 req.hdf['title'] = title
187 return title
188
189 def _do_delete(self, req, db, page):
190 if page.readonly:
191 req.perm.assert_permission('WIKI_ADMIN')
192 else:
193 req.perm.assert_permission('WIKI_DELETE')
194
195 if req.args.has_key('cancel'):
196 req.redirect(req.href.wiki(page.name))
197
198 version = int(req.args.get('version', 0)) or None
199 old_version = int(req.args.get('old_version', 0)) or version
200
201 if version and old_version and version > old_version:
202 # delete from `old_version` exclusive to `version` inclusive:
203 for v in range(old_version, version):
204 page.delete(v + 1, db)
205 else:
206 # only delete that `version`, or the whole page if `None`
207 page.delete(version, db)
208 db.commit()
209
210 if not page.exists:
211 req.redirect(req.href.wiki())
212 else:
213 req.redirect(req.href.wiki(page.name))
214
215 def _do_save(self, req, db, page):
216 if page.readonly:
217 req.perm.assert_permission('WIKI_ADMIN')
218 elif not page.exists:
219 req.perm.assert_permission('WIKI_CREATE')
220 else:
221 req.perm.assert_permission('WIKI_MODIFY')
222
223 page.text = req.args.get('text')
224 if req.perm.has_permission('WIKI_ADMIN'):
225 # Modify the read-only flag if it has been changed and the user is
226 # WIKI_ADMIN
227 page.readonly = int(req.args.has_key('readonly'))
228
229 # Give the manipulators a pass at post-processing the page
230 for manipulator in self.page_manipulators:
231 for field, message in manipulator.validate_wiki_page(req, page):
232 if field:
233 raise InvalidWikiPage("The Wiki page field %s is invalid: %s"
234 % (field, message))
235 else:
236 raise InvalidWikiPage("Invalid Wiki page: %s" % message)
237
238 page.save(get_reporter_id(req, 'author'), req.args.get('comment'),
239 req.remote_addr)
240 req.redirect(req.href.wiki(page.name))
241
242 def _render_confirm(self, req, db, page):
243 if page.readonly:
244 req.perm.assert_permission('WIKI_ADMIN')
245 else:
246 req.perm.assert_permission('WIKI_DELETE')
247
248 version = None
249 if req.args.has_key('delete_version'):
250 version = int(req.args.get('version', 0))
251 old_version = int(req.args.get('old_version', 0)) or version
252
253 self._set_title(req, page, 'delete')
254 req.hdf['wiki'] = {'mode': 'delete'}
255 if version is not None:
256 num_versions = 0
257 for v,t,author,comment,ipnr in page.get_history():
258 if v >= old_version:
259 num_versions += 1;
260 if num_versions > 1:
261 break
262 req.hdf['wiki'] = {'version': version, 'old_version': old_version,
263 'only_version': num_versions == 1}
264
265 def _render_diff(self, req, db, page):
266 req.perm.assert_permission('WIKI_VIEW')
267
268 if not page.exists:
269 raise TracError("Version %s of page %s does not exist" %
270 (req.args.get('version'), page.name))
271
272 add_stylesheet(req, 'common/css/diff.css')
273
274 self._set_title(req, page, 'diff')
275
276 # Ask web spiders to not index old versions
277 req.hdf['html.norobots'] = 1
278
279 old_version = req.args.get('old_version')
280 if old_version:
281 old_version = int(old_version)
282 if old_version == page.version:
283 old_version = None
284 elif old_version > page.version: # FIXME: what about reverse diffs?
285 old_version, page = page.version, \
286 WikiPage(self.env, page.name, old_version)
287 latest_page = WikiPage(self.env, page.name)
288 new_version = int(page.version)
289 info = {
290 'version': new_version,
291 'latest_version': latest_page.version,
292 'history_href': req.href.wiki(page.name, action='history')
293 }
294
295 num_changes = 0
296 old_page = None
297 prev_version = next_version = None
298 for version,t,author,comment,ipnr in latest_page.get_history():
299 if version == new_version:
300 if t:
301 info['time'] = format_datetime(t)
302 info['time_delta'] = pretty_timedelta(t)
303 info['author'] = author or 'anonymous'
304 info['comment'] = wiki_to_html(comment or '--',
305 self.env, req, db)
306 info['ipnr'] = ipnr or ''
307 else:
308 if version < new_version:
309 num_changes += 1
310 if not prev_version:
311 prev_version = version
312 if (old_version and version == old_version) or \
313 not old_version:
314 old_page = WikiPage(self.env, page.name, version)
315 info['num_changes'] = num_changes
316 info['old_version'] = version
317 break
318 else:
319 next_version = version
320 req.hdf['wiki'] = info
321
322 # -- prev/next links
323 if prev_version:
324 add_link(req, 'prev', req.href.wiki(page.name, action='diff',
325 version=prev_version),
326 'Version %d' % prev_version)
327 if next_version:
328 add_link(req, 'next', req.href.wiki(page.name, action='diff',
329 version=next_version),
330 'Version %d' % next_version)
331
332 # -- text diffs
333 diff_style, diff_options = get_diff_options(req)
334
335 oldtext = old_page and old_page.text.splitlines() or []
336 newtext = page.text.splitlines()
337 context = 3
338 for option in diff_options:
339 if option.startswith('-U'):
340 context = int(option[2:])
341 break
342 if context < 0:
343 context = None
344 changes = hdf_diff(oldtext, newtext, context=context,
345 ignore_blank_lines='-B' in diff_options,
346 ignore_case='-i' in diff_options,
347 ignore_space_changes='-b' in diff_options)
348 req.hdf['wiki.diff'] = changes
349
350 def _render_editor(self, req, db, page, preview=False):
351 req.perm.assert_permission('WIKI_MODIFY')
352
353 if req.args.has_key('text'):
354 page.text = req.args.get('text')
355 if preview:
356 page.readonly = req.args.has_key('readonly')
357
358 author = get_reporter_id(req, 'author')
359 comment = req.args.get('comment', '')
360 editrows = req.args.get('editrows')
361 if editrows:
362 pref = req.session.get('wiki_editrows', '20')
363 if editrows != pref:
364 req.session['wiki_editrows'] = editrows
365 else:
366 editrows = req.session.get('wiki_editrows', '20')
367
368 self._set_title(req, page, 'edit')
369 info = {
370 'page_source': page.text,
371 'version': page.version,
372 'author': author,
373 'comment': comment,
374 'readonly': page.readonly,
375 'edit_rows': editrows,
376 'scroll_bar_pos': req.args.get('scroll_bar_pos', '')
377 }
378 if page.exists:
379 info['history_href'] = req.href.wiki(page.name,
380 action='history')
381 info['last_change_href'] = req.href.wiki(page.name,
382 action='diff',
383 version=page.version)
384 if preview:
385 info['page_html'] = wiki_to_html(page.text, self.env, req, db)
386 info['comment_html'] = wiki_to_oneliner(comment, self.env, req, db)
387 info['readonly'] = int(req.args.has_key('readonly'))
388 req.hdf['wiki'] = info
389
390 def _render_history(self, req, db, page):
391 """Extract the complete history for a given page and stores it in the
392 HDF.
393
394 This information is used to present a changelog/history for a given
395 page.
396 """
397 req.perm.assert_permission('WIKI_VIEW')
398
399 if not page.exists:
400 raise TracError, "Page %s does not exist" % page.name
401
402 self._set_title(req, page, 'history')
403
404 history = []
405 for version, t, author, comment, ipnr in page.get_history():
406 history.append({
407 'url': req.href.wiki(page.name, version=version),
408 'diff_url': req.href.wiki(page.name, version=version,
409 action='diff'),
410 'version': version,
411 'time': format_datetime(t),
412 'time_delta': pretty_timedelta(t),
413 'author': author,
414 'comment': wiki_to_oneliner(comment or '', self.env, db),
415 'ipaddr': ipnr
416 })
417 req.hdf['wiki.history'] = history
418
419 def _render_view(self, req, db, page):
420 req.perm.assert_permission('WIKI_VIEW')
421
422 page_name = self._set_title(req, page, '')
423 if page.name == 'WikiStart':
424 req.hdf['title'] = ''
425
426 version = req.args.get('version')
427 if version:
428 # Ask web spiders to not index old versions
429 req.hdf['html.norobots'] = 1
430
431 # Add registered converters
432 for conversion in Mimeview(self.env).get_supported_conversions(
433 'text/x-trac-wiki'):
434 conversion_href = req.href.wiki(page.name, version=version,
435 format=conversion[0])
436 add_link(req, 'alternate', conversion_href, conversion[1],
437 conversion[3])
438
439 latest_page = WikiPage(self.env, page.name)
440 req.hdf['wiki'] = {'exists': page.exists,
441 'version': page.version,
442 'latest_version': latest_page.version,
443 'readonly': page.readonly}
444 if page.exists:
445 req.hdf['wiki'] = {
446 'page_html': wiki_to_html(page.text, self.env, req),
447 'history_href': req.href.wiki(page.name, action='history'),
448 'last_change_href': req.href.wiki(page.name, action='diff',
449 version=page.version)
450 }
451 if version:
452 req.hdf['wiki'] = {
453 'comment_html': wiki_to_oneliner(page.comment or '--',
454 self.env, db),
455 'author': page.author,
456 'age': pretty_timedelta(page.time)
457 }
458 else:
459 if not req.perm.has_permission('WIKI_CREATE'):
460 raise HTTPNotFound('Page %s not found', page.name)
461 req.hdf['wiki.page_html'] = html.P('Describe "%s" here' % page_name)
462
463 # Show attachments
464 req.hdf['wiki.attachments'] = attachments_to_hdf(self.env, req, db,
465 'wiki', page.name)
466 if req.perm.has_permission('WIKI_MODIFY'):
467 attach_href = req.href.attachment('wiki', page.name)
468 req.hdf['wiki.attach_href'] = attach_href
469
470 # ISearchSource methods
471
472 def get_search_filters(self, req):
473 if req.perm.has_permission('WIKI_VIEW'):
474 yield ('wiki', 'Wiki')
475
476 def get_search_results(self, req, terms, filters):
477 if not 'wiki' in filters:
478 return
479 db = self.env.get_db_cnx()
480 sql_query, args = search_to_sql(db, ['w1.name', 'w1.author', 'w1.text'], terms)
481 cursor = db.cursor()
482 cursor.execute("SELECT w1.name,w1.time,w1.author,w1.text "
483 "FROM wiki w1,"
484 "(SELECT name,max(version) AS ver "
485 "FROM wiki GROUP BY name) w2 "
486 "WHERE w1.version = w2.ver AND w1.name = w2.name "
487 "AND " + sql_query, args)
488
489 for name, date, author, text in cursor:
490 yield (req.href.wiki(name), '%s: %s' % (name, shorten_line(text)),
491 date, author, shorten_result(text, terms))
Copyright (C) 2012-2017 Edgewall Software