web_ui.py :  » Project-Management » Trac » Trac-0.11.7 » trac » wiki » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Project Management » Trac 
Trac » Trac 0.11.7 » trac » wiki » web_ui.py
# -*- coding: utf-8 -*-
#
# Copyright (C) 2003-2009 Edgewall Software
# Copyright (C) 2003-2005 Jonas Borgstrm <jonas@edgewall.com>
# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Jonas Borgstrm <jonas@edgewall.com>
#         Christopher Lenz <cmlenz@gmx.de>

from datetime import datetime
import pkg_resources
import re

from genshi.core import Markup
from genshi.builder import tag

from trac.attachment import AttachmentModule
from trac.config import IntOption
from trac.core import *
from trac.mimeview.api import Mimeview,IContentConverter,Context
from trac.perm import IPermissionRequestor
from trac.resource import *
from trac.search import ISearchSource,search_to_sql,shorten_result
from trac.timeline.api import ITimelineEventProvider
from trac.util import get_reporter_id
from trac.util.datefmt import to_timestamp,utc
from trac.util.text import shorten_line
from trac.util.translation import _
from trac.versioncontrol.diff import get_diff_options,diff_blocks
from trac.web.chrome import add_ctxtnav,add_link,add_notice,add_script,\
                            add_stylesheet, add_warning, prevnext_nav, \
                            INavigationContributor, ITemplateProvider
from trac.web import IRequestHandler
from trac.wiki.api import IWikiPageManipulator,WikiSystem
from trac.wiki.formatter import format_to
from trac.wiki.model import WikiPage
 
class InvalidWikiPage(TracError):
    """Exception raised when a Wiki page fails validation.
    
    :deprecated: Not used anymore since 0.11
    """
 

class WikiModule(Component):

    implements(IContentConverter, INavigationContributor, IPermissionRequestor,
               IRequestHandler, ITimelineEventProvider, ISearchSource,
               ITemplateProvider)

    page_manipulators = ExtensionPoint(IWikiPageManipulator)

    max_size = IntOption('wiki', 'max_size', 262144,
        """Maximum allowed wiki page size in bytes. (''since 0.11.2'')""")

    PAGE_TEMPLATES_PREFIX = 'PageTemplates/'
    DEFAULT_PAGE_TEMPLATE = 'DefaultPage'

    # IContentConverter methods
    def get_supported_conversions(self):
        yield ('txt', _('Plain Text'), 'txt', 'text/x-trac-wiki', 'text/plain',
               9)

    def convert_content(self, req, mimetype, content, key):
        # Tell the browser that the content should be downloaded and
        # not rendered. The x=y part is needed to keep Safari from being 
        # confused by the multiple content-disposition headers.
        req.send_header('Content-Disposition', 'attachment; x=y')

        return (content, 'text/plain;charset=utf-8')

    # INavigationContributor methods

    def get_active_navigation_item(self, req):
        return 'wiki'

    def get_navigation_items(self, req):
        if 'WIKI_VIEW' in req.perm('wiki'):
            yield ('mainnav', 'wiki',
                   tag.a(_('Wiki'), href=req.href.wiki(), accesskey=1))
            yield ('metanav', 'help',
                   tag.a(_('Help/Guide'), href=req.href.wiki('TracGuide'),
                         accesskey=6))

    # IPermissionRequestor methods

    def get_permission_actions(self):
        actions = ['WIKI_CREATE', 'WIKI_DELETE', 'WIKI_MODIFY', 'WIKI_VIEW']
        return actions + [('WIKI_ADMIN', actions)]

    # IRequestHandler methods

    def match_request(self, req):
        match = re.match(r'/wiki(?:/(.+))?$', req.path_info)
        if match:
            if match.group(1):
                req.args['page'] = match.group(1)
            return 1

    def process_request(self, req):
        action = req.args.get('action', 'view')
        pagename = req.args.get('page', 'WikiStart')
        version = req.args.get('version')
        old_version = req.args.get('old_version')

        if pagename.endswith('/'):
            req.redirect(req.href.wiki(pagename.strip('/')))

        page = WikiPage(self.env, pagename)
        versioned_page = WikiPage(self.env, pagename, version=version)

        req.perm(page.resource).require('WIKI_VIEW')
        req.perm(versioned_page.resource).require('WIKI_VIEW')

        if version and versioned_page.version == 0 and \
               page.version != 0:
            raise TracError(_('No version "%(num)s" for Wiki page "%(name)s"',
                              num=version, name=page.name))

        add_stylesheet(req, 'common/css/wiki.css')

        if req.method == 'POST':
            if action == 'edit':
                if 'cancel' in req.args:
                    req.redirect(req.href.wiki(page.name))
                
                has_collision = int(version) != page.version
                for a in ('preview', 'diff', 'merge'):
                    if a in req.args:
                        action = a
                        break
                valid = self._validate(req, versioned_page)
                if action == 'edit' and not has_collision and valid:
                    return self._do_save(req, versioned_page)
                else:
                    return self._render_editor(req, page, action, has_collision)
            elif action == 'delete':
                self._do_delete(req, versioned_page)
            elif action == 'diff':
                get_diff_options(req)
                req.redirect(req.href.wiki(versioned_page.name, action='diff',
                                           old_version=old_version))
        elif action == 'delete':
            return self._render_confirm(req, versioned_page)
        elif action == 'edit':
            return self._render_editor(req, versioned_page)
        elif action == 'diff':
            return self._render_diff(req, versioned_page)
        elif action == 'history':
            return self._render_history(req, versioned_page)
        else:
            format = req.args.get('format')
            if format:
                Mimeview(self.env).send_converted(req, 'text/x-trac-wiki',
                                                  versioned_page.text,
                                                  format, versioned_page.name)
            return self._render_view(req, versioned_page)

    # ITemplateProvider methods

    def get_htdocs_dirs(self):
        return []

    def get_templates_dirs(self):
        return [pkg_resources.resource_filename('trac.wiki', 'templates')]

    # Internal methods

    def _validate(self, req, page):
        valid = True
        
        # Validate page size
        if len(req.args.get('text', '')) > self.max_size:
            add_warning(req, _('The wiki page is too long (must be less '
                               'than %(num)s characters)',
                               num=self.max_size))
            valid = False

        # Give the manipulators a pass at post-processing the page
        for manipulator in self.page_manipulators:
            for field, message in manipulator.validate_wiki_page(req, page):
                valid = False
                if field:
                    add_warning(req, _("The Wiki page field '%(field)s' is "
                                       "invalid: %(message)s",
                                       field=field, message=message))
                else:
                    add_warning(req, _("Invalid Wiki page: %(message)s",
                                       message=message))
        return valid

    def _page_data(self, req, page, action=''):
        title = get_resource_summary(self.env, page.resource)
        if action:
            title += ' (%s)' % action
        return {'page': page, 'action': action, 'title': title}

    def _prepare_diff(self, req, page, old_text, new_text,
                      old_version, new_version):
        diff_style, diff_options, diff_data = get_diff_options(req)
        diff_context = 3
        for option in diff_options:
            if option.startswith('-U'):
                diff_context = int(option[2:])
                break
        if diff_context < 0:
            diff_context = None
        diffs = diff_blocks(old_text, new_text, context=diff_context,
                            ignore_blank_lines='-B' in diff_options,
                            ignore_case='-i' in diff_options,
                            ignore_space_changes='-b' in diff_options)
        def version_info(v, last=0):
            return {'path': get_resource_name(self.env, page.resource),
                    'rev': v or 'currently edited', 
                    'shortrev': v or last + 1,
                    'href': v and req.href.wiki(page.name, version=v) or None}
        changes = [{'diffs': diffs, 'props': [],
                    'new': version_info(new_version, old_version),
                    'old': version_info(old_version)}]

        add_stylesheet(req, 'common/css/diff.css')
        add_script(req, 'common/js/diff.js')
        return diff_data, changes

    def _do_delete(self, req, page):
        if page.readonly:
            req.perm(page.resource).require('WIKI_ADMIN')
        else:
            req.perm(page.resource).require('WIKI_DELETE')

        if 'cancel' in req.args:
            req.redirect(get_resource_url(self.env, page.resource, req.href))

        version = int(req.args.get('version', 0)) or None
        old_version = int(req.args.get('old_version', 0)) or version

        db = self.env.get_db_cnx()
        if version and old_version and version > old_version:
            # delete from `old_version` exclusive to `version` inclusive:
            for v in range(old_version, version):
                page.delete(v + 1, db)
        else:
            # only delete that `version`, or the whole page if `None`
            page.delete(version, db)
        db.commit()

        if not page.exists:
            add_notice(req, _('The page %(name)s has been deleted.',
                              name=page.name))
            req.redirect(req.href.wiki())
        else:
            if version and old_version and version > old_version + 1:
                add_notice(req, _('The versions %(from_)d to %(to)d of the '
                                  'page %(name)s have been deleted.',
                            from_=old_version + 1, to=version, name=page.name))
            else:
                add_notice(req, _('The version %(version)d of the page '
                                  '%(name)s has been deleted.',
                                  version=version, name=page.name))
            req.redirect(req.href.wiki(page.name))

    def _do_save(self, req, page):
        if page.readonly:
            req.perm(page.resource).require('WIKI_ADMIN')
        elif not page.exists:
            req.perm(page.resource).require('WIKI_CREATE')
        else:
            req.perm(page.resource).require('WIKI_MODIFY')

        page.text = req.args.get('text')
        if 'WIKI_ADMIN' in req.perm(page.resource):
            # Modify the read-only flag if it has been changed and the user is
            # WIKI_ADMIN
            page.readonly = int('readonly' in req.args)

        try:
            page.save(get_reporter_id(req, 'author'),
                            req.args.get('comment'),
                            req.remote_addr)
            add_notice(req, _('Your changes have been saved.'))
            req.redirect(get_resource_url(self.env, page.resource, req.href,
                                          version=None))
        except TracError:
            add_warning(req, _("Page not modified, showing latest version."))
            return self._render_view(req, page)

    def _render_confirm(self, req, page):
        if page.readonly:
            req.perm(page.resource).require('WIKI_ADMIN')
        else:
            req.perm(page.resource).require('WIKI_DELETE')

        version = None
        if 'delete_version' in req.args:
            version = int(req.args.get('version', 0))
        old_version = int(req.args.get('old_version') or 0) or version

        data = self._page_data(req, page, 'delete')
        data.update({'new_version': None, 'old_version': None,
                     'num_versions': 0})
        if version is not None:
            num_versions = 0
            for v,t,author,comment,ipnr in page.get_history():
                num_versions += 1;
                if num_versions > 1:
                    break
            data.update({'new_version': version, 'old_version': old_version,
                         'num_versions': num_versions})
        self._wiki_ctxtnav(req, page)
        return 'wiki_delete.html', data, None

    def _render_diff(self, req, page):
        if not page.exists:
            raise TracError(_('Version %(num)s of page "%(name)s" does not '
                              'exist',
                              num=req.args.get('version'), name=page.name))

        old_version = req.args.get('old_version')
        if old_version:
            old_version = int(old_version)
            if old_version == page.version:
                old_version = None
            elif old_version > page.version:
                # FIXME: what about reverse diffs?
                old_version = page.resource.version
                page = WikiPage(self.env, page.name, version=old_version)
                req.perm(page.resource).require('WIKI_VIEW')
        latest_page = WikiPage(self.env, page.name, version=None)
        req.perm(latest_page.resource).require('WIKI_VIEW')
        new_version = int(page.version)

        date = author = comment = ipnr = None
        num_changes = 0
        old_page = None
        prev_version = next_version = None
        for version, t, a, c, i in latest_page.get_history():
            if version == new_version:
                date = t
                author = a or 'anonymous'
                comment = c or '--'
                ipnr = i or ''
            else:
                if version < new_version:
                    num_changes += 1
                    if not prev_version:
                        prev_version = version
                    if (old_version and version == old_version) or \
                            not old_version:
                        old_version = version
                        old_page = WikiPage(self.env, page.name, old_version)
                        req.perm(old_page.resource).require('WIKI_VIEW')
                        break
                else:
                    next_version = version
        if not old_version:
            old_version = 0

        # -- text diffs
        old_text = old_page and old_page.text.splitlines() or []
        new_text = page.text.splitlines()
        diff_data, changes = self._prepare_diff(req, page, old_text, new_text,
                                                old_version, new_version)

        # -- prev/up/next links
        if prev_version:
            add_link(req, 'prev', req.href.wiki(page.name, action='diff',
                                                version=prev_version),
                     _('Version %(num)s', num=prev_version))
        add_link(req, 'up', req.href.wiki(page.name, action='history'),
                 _('Page history'))
        if next_version:
            add_link(req, 'next', req.href.wiki(page.name, action='diff',
                                                version=next_version),
                     _('Version %(num)s', num=next_version))

        data = self._page_data(req, page, 'diff')
        data.update({ 
            'change': {'date': date, 'author': author, 'ipnr': ipnr,
                       'comment': comment},
            'new_version': new_version, 'old_version': old_version,
            'latest_version': latest_page.version,
            'num_changes': num_changes,
            'longcol': 'Version', 'shortcol': 'v',
            'changes': changes,
            'diff': diff_data,
        })
        prevnext_nav(req, _('Change'), _('Wiki History'))
        return 'wiki_diff.html', data, None

    def _render_editor(self, req, page, action='edit', has_collision=False):
        if has_collision:
            if action == 'merge':
                page = WikiPage(self.env, page.name, version=None)
                req.perm(page.resource).require('WIKI_VIEW')
            else:
                action = 'collision'

        if page.readonly:
            req.perm(page.resource).require('WIKI_ADMIN')
        else:
            req.perm(page.resource).require('WIKI_MODIFY')
        original_text = page.text
        if 'text' in req.args:
            page.text = req.args.get('text')
        elif 'template' in req.args:
            template = self.PAGE_TEMPLATES_PREFIX + req.args.get('template')
            template_page = WikiPage(self.env, template)
            if template_page and template_page.exists and \
                   'WIKI_VIEW' in req.perm(template_page.resource):
                page.text = template_page.text
        if action == 'preview':
            page.readonly = 'readonly' in req.args

        author = get_reporter_id(req, 'author')
        comment = req.args.get('comment', '')
        editrows = req.args.get('editrows')
        
        if editrows:
            pref = req.session.get('wiki_editrows', '20')
            if editrows != pref:
                req.session['wiki_editrows'] = editrows
        else:
            editrows = req.session.get('wiki_editrows', '20')

        data = self._page_data(req, page, action)
        data.update({
            'author': author,
            'comment': comment,
            'edit_rows': editrows,
            'scroll_bar_pos': req.args.get('scroll_bar_pos', ''),
            'diff': None,
        })
        if action in ('diff', 'merge'):
            old_text = original_text and original_text.splitlines() or []
            new_text = page.text and page.text.splitlines() or []
            diff_data, changes = self._prepare_diff(
                req, page, old_text, new_text, page.version, '')
            data.update({'diff': diff_data, 'changes': changes,
                         'action': 'preview', 'merge': action == 'merge',
                         'longcol': 'Version', 'shortcol': 'v'})
        
        self._wiki_ctxtnav(req, page)
        return 'wiki_edit.html', data, None

    def _render_history(self, req, page):
        """Extract the complete history for a given page.

        This information is used to present a changelog/history for a given
        page.
        """
        if not page.exists:
            raise TracError(_("Page %(name)s does not exist", name=page.name))

        data = self._page_data(req, page, 'history')

        history = []
        for version, date, author, comment, ipnr in page.get_history():
            history.append({
                'version': version,
                'date': date,
                'author': author,
                'comment': comment,
                'ipnr': ipnr
            })
        data.update({'history': history, 'resource': page.resource})
        add_ctxtnav(req, 'Back to '+page.name, req.href.wiki(page.name))
        return 'history_view.html', data, None

    def _render_view(self, req, page):
        version = page.resource.version

        # Add registered converters
        if page.exists:
            for conversion in Mimeview(self.env).get_supported_conversions(
                                                 'text/x-trac-wiki'):
                conversion_href = req.href.wiki(page.name, version=version,
                                                format=conversion[0])
                # or...
                conversion_href = get_resource_url(self.env, page.resource,
                                                req.href, format=conversion[0])
                add_link(req, 'alternate', conversion_href, conversion[1],
                         conversion[3])

        data = self._page_data(req, page)
        if page.name == 'WikiStart':
            data['title'] = ''

        if not page.exists:
            if 'WIKI_CREATE' not in req.perm(page.resource):
                raise ResourceNotFound(_('Page %(name)s not found',
                                         name=page.name))

        latest_page = WikiPage(self.env, page.name, version=None)
        req.perm(latest_page.resource).require('WIKI_VIEW')

        prev_version = next_version = None
        if version:
            try:
                version = int(version)
                for hist in latest_page.get_history():
                    v = hist[0]
                    if v != version:
                        if v < version:
                            if not prev_version:
                                prev_version = v
                                break
                        else:
                            next_version = v
            except ValueError:
                version = None
            
        prefix = self.PAGE_TEMPLATES_PREFIX
        templates = [template[len(prefix):] for template in
                     WikiSystem(self.env).get_pages(prefix) if
                     'WIKI_VIEW' in req.perm('wiki', template)]

        # -- prev/up/next links
        if prev_version:
            add_link(req, 'prev',
                     req.href.wiki(page.name, version=prev_version),
                     _('Version %(num)s', num=prev_version))

        parent = None
        if version:
            add_link(req, 'up', req.href.wiki(page.name, version=None),
                     _('View latest version'))
        elif '/' in page.name:
            parent = page.name[:page.name.rindex('/')]
            add_link(req, 'up', req.href.wiki(parent, version=None),
                     _("View parent page"))
        
        if next_version:
            add_link(req, 'next',
                     req.href.wiki(page.name, version=next_version),
                     _('Version %(num)s', num=next_version))

        # Add ctxtnav entries
        if version:
            prevnext_nav(req, _('Version'), _('View Latest Version'))
            add_ctxtnav(req, _('Last Change'),
                        req.href.wiki(page.name, action='diff',
                                      version=page.version))
        else:
            if parent:
                add_ctxtnav(req, _('Up'), req.href.wiki(parent))
            self._wiki_ctxtnav(req, page)

        context = Context.from_request(req, page.resource)
        data.update({
            'context': context,
            'latest_version': latest_page.version,
            'attachments': AttachmentModule(self.env).attachment_data(context),
            'default_template': self.DEFAULT_PAGE_TEMPLATE,
            'templates': templates,
            'version': version
        })
        return 'wiki_view.html', data, None
    
    def _wiki_ctxtnav(self, req, page):
        """Add the normal wiki ctxtnav entries."""
        add_ctxtnav(req, _('Start Page'), req.href.wiki('WikiStart'))
        add_ctxtnav(req, _('Index'), req.href.wiki('TitleIndex'))
        if page.exists:
            add_ctxtnav(req, _('History'), req.href.wiki(page.name, 
                                                         action='history'))
            add_ctxtnav(req, _('Last Change'),
                        req.href.wiki(page.name, action='diff',
                                      version=page.version))

    # ITimelineEventProvider methods

    def get_timeline_filters(self, req):
        if 'WIKI_VIEW' in req.perm:
            yield ('wiki', _('Wiki changes'))

    def get_timeline_events(self, req, start, stop, filters):
        db = self.env.get_db_cnx()
        if 'wiki' in filters:
            wiki_realm = Resource('wiki')
            cursor = db.cursor()
            cursor.execute("SELECT time,name,comment,author,version "
                           "FROM wiki WHERE time>=%s AND time<=%s",
                           (to_timestamp(start), to_timestamp(stop)))
            for ts,name,comment,author,version in cursor:
                wiki_page = wiki_realm(id=name, version=version)
                if 'WIKI_VIEW' not in req.perm(wiki_page):
                    continue
                yield ('wiki', datetime.fromtimestamp(ts, utc), author,
                       (wiki_page, comment))

            # Attachments
            for event in AttachmentModule(self.env).get_timeline_events(
                req, wiki_realm, start, stop):
                yield event

    def render_timeline_event(self, context, field, event):
        wiki_page, comment = event[3]
        if field == 'url':
            return context.href.wiki(wiki_page.id, version=wiki_page.version)
        elif field == 'title':
            return tag(tag.em(get_resource_name(self.env, wiki_page)),
                       wiki_page.version > 1 and ' edited' or ' created')
        elif field == 'description':
            markup = format_to(self.env, None, context(resource=wiki_page),
                               comment)
            if wiki_page.version > 1:
                diff_href = context.href.wiki(
                    wiki_page.id, version=wiki_page.version, action='diff')
                markup = tag(markup, ' ', tag.a('(diff)', href=diff_href))
            return markup

    # ISearchSource methods

    def get_search_filters(self, req):
        if 'WIKI_VIEW' in req.perm:
            yield ('wiki', _('Wiki'))

    def get_search_results(self, req, terms, filters):
        if not 'wiki' in filters:
            return
        db = self.env.get_db_cnx()
        sql_query, args = search_to_sql(db, ['w1.name', 'w1.author', 'w1.text'],
                                        terms)
        cursor = db.cursor()
        cursor.execute("SELECT w1.name,w1.time,w1.author,w1.text "
                       "FROM wiki w1,"
                       "(SELECT name,max(version) AS ver "
                       "FROM wiki GROUP BY name) w2 "
                       "WHERE w1.version = w2.ver AND w1.name = w2.name "
                       "AND " + sql_query, args)

        wiki_realm = Resource('wiki')
        for name, ts, author, text in cursor:
            page = wiki_realm(id=name)
            if 'WIKI_VIEW' in req.perm(page):
                yield (get_resource_url(self.env, page, req.href),
                       '%s: %s' % (name, shorten_line(text)),
                       datetime.fromtimestamp(ts, utc), author,
                       shorten_result(text, terms))
        
        # Attachments
        for result in AttachmentModule(self.env).get_search_results(
            req, wiki_realm, terms):
            yield result
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.