You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bloodhound.apache.org by rj...@apache.org on 2014/02/13 06:08:08 UTC

svn commit: r1567849 [2/17] - in /bloodhound/vendor/trac: 1.0-stable/ current/ current/contrib/ current/contrib/cgi-bin/ current/contrib/workflow/ current/doc/ current/doc/utils/ current/sample-plugins/ current/sample-plugins/permissions/ current/sampl...

Modified: bloodhound/vendor/trac/current/sample-plugins/revision_links.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/sample-plugins/revision_links.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/sample-plugins/revision_links.py (original)
+++ bloodhound/vendor/trac/current/sample-plugins/revision_links.py Thu Feb 13 05:08:02 2014
@@ -1,3 +1,17 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2013 Edgewall Software
+# Copyright (C) 2007 Christian Boos <cb...@edgewall.org>
+# 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.com/license.html.
+#
+# 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/.
+
 """Sample Wiki syntax extension plugin."""
 
 from genshi.builder import tag
@@ -8,8 +22,8 @@ from trac.versioncontrol.api import NoSu
 from trac.versioncontrol.web_ui import ChangesetModule
 from trac.wiki.api import IWikiSyntaxProvider
 
-revision = "$Rev: 11490 $"
-url = "$URL: http://svn.edgewall.org/repos/trac/tags/trac-1.0.1/sample-plugins/revision_links.py $"
+revision = "$Rev: 12500 $"
+url = "$URL: https://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/revision_links.py $"
 
 class RevisionLinks(Component):
     """Adds a few more ways to refer to changesets."""
@@ -53,4 +67,3 @@ class RevisionLinks(Component):
             pass
         return tag.a(label, class_="missing changeset", rel="nofollow",
                      href=formatter.href.changeset(rev))
-

Modified: bloodhound/vendor/trac/current/sample-plugins/workflow/CodeReview.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/sample-plugins/workflow/CodeReview.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/sample-plugins/workflow/CodeReview.py (original)
+++ bloodhound/vendor/trac/current/sample-plugins/workflow/CodeReview.py Thu Feb 13 05:08:02 2014
@@ -1,3 +1,17 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2013 Edgewall Software
+# Copyright (C) 2007 Eli Carter <re...@gmail.com>
+# 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.com/license.html.
+#
+# 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/.
+
 from genshi.builder import tag
 
 from trac.core import implements,Component
@@ -6,8 +20,8 @@ from trac.ticket.default_workflow import
 from trac.perm import IPermissionRequestor
 from trac.config import Option, ListOption
 
-revision = "$Rev: 11490 $"
-url = "$URL: http://svn.edgewall.org/repos/trac/tags/trac-1.0.1/sample-plugins/workflow/CodeReview.py $"
+revision = "$Rev: 12164 $"
+url = "$URL: https://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/workflow/CodeReview.py $"
 
 class CodeReviewActionController(Component):
     """Support for simple code reviews.

Modified: bloodhound/vendor/trac/current/sample-plugins/workflow/DeleteTicket.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/sample-plugins/workflow/DeleteTicket.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/sample-plugins/workflow/DeleteTicket.py (original)
+++ bloodhound/vendor/trac/current/sample-plugins/workflow/DeleteTicket.py Thu Feb 13 05:08:02 2014
@@ -1,11 +1,25 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2013 Edgewall Software
+# Copyright (C) 2007 Eli Carter <re...@gmail.com>
+# 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.com/license.html.
+#
+# 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/.
+
 from genshi.builder import tag
 
 from trac.core import implements,Component
 from trac.ticket.api import ITicketActionController
 from trac.perm import IPermissionRequestor
 
-revision = "$Rev: 11490 $"
-url = "$URL: http://svn.edgewall.org/repos/trac/tags/trac-1.0.1/sample-plugins/workflow/DeleteTicket.py $"
+revision = "$Rev: 12164 $"
+url = "$URL: https://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/workflow/DeleteTicket.py $"
 
 class DeleteTicketActionController(Component):
     """Provides the admin with a way to delete a ticket.

Modified: bloodhound/vendor/trac/current/sample-plugins/workflow/MilestoneOperation.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/sample-plugins/workflow/MilestoneOperation.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/sample-plugins/workflow/MilestoneOperation.py (original)
+++ bloodhound/vendor/trac/current/sample-plugins/workflow/MilestoneOperation.py Thu Feb 13 05:08:02 2014
@@ -1,14 +1,16 @@
 # -*- coding: utf-8 -*-
 #
+# Copyright (C) 2002-2013 Edgewall Software
 # Copyright (C) 2012 Franz Mayer <fr...@gefasoft.de>
+# All rights reserved.
 #
-# "THE BEER-WARE LICENSE" (Revision 42):
-# <fr...@gefasoft.de> wrote this file.  As long as you retain this
-# notice you can do whatever you want with this stuff. If we meet some day,
-# and you think this stuff is worth it, you can buy me a beer in return.
-# Franz Mayer
+# 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.com/license.html.
 #
-# Author: Franz Mayer <fr...@gefasoft.de>
+# 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/.
 
 from genshi.builder import tag
 

Modified: bloodhound/vendor/trac/current/sample-plugins/workflow/StatusFixer.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/sample-plugins/workflow/StatusFixer.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/sample-plugins/workflow/StatusFixer.py (original)
+++ bloodhound/vendor/trac/current/sample-plugins/workflow/StatusFixer.py Thu Feb 13 05:08:02 2014
@@ -1,11 +1,25 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2013 Edgewall Software
+# Copyright (C) 2007 Eli Carter <re...@gmail.com>
+# 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.com/license.html.
+#
+# 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/.
+
 from genshi.builder import tag
 
 from trac.core import Component, implements
 from trac.ticket.api import ITicketActionController, TicketSystem
 from trac.perm import IPermissionRequestor
 
-revision = "$Rev: 11075 $"
-url = "$URL: http://svn.edgewall.org/repos/trac/tags/trac-1.0.1/sample-plugins/workflow/StatusFixer.py $"
+revision = "$Rev: 12164 $"
+url = "$URL: https://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/workflow/StatusFixer.py $"
 
 class StatusFixerActionController(Component):
     """Provides the admin with a way to correct a ticket's status.

Modified: bloodhound/vendor/trac/current/sample-plugins/workflow/VoteOperation.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/sample-plugins/workflow/VoteOperation.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/sample-plugins/workflow/VoteOperation.py (original)
+++ bloodhound/vendor/trac/current/sample-plugins/workflow/VoteOperation.py Thu Feb 13 05:08:02 2014
@@ -1,3 +1,17 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2013 Edgewall Software
+# Copyright (C) 2007 Eli Carter <re...@gmail.com>
+# 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.com/license.html.
+#
+# 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/.
+
 from genshi.builder import tag
 
 from trac.core import implements,Component
@@ -6,8 +20,8 @@ from trac.ticket.default_workflow import
 from trac.ticket.model import Priority, Ticket
 #from trac.perm import IPermissionRequestor # (TODO)
 
-revision = "$Rev: 11490 $"
-url = "$URL: http://svn.edgewall.org/repos/trac/tags/trac-1.0.1/sample-plugins/workflow/VoteOperation.py $"
+revision = "$Rev: 12164 $"
+url = "$URL: https://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/workflow/VoteOperation.py $"
 
 class VoteOperation(Component):
     """Provides a simplistic vote feature.

Modified: bloodhound/vendor/trac/current/setup.cfg
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/setup.cfg?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/setup.cfg (original)
+++ bloodhound/vendor/trac/current/setup.cfg Thu Feb 13 05:08:02 2014
@@ -1,6 +1,6 @@
 [egg_info]
-#tag_build = dev
-#tag_svn_revision = true
+tag_build = dev
+tag_svn_revision = true
 
 [bdist_wininst]
 bitmap = setup_wininst.bmp

Modified: bloodhound/vendor/trac/current/setup.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/setup.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/setup.py (original)
+++ bloodhound/vendor/trac/current/setup.py Thu Feb 13 05:08:02 2014
@@ -18,10 +18,10 @@ from setuptools import setup, find_packa
 
 min_python = (2, 5)
 if sys.version_info < min_python:
-    print "Trac requires Python %d.%d or later" % min_python
+    print("Trac requires Python %d.%d or later" % min_python)
     sys.exit(1)
 if sys.version_info >= (3,):
-    print "Trac doesn't support Python 3 (yet)"
+    print("Trac doesn't support Python 3 (yet)")
     sys.exit(1)
 
 extra = {}
@@ -49,13 +49,13 @@ except ImportError:
 try:
     import genshi
 except ImportError:
-    print "Genshi is needed by Trac setup, pre-installing"
+    print("Genshi is needed by Trac setup, pre-installing")
     # give some context to the warnings we might get when installing Genshi
 
 
 setup(
     name = 'Trac',
-    version = '1.0.1',
+    version = '1.0.2',
     description = 'Integrated SCM, wiki, issue tracker and project environment',
     long_description = """
 Trac is a minimalistic web-based software project management and bug/issue
@@ -104,6 +104,7 @@ facilities.
     ],
     extras_require = {
         'Babel': ['Babel>=0.9.5'],
+        'ConfigObj': ['ConfigObj'],
         'Pygments': ['Pygments>=0.6'],
         'reST': ['docutils>=0.3'],
         'SilverCity': ['SilverCity>=0.9.4'],
@@ -149,7 +150,7 @@ facilities.
         tracopt.mimeview.enscript = tracopt.mimeview.enscript
         tracopt.mimeview.php = tracopt.mimeview.php
         tracopt.mimeview.silvercity = tracopt.mimeview.silvercity[SilverCity]
-        tracopt.perm.authz_policy = tracopt.perm.authz_policy
+        tracopt.perm.authz_policy = tracopt.perm.authz_policy[ConfigObj]
         tracopt.perm.config_perm_provider = tracopt.perm.config_perm_provider
         tracopt.ticket.clone = tracopt.ticket.clone
         tracopt.ticket.commit_updater = tracopt.ticket.commit_updater

Modified: bloodhound/vendor/trac/current/trac/__init__.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/__init__.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/__init__.py (original)
+++ bloodhound/vendor/trac/current/trac/__init__.py Thu Feb 13 05:08:02 2014
@@ -16,4 +16,4 @@ from pkg_resources import DistributionNo
 try:
     __version__ = get_distribution('Trac').version
 except DistributionNotFound:
-    __version__ = '1.0.1'
+    __version__ = '1.0.2'

Modified: bloodhound/vendor/trac/current/trac/admin/api.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/api.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/api.py (original)
+++ bloodhound/vendor/trac/current/trac/admin/api.py Thu Feb 13 05:08:02 2014
@@ -11,15 +11,18 @@
 # individuals. For the exact contribution history, see the revision
 # history and logs, available at http://trac.edgewall.org/log/.
 
+import os
 import os.path
 import sys
 import traceback
 
 from trac.core import *
 from trac.util.text import levenshtein_distance
-from trac.util.translation import _
+from trac.util.translation import _, get_negotiated_locale, has_babel
 
 
+LANG = os.environ.get('LANG')
+
 console_date_format = '%Y-%m-%d'
 console_datetime_format = '%Y-%m-%d %H:%M:%S'
 console_date_format_hint = 'YYYY-MM-DD'
@@ -192,3 +195,21 @@ def get_dir_list(path, dirs_only=False):
         except OSError:
             pass
     return result
+
+
+def get_console_locale(env=None, lang=LANG):
+    """Return negotiated locale for console by LANG environment and
+    [trac] default_language."""
+    if has_babel:
+        from babel.core import Locale, UnknownLocaleError, parse_locale
+        try:
+            lang = '_'.join(filter(None, parse_locale(lang)))
+        except:
+            lang = None
+        default = env.config.get('trac', 'default_language', '') \
+                  if env else None
+        try:
+            return get_negotiated_locale([lang, default]) or Locale.default()
+        except UnknownLocaleError:
+            pass
+    return None

Modified: bloodhound/vendor/trac/current/trac/admin/console.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/console.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/console.py (original)
+++ bloodhound/vendor/trac/current/trac/admin/console.py Thu Feb 13 05:08:02 2014
@@ -15,7 +15,6 @@
 from __future__ import with_statement
 
 import cmd
-import locale
 import os.path
 import pkg_resources
 from shlex import shlex
@@ -24,7 +23,8 @@ import sys
 import traceback
 
 from trac import __version__ as VERSION
-from trac.admin import AdminCommandError, AdminCommandManager
+from trac.admin.api import AdminCommandError, AdminCommandManager, \
+                           get_console_locale
 from trac.core import TracError
 from trac.env import Environment
 from trac.ticket.model import *
@@ -33,15 +33,15 @@ from trac.util.html import html
 from trac.util.text import console_print, exception_to_unicode, printout, \
                            printerr, raw_input, to_unicode, \
                            getpreferredencoding
-from trac.util.translation import _, ngettext, get_negotiated_locale, \
-                                  has_babel, cleandoc_
+from trac.util.translation import _, ngettext, has_babel, cleandoc_
 from trac.versioncontrol.api import RepositoryManager
 from trac.wiki.admin import WikiAdmin
 from trac.wiki.macros import WikiMacroBase
 
+
 TRAC_VERSION = pkg_resources.get_distribution('Trac').version
 rl_completion_suppress_append = None
-LANG = os.environ.get('LANG')
+
 
 def find_readline_lib():
     """Return the name (and possibly the full path) of the readline library
@@ -67,6 +67,7 @@ class TracAdmin(cmd.Cmd):
     envname = None
     __env = None
     needs_upgrade = None
+    cmd_mgr = None
 
     def __init__(self, envdir=None):
         cmd.Cmd.__init__(self)
@@ -146,6 +147,7 @@ Type:  '?' or 'help' for help on command
         self.prompt = "Trac [%s]> " % self.envname
         if env is not None:
             self.__env = env
+            self.cmd_mgr = AdminCommandManager(env)
 
     def env_check(self):
         if not self.__env:
@@ -168,12 +170,13 @@ Type:  '?' or 'help' for help on command
 
     def _init_env(self):
         self.__env = env = Environment(self.envname)
+        negotiated = None
         # fixup language according to env settings
         if has_babel:
-            default = env.config.get('trac', 'default_language', '')
-            negotiated = get_negotiated_locale([LANG, default])
+            negotiated = get_console_locale(env)
             if negotiated:
                 translation.activate(negotiated)
+        self.cmd_mgr = AdminCommandManager(env)
 
     ##
     ## Utility methods
@@ -240,9 +243,8 @@ Type:  '?' or 'help' for help on command
         if line and line[-1] == ' ':    # Space starts new argument
             args.append('')
         if self.env_check():
-            cmd_mgr = AdminCommandManager(self.env)
             try:
-                comp = cmd_mgr.complete_command(args, cmd_only)
+                comp = self.cmd_mgr.complete_command(args, cmd_only)
             except Exception, e:
                 printerr()
                 printerr(_('Completion error: %(err)s',
@@ -281,8 +283,7 @@ Type:  '?' or 'help' for help on command
             raise TracError(_('The Trac Environment needs to be upgraded.\n\n'
                               'Run "trac-admin %(path)s upgrade"',
                               path=self.envname))
-        cmd_mgr = AdminCommandManager(self.env)
-        return cmd_mgr.execute_command(*args)
+        return self.cmd_mgr.execute_command(*args)
 
     ##
     ## Available Commands
@@ -306,15 +307,14 @@ Type:  '?' or 'help' for help on command
         if arg[0]:
             doc = getattr(self, "_help_" + arg[0], None)
             if doc is None and self.env_check():
-                cmd_mgr = AdminCommandManager(self.env)
-                doc = cmd_mgr.get_command_help(arg)
+                doc = self.cmd_mgr.get_command_help(arg)
             if doc:
                 self.print_doc(doc)
             else:
                 printerr(_("No documentation found for '%(cmd)s'."
                            " Use 'help' to see the list of commands.",
                            cmd=' '.join(arg)))
-                cmds = cmd_mgr.get_similar_commands(arg[0])
+                cmds = self.cmd_mgr.get_similar_commands(arg[0])
                 if cmds:
                     printout('')
                     printout(ngettext("Did you mean this?",
@@ -557,14 +557,8 @@ def run(args=None):
     """Main entry point."""
     if args is None:
         args = sys.argv[1:]
-    locale = None
     if has_babel:
-        import babel
-        try:
-            locale = get_negotiated_locale([LANG]) or babel.Locale.default()
-        except babel.UnknownLocaleError:
-            pass
-        translation.activate(locale)
+        translation.activate(get_console_locale())
     admin = TracAdmin()
     if len(args) > 0:
         if args[0] in ('-h', '--help', 'help'):

Modified: bloodhound/vendor/trac/current/trac/admin/templates/admin_basics.html
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/templates/admin_basics.html?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/templates/admin_basics.html (original)
+++ bloodhound/vendor/trac/current/trac/admin/templates/admin_basics.html Thu Feb 13 05:08:02 2014
@@ -39,15 +39,25 @@ ${project.descr}</textarea>
                       selected="${tzname == default_timezone or None}">${tzname}</option>
             </select>
           </label>
+          <span py:if="not has_pytz" class="hint">
+            Install pytz for a complete list of timezones.
+          </span>
         </div>
         <div class="field">
           <label>Default language:<br />
-            <select name="default_language">
+            <select name="default_language" disabled="${not languages or None}"
+                    title="${_('Translations are currently unavailable') if not languages else None}">
               <option value="">Browser's language</option>
               <option py:for="locale, language in languages" value="${locale}"
                       selected="${locale == default_language or None}">${language}</option>
             </select>
           </label>
+          <span py:if="not has_babel" class="hint">
+            Install Babel for extended language support.
+          </span>
+          <span py:if="has_babel and not languages" class="hint">
+            Message catalogs have not been compiled.
+          </span>
         </div>
         <div class="field">
           <label>Default date format:<br />
@@ -57,6 +67,9 @@ ${project.descr}</textarea>
                       selected="${default_date_format == 'iso8601' or None}">ISO 8601 format</option>
             </select>
           </label>
+          <span py:if="not has_babel" class="hint">
+            Install Babel for localized date formats.
+          </span>
         </div>
       </fieldset>
       <div class="buttons">

Modified: bloodhound/vendor/trac/current/trac/admin/templates/admin_perms.html
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/templates/admin_perms.html?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/templates/admin_perms.html (original)
+++ bloodhound/vendor/trac/current/trac/admin/templates/admin_perms.html Thu Feb 13 05:08:02 2014
@@ -13,7 +13,7 @@
   <body>
     <h2>Manage Permissions and Groups</h2>
 
-    <py:if test="'PERMISSION_GRANT' in perm">
+    <py:if test="'PERMISSION_GRANT' in perm('admin', 'general/perm')">
       <form id="addperm" class="addnew" method="post" action="">
         <fieldset>
           <legend>Grant Permission:</legend>
@@ -22,13 +22,14 @@
           </div>
           <div class="field">
             <label>Action:
-              <select id="action" name="action">
-                <option py:for="action in sorted(actions)">$action</option>
+              <select id="action" name="action"
+                      py:with="allowed_actions = [a for a in actions if a in perm]">
+                <option py:for="action in sorted(allowed_actions)">$action</option>
               </select>
             </label>
           </div>
           <div class="buttons">
-            <input type="submit" name="add" value="${_('Add')}" />
+            <input type="submit" name="add" class="trac-disable-on-submit" value="${_('Add')}" />
           </div>
           <p class="help">
             Grant permission for an action to a subject, which can be either a user
@@ -47,7 +48,7 @@
             <label>Group: <input id="sg_group" type="text" name="group" /></label>
           </div>
           <div class="buttons">
-            <input type="submit" name="add" value="${_('Add')}"/>
+            <input type="submit" name="add" class="trac-disable-on-submit" value="${_('Add')}"/>
           </div>
           <p class="help">
             Add a user or group to an existing permission group.
@@ -56,7 +57,7 @@
       </form>
     </py:if>
 
-    <form id="revokeform" method="post" action="" py:with="can_revoke = 'PERMISSION_REVOKE' in perm">
+    <form id="revokeform" method="post" action="" py:with="can_revoke = 'PERMISSION_REVOKE' in perm('admin', 'general/perm')">
       <h3>Permissions</h3>
       <table class="listing" id="permlist">
         <thead>
@@ -67,14 +68,18 @@
               class="${'odd' if idx % 2 else 'even'}">
             <td>$subject</td>
             <td>
-              <label py:for="subject, action in perm_group">
+              <label py:for="subject, action in perm_group"
+                     py:with="invalid = action not in actions; has_perm = action in perm">
                 <!--! base64 makes it safe to use ':' as separator when passing
                       both subject and action as one query parameter -->
                 <input py:if="can_revoke" type="checkbox" name="sel"
+                       title="${_('You don\'t have permission to revoke this action')
+                                if not has_perm else None}"
                        value="${'%s:%s' % (unicode_to_base64(subject),
-                                           unicode_to_base64(action))}"/>
-                <span py:strip="action in actions" class="missing"
-                      title="Action is no longer defined">${action}</span>
+                                           unicode_to_base64(action))}"
+                       disabled="${'disabled' if not has_perm else None}" />
+                <span class="${classes(missing=invalid)}"
+                      title="${_('%(action)s is no longer defined', action=action) if invalid else action}">${action}</span>
               </label>
             </td>
           </tr>

Modified: bloodhound/vendor/trac/current/trac/admin/templates/admin_plugins.html
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/templates/admin_plugins.html?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/templates/admin_plugins.html (original)
+++ bloodhound/vendor/trac/current/trac/admin/templates/admin_plugins.html Thu Feb 13 05:08:02 2014
@@ -54,7 +54,7 @@
   </head>
 
   <body>
-    <h2>Manage Plugins</h2>
+    <h2>Manage Plugins <span class="trac-count">(${len(plugins)})</span></h2>
 
     <form id="addplug" class="addnew" method="post" enctype="multipart/form-data" action="">
       <fieldset>
@@ -65,8 +65,8 @@
           </label>
         </div>
         <div class="buttons">
-          <input type="submit" name="install" value="${_('Install')}"
-                 disabled="${readonly or None}" />
+          <input type="submit" name="install" class="trac-disable-on-submit"
+                 value="${_('Install')}" disabled="${readonly or None}" />
         </div>
         <p class="help" py:choose="readonly">
           <py:when test="True">
@@ -80,7 +80,7 @@
       </fieldset>
     </form>
 
-    <form py:for="idx, plugin in enumerate(plugins)" method="post" action="">
+    <form py:for="idx, plugin in enumerate(plugins)" id="edit-plugin-${plugin.name.lower()}" method="post" action="">
       <div class="plugin" id="trac-plugin-${plugin.name}">
         <h3 class="foldable">${plugin.name} ${plugin.version}</h3>
         <!--! FIXME: Plugin uninstall disabled as it is unreliable (#3545)
@@ -148,7 +148,7 @@
                 </p>
                 <div py:if="module.description" xml:space="preserve">${safe_wiki_to_html(context, module.description)}</div>
               </td>
-              <td class="sel"></td>
+              <td class="sel trac-module"></td>
             </tr>
             <tr py:for="component_name, component in sorted(module.components.iteritems())">
               <td py:with="show_doc = show == plugin.name or show == component.full_name" id="trac-comp-${component.full_name}"
@@ -164,7 +164,7 @@
                 </p>
                 <div py:if="component.description" xml:space="preserve">${safe_wiki_to_html(context, component.description)}</div>
               </td>
-              <td class="sel">
+              <td class="sel trac-component">
                 <input py:if="not component.required" type="hidden" name="component"
                        value="${module_name}.${component_name}" />
                 <input type="checkbox" name="enable"

Modified: bloodhound/vendor/trac/current/trac/admin/tests/__init__.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/tests/__init__.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/tests/__init__.py (original)
+++ bloodhound/vendor/trac/current/trac/admin/tests/__init__.py Thu Feb 13 05:08:02 2014
@@ -1,3 +1,16 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2013 Edgewall Software
+# 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/.
+
 import unittest
 
 from trac.admin.tests import console

Modified: bloodhound/vendor/trac/current/trac/admin/tests/console-tests.txt
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/tests/console-tests.txt?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/tests/console-tests.txt (original)
+++ bloodhound/vendor/trac/current/trac/admin/tests/console-tests.txt Thu Feb 13 05:08:02 2014
@@ -54,7 +54,7 @@ resolution remove    Remove a resolution
 session add          Create a session for the given sid
 session delete       Delete the session of the specified sid
 session list         List the name and email for the given sids
-session purge        Purge all anonymous sessions older than the given age
+session purge        Purge all anonymous sessions older than the given age or date
 session set          Set the name or email attribute of the given sid
 severity add         Add a severity value option
 severity change      Change a severity value
@@ -87,6 +87,8 @@ wiki upgrade         Upgrade default wik
 Name  Size  Author  Date  Description
 -------------------------------------
 
+===== test_attachment_add_nonexistent_resource =====
+ResourceNotFound: NonExistentPage doesn't exist, can't create attachment
 ===== test_config_get =====
 Test project
 ===== test_config_set =====
@@ -357,9 +359,11 @@ Available actions:
  WIKI_MODIFY, WIKI_RENAME, WIKI_VIEW
 
 ===== test_permission_remove_unknown_user =====
-Error: Cannot remove permission TICKET_VIEW for user joe.
+Error: Cannot remove permission TICKET_VIEW for user joe. The user has not been granted the permission.
 ===== test_permission_remove_action_not_granted =====
-Error: Cannot remove permission TICKET_CREATE for user anonymous.
+Error: Cannot remove permission TICKET_CREATE for user anonymous. The user has not been granted the permission.
+===== test_permission_remove_action_granted_through_meta_permission =====
+Error: Cannot remove permission WIKI_VIEW for user joe. The permission is granted through a meta-permission or group.
 ===== test_permission_export_ok =====
 anonymous,BROWSER_VIEW,CHANGESET_VIEW,FILE_VIEW,LOG_VIEW,MILESTONE_VIEW,REPORT_SQL_VIEW,REPORT_VIEW,ROADMAP_VIEW,SEARCH_VIEW,TICKET_VIEW,TIMELINE_VIEW,WIKI_VIEW
 authenticated,TICKET_CREATE,TICKET_MODIFY,WIKI_CREATE,WIKI_MODIFY
@@ -412,6 +416,14 @@ Name           Owner
 -----------------------
 component1     somebody
 component2     somebody
+new_component
+
+===== test_component_add_optional_owner_ok =====
+
+Name           Owner
+-----------------------
+component1     somebody
+component2     somebody
 new_component  new_user
 
 ===== test_component_add_error_already_exists =====
@@ -720,6 +732,8 @@ milestone4
 
 ===== test_milestone_add_error_already_exists =====
 IntegrityError: [...]
+===== test_milestone_add_invalid_date =====
+TracError: "<add>" is an invalid date, or the date format is not known. Try "%(hint)s" or "%(isohint)s" instead.
 ===== test_milestone_rename_ok =====
 
 Name               Due  Completed
@@ -751,6 +765,8 @@ milestone4
 
 ===== test_milestone_due_error_bad_milestone =====
 ResourceNotFound: Milestone bad_milestone does not exist.
+===== test_milestone_due_invalid_date =====
+TracError: "<due>" is an invalid date, or the date format is not known. Try "%(hint)s" or "%(isohint)s" instead.
 ===== test_milestone_completed_ok =====
 
 Name        Due  Completed
@@ -762,6 +778,8 @@ milestone4
 
 ===== test_milestone_completed_error_bad_milestone =====
 ResourceNotFound: Milestone bad_milestone does not exist.
+===== test_milestone_completed_invalid_date =====
+TracError: "<com>" is an invalid date, or the date format is not known. Try "%(hint)s" or "%(isohint)s" instead.
 ===== test_milestone_remove_ok =====
 
 Name        Due  Completed
@@ -1013,3 +1031,5 @@ name17  0     2010-01-18  val17  val17
 name18  0     2010-01-19  val18  val18
 name19  0     2010-01-20  val19  val19
 
+===== test_session_purge_invalid_date =====
+TracError: "<purge>" is an invalid date, or the date format is not known. Try "%(hint)s" or "%(isohint)s" instead.

Modified: bloodhound/vendor/trac/current/trac/admin/tests/console.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/tests/console.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/tests/console.py (original)
+++ bloodhound/vendor/trac/current/trac/admin/tests/console.py Thu Feb 13 05:08:02 2014
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2004-2009 Edgewall Software
+# Copyright (C) 2004-2013 Edgewall Software
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -42,9 +42,13 @@ import trac.search.web_ui
 import trac.timeline.web_ui
 import trac.wiki.web_ui
 
-from trac.admin import console, console_date_format
+from trac.admin import console
+from trac.admin.api import AdminCommandManager, console_date_format, \
+                           get_console_locale
 from trac.test import EnvironmentStub
-from trac.util.datefmt import format_date, get_date_format_hint
+from trac.util.datefmt import format_date, get_date_format_hint, \
+                              get_datetime_format_hint
+from trac.util.translation import get_available_locales, has_babel
 from trac.web.tests.session import _prep_session_table
 
 STRIP_TRAILING_SPACE = re.compile(r'( +)$', re.MULTILINE)
@@ -123,8 +127,17 @@ class TracadminTestCase(unittest.TestCas
             sys.stderr = _err
             sys.stdout = _out
 
+    @property
+    def _datetime_format_hint(self):
+        return get_datetime_format_hint(get_console_locale(self.env))
+
+    def _get_command_help(self, *args):
+        docs = AdminCommandManager(self.env).get_command_help(list(args))
+        self.assertEqual(1, len(docs))
+        return docs[0][2]
+
     def assertEqual(self, expected_results, output):
-        if not (isinstance(expected_results, basestring) and \
+        if not (isinstance(expected_results, basestring) and
                 isinstance(output, basestring)):
             return unittest.TestCase.assertEqual(self, expected_results, output)
         def diff():
@@ -161,6 +174,61 @@ class TracadminTestCase(unittest.TestCas
         self.assertEqual(0, rv)
         self.assertEqual(expected_results, output)
 
+    # Locale test
+
+    def _test_get_console_locale_with_babel(self):
+        from babel.core import Locale, UnknownLocaleError
+        locales = get_available_locales()
+        en_US = Locale.parse('en_US')
+        de = Locale.parse('de')
+        de_DE = Locale.parse('de_DE')
+        try:
+            default = Locale.default()
+        except UnknownLocaleError:
+            default = None
+
+        language = self.env.config.get('trac', 'default_language')
+        try:
+            self.assertEqual(default, get_console_locale(None, None))
+            self.env.config.set('trac', 'default_language', '')
+            if 'de' in locales:
+                self.assertEqual(de, get_console_locale(None, 'de_DE.UTF8'))
+                self.env.config.set('trac', 'default_language', 'de')
+                self.assertEqual(de, get_console_locale(self.env, None))
+                self.assertEqual(de, get_console_locale(self.env, 'C'))
+                self.env.config.set('trac', 'default_language', 'en_US')
+                self.assertEqual(en_US, get_console_locale(self.env, None))
+                self.assertEqual(en_US, get_console_locale(self.env, 'C'))
+                self.assertEqual(de, get_console_locale(self.env,
+                                                        'de_DE.UTF8'))
+            if not locales:  # compiled catalog is missing
+                self.assertEqual(default, get_console_locale(None,
+                                                             'de_DE.UTF8'))
+                self.env.config.set('trac', 'default_language', 'de')
+                self.assertEqual(default, get_console_locale(self.env, None))
+                self.assertEqual(default, get_console_locale(self.env, 'C'))
+                self.env.config.set('trac', 'default_language', 'en_US')
+                self.assertEqual(en_US, get_console_locale(self.env, None))
+                self.assertEqual(en_US, get_console_locale(self.env, 'C'))
+                self.assertEqual(en_US, get_console_locale(self.env,
+                                                           'de_DE.UTF8'))
+        finally:
+            self.env.config.set('trac', 'default_language', language)
+
+    def _test_get_console_locale_without_babel(self):
+        self.assertEqual(None, get_console_locale(None, 'en_US.UTF8'))
+        language = self.env.config.get('trac', 'default_language')
+        try:
+            self.env.config.set('trac', 'default_language', 'en_US')
+            self.assertEqual(None, get_console_locale(self.env, 'en_US.UTF8'))
+        finally:
+            self.env.config.set('trac', 'default_language', language)
+
+    if has_babel:
+        test_get_console_locale = _test_get_console_locale_with_babel
+    else:
+        test_get_console_locale = _test_get_console_locale_without_babel
+
     # Attachment tests
 
     def test_attachment_list_empty(self):
@@ -177,6 +245,15 @@ class TracadminTestCase(unittest.TestCas
         self.assertEqual(0, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
+    def test_attachment_add_nonexistent_resource(self):
+        """Tests the 'attachment add' command in trac-admin, on a non-existent
+        resource."""
+        test_name = sys._getframe().f_code.co_name
+        rv, output = self._execute('attachment add wiki:NonExistentPage %s'
+                                   % __file__)
+        self.assertEqual(2, rv)
+        self.assertEqual(self.expected_results[test_name], output)
+
     # Config tests
 
     def test_config_get(self):
@@ -334,6 +411,17 @@ class TracadminTestCase(unittest.TestCas
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
+    def test_permission_remove_action_granted_through_meta_permission(self):
+        """
+        Tests the 'permission remove' command in trac-admin.  This particular
+        test tries removing WIKI_VIEW from a user. WIKI_VIEW has been granted
+        through user anonymous."""
+        test_name = sys._getframe().f_code.co_name
+        self._execute('permission add joe TICKET_VIEW')
+        rv, output = self._execute('permission remove joe WIKI_VIEW')
+        self.assertEqual(2, rv)
+        self.assertEqual(self.expected_results[test_name], output)
+
     def test_permission_export_ok(self):
         """
         Tests the 'permission export' command in trac-admin.  This particular
@@ -381,6 +469,18 @@ class TracadminTestCase(unittest.TestCas
         test passes valid arguments and checks for success.
         """
         test_name = sys._getframe().f_code.co_name
+        self._execute('component add new_component')
+        rv, output = self._execute('component list')
+        self.assertEqual(0, rv)
+        self.assertEqual(self.expected_results[test_name], output)
+
+    def test_component_add_optional_owner_ok(self):
+        """
+        Tests the 'component add' command in trac-admin with the optional
+        'owner' argument.  This particular test passes valid arguments and
+        checks for success.
+        """
+        test_name = sys._getframe().f_code.co_name
         self._execute('component add new_component new_user')
         rv, output = self._execute('component list')
         self.assertEqual(0, rv)
@@ -449,7 +549,7 @@ class TracadminTestCase(unittest.TestCas
         rv, output = self._execute('component chown bad_component changed_owner')
         self.assertEqual(2, rv)
         # We currently trigger a deprecation warning with py26 so we
-        # can currrently only verify that the end of the output string is
+        # can currently only verify that the end of the output string is
         # correct
         self.assertEqual(output.endswith(self.expected_results[test_name]), True)
 
@@ -993,7 +1093,7 @@ class TracadminTestCase(unittest.TestCas
         """
         test_name = sys._getframe().f_code.co_name
         self._execute(u'milestone add \xa9tat_final "%s"'  #\xc2\xa9
-                              % self._test_date)
+                      % self._test_date)
         rv, output = self._execute('milestone list')
         self.assertEqual(0, rv)
         self.assertEqual(self.expected_results[test_name], output)
@@ -1010,6 +1110,15 @@ class TracadminTestCase(unittest.TestCas
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
+    def test_milestone_add_invalid_date(self):
+        test_name = sys._getframe().f_code.co_name
+        rv, output = self._execute('milestone add new_milestone <add>')
+        self.assertEqual(2, rv)
+        self.assertEqual(self.expected_results[test_name] %
+                         {'hint': self._datetime_format_hint,
+                          'isohint': get_datetime_format_hint('iso8601')},
+                         output)
+
     def test_milestone_rename_ok(self):
         """
         Tests the 'milestone rename' command in trac-admin.  This particular
@@ -1065,6 +1174,15 @@ class TracadminTestCase(unittest.TestCas
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
+    def test_milestone_due_invalid_date(self):
+        test_name = sys._getframe().f_code.co_name
+        rv, output = self._execute('milestone due milestone1 <due>')
+        self.assertEqual(2, rv)
+        self.assertEqual(self.expected_results[test_name] %
+                         {'hint': self._datetime_format_hint,
+                          'isohint': get_datetime_format_hint('iso8601')},
+                         output)
+
     def test_milestone_completed_ok(self):
         """
         Tests the 'milestone completed' command in trac-admin.  This particular
@@ -1089,6 +1207,15 @@ class TracadminTestCase(unittest.TestCas
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
+    def test_milestone_completed_invalid_date(self):
+        test_name = sys._getframe().f_code.co_name
+        rv, output = self._execute('milestone completed milestone1 <com>')
+        self.assertEqual(2, rv)
+        self.assertEqual(self.expected_results[test_name] %
+                         {'hint': self._datetime_format_hint,
+                          'isohint': get_datetime_format_hint('iso8601')},
+                         output)
+
     def test_milestone_remove_ok(self):
         """
         Tests the 'milestone remove' command in trac-admin.  This particular
@@ -1171,20 +1298,20 @@ class TracadminTestCase(unittest.TestCas
         self.assertEqual(0, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_add_missing_sid(self):
+    def test_session_add_missing_sid(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session add')
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_add_duplicate_sid(self):
+    def test_session_add_duplicate_sid(self):
         test_name = sys._getframe().f_code.co_name
         _prep_session_table(self.env)
         rv, output = self._execute('session add name00')
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_add_sid_all(self):
+    def test_session_add_sid_all(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session add john John john@example.org')
         self.assertEqual(0, rv)
@@ -1193,7 +1320,7 @@ class TracadminTestCase(unittest.TestCas
                          % {'today': format_date(None, console_date_format)},
                          output)
 
-    def  test_session_add_sid(self):
+    def test_session_add_sid(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session add john')
         self.assertEqual(0, rv)
@@ -1202,7 +1329,7 @@ class TracadminTestCase(unittest.TestCas
                          % {'today': format_date(None, console_date_format)},
                          output)
 
-    def  test_session_add_sid_name(self):
+    def test_session_add_sid_name(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session add john John')
         self.assertEqual(0, rv)
@@ -1211,7 +1338,7 @@ class TracadminTestCase(unittest.TestCas
                          % {'today': format_date(None, console_date_format)},
                          output)
 
-    def  test_session_set_attr_name(self):
+    def test_session_set_attr_name(self):
         test_name = sys._getframe().f_code.co_name
         _prep_session_table(self.env)
         rv, output = self._execute('session set name name00 JOHN')
@@ -1219,7 +1346,7 @@ class TracadminTestCase(unittest.TestCas
         rv, output = self._execute('session list name00')
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_set_attr_email(self):
+    def test_session_set_attr_email(self):
         test_name = sys._getframe().f_code.co_name
         _prep_session_table(self.env)
         rv, output = self._execute('session set email name00 JOHN@EXAMPLE.ORG')
@@ -1227,31 +1354,31 @@ class TracadminTestCase(unittest.TestCas
         rv, output = self._execute('session list name00')
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_set_attr_missing_attr(self):
+    def test_session_set_attr_missing_attr(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session set')
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_set_attr_missing_value(self):
+    def test_session_set_attr_missing_value(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session set name john')
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_set_attr_missing_sid(self):
+    def test_session_set_attr_missing_sid(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session set name')
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_set_attr_nonexistent_sid(self):
+    def test_session_set_attr_nonexistent_sid(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session set name john foo')
         self.assertEqual(2, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_delete_sid(self):
+    def test_session_delete_sid(self):
         test_name = sys._getframe().f_code.co_name
         _prep_session_table(self.env)
         rv, output = self._execute('session delete name00')
@@ -1259,13 +1386,13 @@ class TracadminTestCase(unittest.TestCas
         rv, output = self._execute('session list nam00')
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_delete_missing_params(self):
+    def test_session_delete_missing_params(self):
         test_name = sys._getframe().f_code.co_name
         rv, output = self._execute('session delete')
         self.assertEqual(0, rv)
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_delete_anonymous(self):
+    def test_session_delete_anonymous(self):
         test_name = sys._getframe().f_code.co_name
         _prep_session_table(self.env)
         rv, output = self._execute('session delete anonymous')
@@ -1282,7 +1409,7 @@ class TracadminTestCase(unittest.TestCas
         rv, output = self._execute('session list *')
         self.assertEqual(self.expected_results[test_name], output)
 
-    def  test_session_purge_age(self):
+    def test_session_purge_age(self):
         test_name = sys._getframe().f_code.co_name
         _prep_session_table(self.env, spread_visits=True)
         rv, output = self._execute('session purge 20100112')
@@ -1290,9 +1417,38 @@ class TracadminTestCase(unittest.TestCas
         rv, output = self._execute('session list *')
         self.assertEqual(self.expected_results[test_name], output)
 
+    def test_session_purge_invalid_date(self):
+        test_name = sys._getframe().f_code.co_name
+        rv, output = self._execute('session purge <purge>')
+        self.assertEqual(2, rv)
+        self.assertEqual(self.expected_results[test_name] %
+                         {'hint': self._datetime_format_hint,
+                          'isohint': get_datetime_format_hint('iso8601')},
+                         output)
+
+    def test_help_milestone_due(self):
+        doc = self._get_command_help('milestone', 'due')
+        self.assertIn(self._datetime_format_hint, doc)
+        self.assertIn(u'"YYYY-MM-DDThh:mm:ss±hh:mm"', doc)
+
+    def test_help_milestone_completed(self):
+        doc = self._get_command_help('milestone', 'completed')
+        self.assertIn(self._datetime_format_hint, doc)
+        self.assertIn(u'"YYYY-MM-DDThh:mm:ss±hh:mm"', doc)
+
+    def test_help_version_time(self):
+        doc = self._get_command_help('version', 'time')
+        self.assertIn(self._datetime_format_hint, doc)
+        self.assertIn(u'"YYYY-MM-DDThh:mm:ss±hh:mm"', doc)
+
+    def test_help_session_purge(self):
+        doc = self._get_command_help('session', 'purge')
+        self.assertIn(u'"YYYY-MM-DDThh:mm:ss±hh:mm"', doc)
+
 
 def suite():
-    return unittest.makeSuite(TracadminTestCase, 'test')
+    return unittest.makeSuite(TracadminTestCase)
+
 
 if __name__ == '__main__':
-    unittest.main()
+    unittest.main(defaultTest='suite')

Modified: bloodhound/vendor/trac/current/trac/admin/tests/functional.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/tests/functional.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/tests/functional.py (original)
+++ bloodhound/vendor/trac/current/trac/admin/tests/functional.py Thu Feb 13 05:08:02 2014
@@ -1,7 +1,68 @@
-#!/usr/bin/python
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2013 Edgewall Software
+# 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/.
+
 from trac.tests.functional import *
 from trac.util.text import unicode_to_base64, unicode_from_base64
 
+
+class AuthorizationTestCaseSetup(FunctionalTwillTestCaseSetup):
+    def test_authorization(self, href, perms, h2_text):
+        """Check permissions required to access an administration panel. A
+        fine-grained permissions test will also be executed if ConfigObj is
+        installed.
+
+        :param href: the relative href of the administration panel
+        :param perms: list or tuple of permissions required to access
+                      the administration panel
+        :param h2_text: the body of the h2 heading on the administration
+                        panel"""
+        self._tester.go_to_front()
+        self._tester.logout()
+        self._tester.login('user')
+        if isinstance(perms, basestring):
+            perms = (perms, )
+
+        h2 = r'<h2>[ \t\n]*%s[ \t\n]*' \
+             r'( <span class="trac-count">\(\d+\)</span>)?[ \t\n]*</h2>'
+        try:
+            for perm in perms:
+                try:
+                    tc.go(href)
+                    tc.find("No administration panels available")
+                    self._testenv.grant_perm('user', perm)
+                    tc.go(href)
+                    tc.find(h2 % h2_text)
+                finally:
+                    self._testenv.revoke_perm('user', perm)
+                try:
+                    tc.go(href)
+                    tc.find("No administration panels available")
+                    self._testenv.enable_authz_permpolicy({
+                        href.strip('/').replace('/', ':', 1): {'user': perm},
+                    })
+                    tc.go(href)
+                    tc.find(h2 % h2_text)
+                except ImportError:
+                    pass
+                finally:
+                    self._testenv.disable_authz_permpolicy()
+        finally:
+            self._tester.go_to_front()
+            self._tester.logout()
+            self._tester.login('admin')
+
+
 class TestBasicSettings(FunctionalTwillTestCaseSetup):
     def runTest(self):
         """Check basic settings."""
@@ -11,24 +72,36 @@ class TestBasicSettings(FunctionalTwillT
         tc.find('https://my.example.com/something')
 
 
+class TestBasicSettingsAuthorization(AuthorizationTestCaseSetup):
+    def runTest(self):
+        """Check permissions required to access Basic Settings panel."""
+        self.test_authorization('/admin/general/basics', 'TRAC_ADMIN',
+                                "Basic Settings")
+
+
 class TestLoggingNone(FunctionalTwillTestCaseSetup):
     def runTest(self):
         """Turn off logging."""
         # For now, we just check that it shows up.
-        self._tester.go_to_admin()
-        tc.follow('Logging')
+        self._tester.go_to_admin("Logging")
         tc.find('trac.log')
         tc.formvalue('modlog', 'log_type', 'none')
         tc.submit()
         tc.find('selected="selected">None</option')
 
 
+class TestLoggingAuthorization(AuthorizationTestCaseSetup):
+    def runTest(self):
+        """Check permissions required to access Logging panel."""
+        self.test_authorization('/admin/general/logging', 'TRAC_ADMIN',
+                                "Logging")
+
+
 class TestLoggingToFile(FunctionalTwillTestCaseSetup):
     def runTest(self):
         """Turn logging back on."""
         # For now, we just check that it shows up.
-        self._tester.go_to_admin()
-        tc.follow('Logging')
+        self._tester.go_to_admin("Logging")
         tc.find('trac.log')
         tc.formvalue('modlog', 'log_type', 'file')
         tc.formvalue('modlog', 'log_file', 'trac.log2')
@@ -43,8 +116,7 @@ class TestLoggingToFileNormal(Functional
     def runTest(self):
         """Setting logging back to normal."""
         # For now, we just check that it shows up.
-        self._tester.go_to_admin()
-        tc.follow('Logging')
+        self._tester.go_to_admin("Logging")
         tc.find('trac.log')
         tc.formvalue('modlog', 'log_file', 'trac.log')
         tc.formvalue('modlog', 'log_level', 'DEBUG')
@@ -54,11 +126,18 @@ class TestLoggingToFileNormal(Functional
         tc.find('selected="selected">DEBUG</option>')
 
 
+class TestPermissionsAuthorization(AuthorizationTestCaseSetup):
+    def runTest(self):
+        """Check permissions required to access Permissions panel."""
+        self.test_authorization('/admin/general/perm',
+                                ('PERMISSION_GRANT', 'PERMISSION_REVOKE'),
+                                "Manage Permissions and Groups")
+
+
 class TestCreatePermissionGroup(FunctionalTwillTestCaseSetup):
     def runTest(self):
         """Create a permissions group"""
-        self._tester.go_to_admin()
-        tc.follow('Permissions')
+        self._tester.go_to_admin("Permissions")
         tc.find('Manage Permissions')
         tc.formvalue('addperm', 'gp_subject', 'somegroup')
         tc.formvalue('addperm', 'action', 'REPORT_CREATE')
@@ -71,8 +150,7 @@ class TestCreatePermissionGroup(Function
 class TestAddUserToGroup(FunctionalTwillTestCaseSetup):
     def runTest(self):
         """Add a user to a permissions group"""
-        self._tester.go_to_admin()
-        tc.follow('Permissions')
+        self._tester.go_to_admin("Permissions")
         tc.find('Manage Permissions')
         tc.formvalue('addsubj', 'sg_subject', 'authenticated')
         tc.formvalue('addsubj', 'sg_group', 'somegroup')
@@ -85,8 +163,7 @@ class TestAddUserToGroup(FunctionalTwill
 class TestRemoveUserFromGroup(FunctionalTwillTestCaseSetup):
     def runTest(self):
         """Remove a user from a permissions group"""
-        self._tester.go_to_admin()
-        tc.follow('Permissions')
+        self._tester.go_to_admin("Permissions")
         tc.find('Manage Permissions')
         authenticated = unicode_to_base64('authenticated')
         somegroup = unicode_to_base64('somegroup')
@@ -99,8 +176,7 @@ class TestRemoveUserFromGroup(Functional
 class TestRemovePermissionGroup(FunctionalTwillTestCaseSetup):
     def runTest(self):
         """Remove a permissions group"""
-        self._tester.go_to_admin()
-        tc.follow('Permissions')
+        self._tester.go_to_admin("Permissions")
         tc.find('Manage Permissions')
         somegroup = unicode_to_base64('somegroup')
         REPORT_CREATE = unicode_to_base64('REPORT_CREATE')
@@ -114,25 +190,144 @@ class TestRemovePermissionGroup(Function
 class TestPluginSettings(FunctionalTwillTestCaseSetup):
     def runTest(self):
         """Check plugin settings."""
-        self._tester.go_to_admin()
-        tc.follow('Plugins')
+        self._tester.go_to_admin("Plugins")
         tc.find('Manage Plugins')
         tc.find('Install Plugin')
 
 
+class TestPluginsAuthorization(AuthorizationTestCaseSetup):
+    def runTest(self):
+        """Check permissions required to access Logging panel."""
+        self.test_authorization('/admin/general/plugin', 'TRAC_ADMIN',
+                                "Manage Plugins")
+
+
+class RegressionTestTicket10752(FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test for regression of http://trac.edgewall.org/ticket/10752
+        Permissions on the web admin page should be greyed out when they
+        are no longer defined.
+        """
+        env = self._testenv.get_trac_environment()
+        env.db_transaction("INSERT INTO permission VALUES (%s,%s)",
+                           ('anonymous', 'MISSING_PERMISSION'))
+        env.config.touch()
+
+        self._tester.go_to_admin("Permissions")
+        tc.find('<span class="missing" '
+                'title="MISSING_PERMISSION is no longer defined">'
+                'MISSING_PERMISSION</span>')
+
+
+class RegressionTestTicket11069(FunctionalTwillTestCaseSetup):
+    def runTest(self):
+        """Test for regression of http://trac.edgewall.org/ticket/11069
+        The permissions list should only be populated with permissions that
+        the user can grant."""
+        self._tester.go_to_front()
+        self._tester.logout()
+        self._tester.login('user')
+        self._testenv.grant_perm('user', 'PERMISSION_GRANT')
+        env = self._testenv.get_trac_environment()
+        from trac.perm import PermissionSystem
+        user_perms = PermissionSystem(env).get_user_permissions('user')
+        all_actions = PermissionSystem(env).get_actions()
+        try:
+            self._tester.go_to_admin("Permissions")
+            for action in all_actions:
+                option = r"<option>%s</option>" % action
+                if action in user_perms and user_perms[action] is True:
+                    tc.find(option)
+                else:
+                    tc.notfind(option)
+        finally:
+            self._testenv.revoke_perm('user', 'PERMISSION_GRANT')
+            self._tester.go_to_front()
+            self._tester.logout()
+            self._tester.login('admin')
+
+
+class RegressionTestTicket11095(FunctionalTwillTestCaseSetup):
+    """Test for regression of http://trac.edgewall.org/ticket/11095
+    The permission is truncated if it overflows the available space (CSS)
+    and the full permission name is shown in the title on hover.
+    """
+    def runTest(self):
+        self._tester.go_to_admin("Permissions")
+        tc.find('<span title="MILESTONE_VIEW">MILESTONE_VIEW</span>')
+        tc.find('<span title="WIKI_VIEW">WIKI_VIEW</span>')
+
+
+class RegressionTestTicket11117(FunctionalTwillTestCaseSetup):
+    """Test for regression of http://trac.edgewall.org/ticket/11117
+    Hint should be shown on the Basic Settings admin panel when pytz is not
+    installed.
+    """
+    def runTest(self):
+        self._tester.go_to_admin("Basic Settings")
+        pytz_hint = "Install pytz for a complete list of timezones."
+        from trac.util.datefmt import pytz
+        if pytz is None:
+            tc.find(pytz_hint)
+        else:
+            tc.notfind(pytz_hint)
+
+
+class RegressionTestTicket11257(FunctionalTwillTestCaseSetup):
+    """Test for regression of http://trac.edgewall.org/ticket/11257
+    Hints should be shown on the Basic Settings admin panel when Babel is not
+    installed.
+    """
+    def runTest(self):
+        from trac.util.translation import get_available_locales, has_babel
+
+        babel_hint_lang = "Install Babel for extended language support."
+        babel_hint_date = "Install Babel for localized date formats."
+        catalog_hint = "Message catalogs have not been compiled."
+        language_select = '<select name="default_language">'
+        disabled_language_select = \
+            '<select name="default_language" disabled="disabled" ' \
+            'title="Translations are currently unavailable">'
+
+        self._tester.go_to_admin("Basic Settings")
+        if has_babel:
+            tc.notfind(babel_hint_lang)
+            tc.notfind(babel_hint_date)
+            if get_available_locales():
+                tc.find(language_select)
+                tc.notfind(catalog_hint)
+            else:
+                tc.find(disabled_language_select)
+                tc.find(catalog_hint)
+        else:
+            tc.find(disabled_language_select)
+            tc.find(babel_hint_lang)
+            tc.find(babel_hint_date)
+            tc.notfind(catalog_hint)
+
+
 def functionalSuite(suite=None):
     if not suite:
-        import trac.tests.functional.testcases
-        suite = trac.tests.functional.testcases.functionalSuite()
+        import trac.tests.functional
+        suite = trac.tests.functional.functionalSuite()
     suite.addTest(TestBasicSettings())
+    suite.addTest(TestBasicSettingsAuthorization())
     suite.addTest(TestLoggingNone())
+    suite.addTest(TestLoggingAuthorization())
     suite.addTest(TestLoggingToFile())
     suite.addTest(TestLoggingToFileNormal())
+    suite.addTest(TestPermissionsAuthorization())
     suite.addTest(TestCreatePermissionGroup())
     suite.addTest(TestAddUserToGroup())
     suite.addTest(TestRemoveUserFromGroup())
     suite.addTest(TestRemovePermissionGroup())
     suite.addTest(TestPluginSettings())
+    suite.addTest(TestPluginsAuthorization())
+    suite.addTest(RegressionTestTicket10752())
+    suite.addTest(RegressionTestTicket11069())
+    suite.addTest(RegressionTestTicket11095())
+    suite.addTest(RegressionTestTicket11117())
+    suite.addTest(RegressionTestTicket11257())
     return suite
 
 

Modified: bloodhound/vendor/trac/current/trac/admin/web_ui.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/admin/web_ui.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/admin/web_ui.py (original)
+++ bloodhound/vendor/trac/current/trac/admin/web_ui.py Thu Feb 13 05:08:02 2014
@@ -34,9 +34,9 @@ from trac.admin.api import IAdminPanelPr
 from trac.core import *
 from trac.loader import get_plugin_info, get_plugins_dir
 from trac.perm import PermissionSystem, IPermissionRequestor
-from trac.util.datefmt import all_timezones
+from trac.util.datefmt import all_timezones, pytz
 from trac.util.text import exception_to_unicode, \
-                            unicode_to_base64, unicode_from_base64
+                           unicode_to_base64, unicode_from_base64
 from trac.util.translation import _, get_available_locales, ngettext
 from trac.web import HTTPNotFound, IRequestHandler
 from trac.web.chrome import add_notice, add_stylesheet, \
@@ -111,8 +111,8 @@ class AdminModule(Component):
         path_info = req.args.get('path_info')
         if not panel_id:
             try:
-                panel_id = filter(
-                            lambda panel: panel[0] == cat_id, panels)[0][2]
+                panel_id = \
+                    filter(lambda panel: panel[0] == cat_id, panels)[0][2]
             except IndexError:
                 raise HTTPNotFound(_('Unknown administration panel'))
 
@@ -206,19 +206,19 @@ class BasicsAdminPanel(Component):
     # IAdminPanelProvider methods
 
     def get_admin_panels(self, req):
-        if 'TRAC_ADMIN' in req.perm:
+        if 'TRAC_ADMIN' in req.perm('admin', 'general/basics'):
             yield ('general', _('General'), 'basics', _('Basic Settings'))
 
     def render_admin_panel(self, req, cat, page, path_info):
-        req.perm.require('TRAC_ADMIN')
-
         if Locale:
-            locales = [Locale.parse(locale)
-                       for locale in  get_available_locales()]
-            languages = sorted((str(locale), locale.display_name)
-                               for locale in locales)
+            locale_ids = get_available_locales()
+            locales = [Locale.parse(locale) for locale in locale_ids]
+            # don't use str(locale) to prevent storing expanded locale
+            # identifier, see #11258
+            languages = sorted((id, locale.display_name)
+                               for id, locale in zip(locale_ids, locales))
         else:
-            locales, languages = [], []
+            locale_ids, locales, languages = [], [], []
 
         if req.method == 'POST':
             for option in ('name', 'url', 'descr'):
@@ -230,7 +230,7 @@ class BasicsAdminPanel(Component):
             self.config.set('trac', 'default_timezone', default_timezone)
 
             default_language = req.args.get('default_language')
-            if default_language not in locales:
+            if default_language not in locale_ids:
                 default_language = ''
             self.config.set('trac', 'default_language', default_language)
 
@@ -249,9 +249,11 @@ class BasicsAdminPanel(Component):
         data = {
             'default_timezone': default_timezone,
             'timezones': all_timezones,
+            'has_pytz': pytz is not None,
             'default_language': default_language.replace('-', '_'),
             'languages': languages,
             'default_date_format': default_date_format,
+            'has_babel': Locale is not None,
         }
         Chrome(self.env).add_textarea_grips(req)
         return 'admin_basics.html', data
@@ -264,7 +266,7 @@ class LoggingAdminPanel(Component):
     # IAdminPanelProvider methods
 
     def get_admin_panels(self, req):
-        if 'TRAC_ADMIN' in req.perm:
+        if 'TRAC_ADMIN' in req.perm('admin', 'general/logging'):
             yield ('general', _('General'), 'logging', _('Logging'))
 
     def render_admin_panel(self, req, cat, page, path_info):
@@ -352,7 +354,8 @@ class PermissionAdminPanel(Component):
 
     # IAdminPanelProvider methods
     def get_admin_panels(self, req):
-        if 'PERMISSION_GRANT' in req.perm or 'PERMISSION_REVOKE' in req.perm:
+        perm = req.perm('admin', 'general/perm')
+        if 'PERMISSION_GRANT' in perm or 'PERMISSION_REVOKE' in perm:
             yield ('general', _('General'), 'perm', _('Permissions'))
 
     def render_admin_panel(self, req, cat, page, path_info):
@@ -366,13 +369,13 @@ class PermissionAdminPanel(Component):
             group = req.args.get('group', '').strip()
 
             if subject and subject.isupper() or \
-                   group and group.isupper():
+                    group and group.isupper():
                 raise TracError(_('All upper-cased tokens are reserved for '
                                   'permission names'))
 
             # Grant permission to subject
             if req.args.get('add') and subject and action:
-                req.perm.require('PERMISSION_GRANT')
+                req.perm('admin', 'general/perm').require('PERMISSION_GRANT')
                 if action not in all_actions:
                     raise TracError(_('Unknown action'))
                 req.perm.require(action)
@@ -389,11 +392,11 @@ class PermissionAdminPanel(Component):
 
             # Add subject to group
             elif req.args.get('add') and subject and group:
-                req.perm.require('PERMISSION_GRANT')
+                req.perm('admin', 'general/perm').require('PERMISSION_GRANT')
                 for action in perm.get_user_permissions(group):
                     if not action in all_actions: # plugin disabled?
-                        self.env.log.warn("Adding %s to group %s: " \
-                            "Permission %s unavailable, skipping perm check." \
+                        self.env.log.warn("Adding %s to group %s: "
+                            "Permission %s unavailable, skipping perm check."
                             % (subject, group, action))
                     else:
                         req.perm.require(action)
@@ -410,7 +413,7 @@ class PermissionAdminPanel(Component):
 
             # Remove permissions action
             elif req.args.get('remove') and req.args.get('sel'):
-                req.perm.require('PERMISSION_REVOKE')
+                req.perm('admin', 'general/perm').require('PERMISSION_REVOKE')
                 sel = req.args.get('sel')
                 sel = sel if isinstance(sel, list) else [sel]
                 for key in sel:
@@ -439,12 +442,10 @@ class PluginAdminPanel(Component):
     # IAdminPanelProvider methods
 
     def get_admin_panels(self, req):
-        if 'TRAC_ADMIN' in req.perm:
+        if 'TRAC_ADMIN' in req.perm('admin', 'general/plugin'):
             yield ('general', _('General'), 'plugin', _('Plugins'))
 
     def render_admin_panel(self, req, cat, page, path_info):
-        req.perm.require('TRAC_ADMIN')
-
         if req.method == 'POST':
             if 'install' in req.args:
                 self._do_install(req)
@@ -453,7 +454,7 @@ class PluginAdminPanel(Component):
             else:
                 self._do_update(req)
             anchor = ''
-            if req.args.has_key('plugin'):
+            if 'plugin' in req.args:
                 anchor = '#no%d' % (int(req.args.get('plugin')) + 1)
             req.redirect(req.href.admin(cat, page) + anchor)
 
@@ -463,7 +464,7 @@ class PluginAdminPanel(Component):
 
     def _do_install(self, req):
         """Install a plugin."""
-        if not req.args.has_key('plugin_file'):
+        if 'plugin_file' not in req.args:
             raise TracError(_('No file uploaded'))
         upload = req.args['plugin_file']
         if isinstance(upload, unicode) or not upload.filename:

Modified: bloodhound/vendor/trac/current/trac/attachment.py
URL: http://svn.apache.org/viewvc/bloodhound/vendor/trac/current/trac/attachment.py?rev=1567849&r1=1567848&r2=1567849&view=diff
==============================================================================
--- bloodhound/vendor/trac/current/trac/attachment.py (original)
+++ bloodhound/vendor/trac/current/trac/attachment.py Thu Feb 13 05:08:02 2014
@@ -38,7 +38,7 @@ from trac.mimeview import *
 from trac.perm import PermissionError, IPermissionPolicy
 from trac.resource import *
 from trac.search import search_to_sql, shorten_result
-from trac.util import content_disposition, get_reporter_id
+from trac.util import content_disposition, create_zipinfo, get_reporter_id
 from trac.util.compat import sha1
 from trac.util.datefmt import format_datetime, from_utimestamp, \
                               to_datetime, to_utimestamp, utc
@@ -93,8 +93,9 @@ class IAttachmentManipulator(Interface):
         attachment. Therefore, a return value of ``[]`` means
         everything is OK."""
 
+
 class ILegacyAttachmentPolicyDelegate(Interface):
-    """Interface that can be used by plugins to seemlessly participate
+    """Interface that can be used by plugins to seamlessly participate
        to the legacy way of checking for attachment permissions.
 
        This should no longer be necessary once it becomes easier to
@@ -303,6 +304,12 @@ class Attachment(object):
             t = to_datetime(t, utc)
         self.date = t
 
+        parent_resource = self.resource.parent
+        if not resource_exists(self.env, parent_resource):
+            raise ResourceNotFound(
+                _("%(parent)s doesn't exist, can't create attachment",
+                  parent=get_resource_name(self.env, parent_resource)))
+
         # Make sure the path to the attachment is inside the environment
         # attachments directory
         attachments_dir = os.path.join(os.path.normpath(self.env.path),
@@ -333,7 +340,6 @@ class Attachment(object):
         for listener in AttachmentModule(self.env).change_listeners:
             listener.attachment_added(self)
 
-
     @classmethod
     def select(cls, env, parent_realm, parent_id, db=None):
         """Iterator yielding all `Attachment` instances attached to
@@ -369,7 +375,8 @@ class Attachment(object):
                 os.rmdir(attachment_dir)
             except OSError, e:
                 env.log.error("Can't delete attachment directory %s: %s",
-                    attachment_dir, exception_to_unicode(e, traceback=True))
+                              attachment_dir,
+                              exception_to_unicode(e, traceback=True))
 
     @classmethod
     def reparent_all(cls, env, parent_realm, parent_id, new_realm, new_id):
@@ -385,7 +392,8 @@ class Attachment(object):
                 os.rmdir(attachment_dir)
             except OSError, e:
                 env.log.error("Can't delete attachment directory %s: %s",
-                    attachment_dir, exception_to_unicode(e, traceback=True))
+                              attachment_dir,
+                              exception_to_unicode(e, traceback=True))
 
     def open(self):
         path = self.path
@@ -428,8 +436,7 @@ class AttachmentModule(Component):
     CHUNK_SIZE = 4096
 
     max_size = IntOption('attachment', 'max_size', 262144,
-        """Maximum allowed file size (in bytes) for ticket and wiki
-        attachments.""")
+        """Maximum allowed file size (in bytes) for attachments.""")
 
     max_zip_size = IntOption('attachment', 'max_zip_size', 2097152,
         """Maximum allowed total size (in bytes) for an attachment list to be
@@ -491,6 +498,10 @@ class AttachmentModule(Component):
                 parent_id, filename = path[:last_slash], path[last_slash + 1:]
 
         parent = parent_realm(id=parent_id)
+        if not resource_exists(self.env, parent):
+            raise ResourceNotFound(
+                _("Parent resource %(parent)s doesn't exist",
+                  parent=get_resource_name(self.env, parent)))
 
         # Link the attachment page to parent resource
         parent_name = get_resource_name(self.env, parent)
@@ -682,10 +693,6 @@ class AttachmentModule(Component):
     def _do_save(self, req, attachment):
         req.perm(attachment.resource).require('ATTACHMENT_CREATE')
         parent_resource = attachment.resource.parent
-        if not resource_exists(self.env, parent_resource):
-            raise ResourceNotFound(
-                _("%(parent)s doesn't exist, can't create attachment",
-                  parent=get_resource_name(self.env, parent_resource)))
 
         if 'cancel' in req.args:
             req.redirect(get_resource_url(self.env, parent_resource, req.href))
@@ -751,7 +758,7 @@ class AttachmentModule(Component):
             try:
                 old_attachment = Attachment(self.env,
                                             attachment.resource(id=filename))
-                if not (req.authname and req.authname != 'anonymous' \
+                if not (req.authname and req.authname != 'anonymous'
                         and old_attachment.author == req.authname) \
                    and 'ATTACHMENT_DELETE' \
                                         not in req.perm(attachment.resource):
@@ -761,7 +768,7 @@ class AttachmentModule(Component):
                         "attachments requires ATTACHMENT_DELETE permission.",
                         name=filename))
                 if (not attachment.description.strip() and
-                    old_attachment.description):
+                        old_attachment.description):
                     attachment.description = old_attachment.description
                 old_attachment.delete()
             except TracError:
@@ -793,7 +800,7 @@ class AttachmentModule(Component):
     def _render_form(self, req, attachment):
         req.perm(attachment.resource).require('ATTACHMENT_CREATE')
         return {'mode': 'new', 'author': get_reporter_id(req),
-            'attachment': attachment, 'max_size': self.max_size}
+                'attachment': attachment, 'max_size': self.max_size}
 
     def _download_as_zip(self, req, parent, attachments=None):
         if attachments is None:
@@ -810,19 +817,14 @@ class AttachmentModule(Component):
         req.send_header('Content-Disposition',
                         content_disposition('inline', filename))
 
-        from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
+        from zipfile import ZipFile, ZIP_DEFLATED
 
         buf = StringIO()
         zipfile = ZipFile(buf, 'w', ZIP_DEFLATED)
         for attachment in attachments:
-            zipinfo = ZipInfo()
-            zipinfo.filename = attachment.filename.encode('utf-8')
-            zipinfo.flag_bits |= 0x800 # filename is encoded with utf-8
-            zipinfo.date_time = attachment.date.utctimetuple()[:6]
-            zipinfo.compress_type = ZIP_DEFLATED
-            if attachment.description:
-                zipinfo.comment = attachment.description.encode('utf-8')
-            zipinfo.external_attr = 0644 << 16L # needed since Python 2.5
+            zipinfo = create_zipinfo(attachment.filename,
+                                     mtime=attachment.date,
+                                     comment=attachment.description)
             try:
                 with attachment.open() as fd:
                     zipfile.writestr(zipinfo, fd.read())
@@ -986,7 +988,7 @@ class LegacyAttachmentPolicy(Component):
         else:
             for d in self.delegates:
                 decision = d.check_attachment_permission(action, username,
-                        resource, perm)
+                                                         resource, perm)
                 if decision is not None:
                     return decision
 
@@ -1100,4 +1102,3 @@ class AttachmentAdmin(Component):
             finally:
                 if destination is not None:
                     output.close()
-