~ubuntu-branches/ubuntu/precise/trac/precise

« back to all changes in this revision

Viewing changes to trac/web/chrome.py

  • Committer: Bazaar Package Importer
  • Author(s): W. Martin Borgert
  • Date: 2009-09-15 21:43:38 UTC
  • mfrom: (1.1.15 upstream)
  • Revision ID: james.westby@ubuntu.com-20090915214338-q3ecy6qxwxfzf9y8
Tags: 0.11.5-2
* Set exec bit for *_frontends (Closes: #510441), thanks to Torsten
  Landschoff for the patch.
* Move python-psycopg2 and python-mysql from Suggests to Depends as
  alternative to python-psqlite2 (Closes: #513117).
* Use debhelper 7 (Closes: #497862).
* Don't compress *-hook files and don't install MS-Windows *.cmd
  files (Closes: #526142), thanks to Jan Dittberner for the patch.
* Add README.source to point to dpatch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- coding: utf-8 -*-
2
2
#
3
 
# Copyright (C) 2005-2008 Edgewall Software
 
3
# Copyright (C) 2005-2009 Edgewall Software
4
4
# Copyright (C) 2005-2006 Christopher Lenz <cmlenz@gmx.de>
5
5
# All rights reserved.
6
6
#
15
15
# Author: Christopher Lenz <cmlenz@gmx.de>
16
16
 
17
17
import datetime
18
 
import os
 
18
import itertools
 
19
import os.path
19
20
import pkg_resources
20
21
import pprint
21
22
import re
38
39
from trac.mimeview import get_mimetype, Context
39
40
from trac.resource import *
40
41
from trac.util import compat, get_reporter_id, presentation, get_pkginfo, \
41
 
                      get_module_path, translation, arity
 
42
                      get_module_path, translation
42
43
from trac.util.compat import partial, set
43
44
from trac.util.html import plaintext
44
45
from trac.util.text import pretty_size, obfuscate_email_address, \
45
 
                           shorten_line, unicode_quote_plus, to_unicode
 
46
                           shorten_line, unicode_quote_plus, to_unicode, \
 
47
                           javascript_quote
46
48
from trac.util.datefmt import pretty_timedelta, format_datetime, format_date, \
47
49
                              format_time, http_date, utc
48
50
from trac.util.translation import _
113
115
def add_warning(req, msg, *args):
114
116
    """Add a non-fatal warning to the request object.
115
117
    When rendering pages, any warnings will be rendered to the user."""
116
 
    req.chrome['warnings'].append(msg % args)
 
118
    if args:
 
119
        msg %= args
 
120
    req.chrome['warnings'].append(msg)
117
121
 
118
122
def add_notice(req, msg, *args):
119
123
    """Add an informational notice to the request object.
120
124
    When rendering pages, any notice will be rendered to the user."""
121
 
    req.chrome['notices'].append(msg % args)
 
125
    if args:
 
126
        msg %= args
 
127
    req.chrome['notices'].append(msg)
122
128
 
123
129
def add_ctxtnav(req, elm_or_label, href=None, title=None):
124
130
    """Add an entry to the current page's ctxtnav bar.
180
186
                     Markup(' &rarr;'), class_='missing'))
181
187
 
182
188
 
 
189
def _save_messages(req, url, permanent):
 
190
    """Save warnings and notices in case of redirect, so that they can
 
191
    be displayed after the redirect."""
 
192
    for type_ in ['warnings', 'notices']:
 
193
        for (i, message) in enumerate(req.chrome[type_]):
 
194
            req.session['chrome.%s.%d' % (type_, i)] = message
 
195
 
 
196
 
183
197
class INavigationContributor(Interface):
184
198
    """Extension point interface for components that contribute items to the
185
199
    navigation.
222
236
        """
223
237
 
224
238
 
 
239
# Mappings for removal of control characters
 
240
_translate_nop = "".join([chr(i) for i in range(256)])
 
241
_invalid_control_chars = "".join([chr(i) for i in range(32)
 
242
                                  if i not in [0x09, 0x0a, 0x0d]])
 
243
 
 
244
    
225
245
class Chrome(Component):
226
246
    """Responsible for assembling the web site chrome, i.e. everything that
227
247
    is not actual page content.
278
298
        """Show email addresses instead of usernames. If false, we obfuscate
279
299
        email addresses (''since 0.11'').""")
280
300
 
 
301
    show_ip_addresses = BoolOption('trac', 'show_ip_addresses', 'false',
 
302
        """Show IP addresses for resource edits (e.g. wiki).
 
303
        (''since 0.11.3'').""")
 
304
 
281
305
    templates = None
282
306
 
283
307
    # A dictionary of default context data for templates
297
321
        'http_date': http_date,
298
322
        'istext': presentation.istext,
299
323
        'itemgetter': compat.itemgetter,
 
324
        'javascript_quote': javascript_quote,
300
325
        'ngettext': translation.ngettext,
301
326
        'paginate': presentation.paginate,
302
327
        'partial': partial,
329
354
            if not os.path.exists(templates_dir):
330
355
                os.mkdir(templates_dir)
331
356
 
332
 
            fileobj = open(os.path.join(templates_dir, 'site.html'), 'w')
333
 
            try:
334
 
                fileobj.write("""<html xmlns="http://www.w3.org/1999/xhtml"
 
357
            if not self.shared_templates_dir or not os.path.exists(
 
358
                        os.path.join(self.shared_templates_dir, "site.html")):
 
359
                fileobj = open(os.path.join(templates_dir, 'site.html'), 'w')
 
360
                try:
 
361
                    fileobj.write("""\
 
362
<html xmlns="http://www.w3.org/1999/xhtml"
335
363
      xmlns:py="http://genshi.edgewall.org/" py:strip="">
336
364
  <!--! Custom match templates go here -->
337
365
</html>""")
338
 
            finally:
339
 
                fileobj.close()
 
366
                finally:
 
367
                    fileobj.close()
340
368
 
341
369
    def environment_needs_upgrade(self, db):
342
370
        return False
417
445
 
418
446
        chrome = {'links': {}, 'scripts': [], 'ctxtnav': [], 'warnings': [],
419
447
                  'notices': []}
 
448
        req.add_redirect_listener(_save_messages)
420
449
 
421
450
        # This is ugly... we can't pass the real Request object to the
422
451
        # add_xxx methods, because it doesn't yet have the chrome attribute
468
497
                    href = category_section.get(name + '.href')
469
498
                    if href:
470
499
                        if href.startswith('/'):
471
 
                            href = req.href(href)
 
500
                            href = req.href() + href
472
501
                        if label:
473
502
                            item = tag.a(label) # create new label
474
503
                        elif not item:
510
539
 
511
540
        return chrome
512
541
 
513
 
 
514
542
    def get_icon_data(self, req):
515
543
        icon = {}
516
544
        icon_src = icon_abs_src = self.env.project_icon
579
607
            'homepage': 'http://trac.edgewall.org/', # FIXME: use setup data
580
608
            'systeminfo': self.env.systeminfo,
581
609
        }
 
610
        
 
611
        href = req and req.href
 
612
        abs_href = req and req.abs_href or self.env.abs_href
 
613
        admin_href = None
 
614
        if self.env.project_admin_trac_url == '.':
 
615
            admin_href = href
 
616
        elif self.env.project_admin_trac_url:
 
617
            admin_href = Href(self.env.project_admin_trac_url)
 
618
            
582
619
        d['project'] = {
583
620
            'name': self.env.project_name,
584
621
            'descr': self.env.project_description,
585
622
            'url': self.env.project_url,
586
623
            'admin': self.env.project_admin,
 
624
            'admin_href': admin_href,
 
625
            'admin_trac_url': self.env.project_admin_trac_url,
587
626
        }
588
627
        d['chrome'] = {
589
628
            'footer': Markup(self.env.project_footer)
606
645
            return tag.span(pretty_timedelta(date),
607
646
                            title=format_datetime(date))
608
647
 
609
 
        href = req and req.href
610
 
        abs_href = req and req.abs_href or self.env.abs_href
611
 
        
612
648
        def get_rel_url(resource, **kwargs):
613
649
            return get_resource_url(self.env, resource, href, **kwargs)
614
650
 
628
664
            'perm': req and req.perm,
629
665
            'authname': req and req.authname or '<trac>',
630
666
            'show_email_addresses': show_email_addresses,
 
667
            'show_ip_addresses': self.show_ip_addresses,
631
668
            'format_author': partial(self.format_author, req),
632
669
            'format_emails': self.format_emails,
633
670
 
680
717
        method = {'text/html': 'xhtml',
681
718
                  'text/plain': 'text'}.get(content_type, 'xml')
682
719
 
 
720
        if method == "xhtml":
 
721
            # Retrieve post-redirect messages saved in session
 
722
            for type_ in ['warnings', 'notices']:
 
723
                try:
 
724
                    for i in itertools.count():
 
725
                        req.chrome[type_].append(
 
726
                            req.session.pop('chrome.%s.%d' % (type_, i)))
 
727
                except KeyError:
 
728
                    pass
 
729
 
683
730
        template = self.load_template(filename, method=method)
684
731
        data = self.populate_data(req, data)
685
732
 
693
740
            return stream
694
741
 
695
742
        if method == 'text':
696
 
            return stream.render('text')
 
743
            buffer = cStringIO()
 
744
            stream.render('text', out=buffer)
 
745
            return buffer.getvalue()
697
746
 
698
747
        doctype = {'text/html': DocType.XHTML_STRICT}.get(content_type)
699
748
        if doctype:
700
749
            if req.form_token:
701
750
                stream |= self._add_form_token(req.form_token)
702
 
            if not req.session or not int(req.session.get('accesskeys', 0)):
 
751
            if not int(req.session.get('accesskeys', 0)):
703
752
                stream |= self._strip_accesskeys
704
753
 
705
754
        links = req.chrome.get('links')
712
761
        })
713
762
 
714
763
        try:
715
 
            return stream.render(method, doctype=doctype)
716
 
        except:
 
764
            buffer = cStringIO()
 
765
            stream.render(method, doctype=doctype, out=buffer)
 
766
            return buffer.getvalue().translate(_translate_nop,
 
767
                                               _invalid_control_chars)
 
768
        except Exception, e:
717
769
            # restore what may be needed by the error template
718
770
            req.chrome['links'] = links
719
771
            req.chrome['scripts'] = scripts
 
772
            # give some hints when hitting a Genshi unicode error
 
773
            if isinstance(e, UnicodeError):
 
774
                pos = self._stream_location(stream)
 
775
                if pos:
 
776
                    location = "'%s', line %s, char %s" % pos
 
777
                else:
 
778
                    location = _("(unknown template location)")
 
779
                raise TracError(_("Genshi %(error)s error while rendering "
 
780
                                  "template %(location)s", 
 
781
                                  error=e.__class__.__name__, 
 
782
                                  location=location))
720
783
            raise
721
784
 
722
785
    # E-mail formatting utilities
778
841
            return stream
779
842
        return inner
780
843
 
 
844
    def _stream_location(self, stream):
 
845
        for kind, data, pos in stream:
 
846
            return pos
 
847