Mercurial > genshi > mirror
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)) |