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/27 03:14:38 UTC

svn commit: r1572404 [3/8] - in /bloodhound/branches/bep_0007_embeddable_objects: ./ bloodhound_dashboard/ bloodhound_dashboard/bhdashboard/ bloodhound_dashboard/bhdashboard/default-pages/ bloodhound_dashboard/bhdashboard/layouts/ bloodhound_dashboard/...

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/api.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/api.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/api.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/api.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,8 +17,6 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-"""Core components to support multi-product"""
-
 import copy
 import os
 import shutil
@@ -37,16 +36,17 @@ from trac.resource import IExternalResou
                           IResourceManager, ResourceNotFound
 from trac.ticket.api import ITicketFieldProvider, ITicketManipulator
 from trac.util.text import to_unicode, unquote_label, unicode_unquote
-from trac.util.translation import _, N_
 from trac.web.chrome import ITemplateProvider, add_warning
 from trac.web.main import FakePerm, FakeSession
+from trac.wiki.admin import WikiAdmin
 from trac.wiki.api import IWikiSyntaxProvider
 from trac.wiki.parser import WikiParser
 
 from multiproduct.dbcursor import GLOBAL_PRODUCT
 from multiproduct.model import Product, ProductResourceMap, ProductSetting
 from multiproduct.util import EmbeddedLinkFormatter, IDENTIFIER, \
-    using_sqlite_backend, using_mysql_backend
+                              using_mysql_backend, using_sqlite_backend
+from multiproduct.util.translation import _, N_, add_domain
 
 __all__ = ['MultiProductSystem', 'PRODUCT_SYNTAX_DELIMITER']
 
@@ -75,7 +75,7 @@ class MultiProductSystem(Component):
 
     implements(IEnvironmentSetupParticipant, IExternalResourceConnector,
                IPermissionRequestor, IResourceChangeListener, IResourceManager,
-               ISupportMultiProductEnvironment, ITemplateProvider, 
+               ISupportMultiProductEnvironment, ITemplateProvider,
                ITicketFieldProvider, IWikiSyntaxProvider, ITicketManipulator)
 
     default_product_prefix = Option(
@@ -83,26 +83,30 @@ class MultiProductSystem(Component):
         'default_product_prefix',
         default='@',
         doc="""Prefix used for default product when migrating single-product
-        installations to multi-product.""")
+        installations to multi-product.""", doc_domain='multiproduct')
+
+    default_product = Option('ticket', 'default_product', '',
+        """Default product for newly created tickets.""")
 
     product_base_url = Option('multiproduct', 'product_base_url', '',
         """A pattern used to generate the base URL of product environments,
         e.g. the use cases listed in bh:wiki:/Proposals/BEP-0003#url-mapping .
-        Both absolute as well as relative URLs are supported. The later 
+        Both absolute as well as relative URLs are supported. The later
         will be resolved with respect to the base URL of the parent global
         environment. The pattern may contain references to $(envname)s,
         $(prefix)s and $(name)s placeholders representing the environment name,
         product prefix and product name respectively . If nothing is set the
         following will be used `products/$(prefix)s`
 
-        Note the usage of `$(...)s` instead of `%(...)s` as the later form 
-        would be interpreted by the ConfigParser itself. """)
+        Note the usage of `$(...)s` instead of `%(...)s` as the later form
+        would be interpreted by the ConfigParser itself. """,
+                              doc_domain='multiproduct')
 
     product_config_parent = PathOption('inherit', 'multiproduct', '',
         """The path to the configuration file containing the settings shared
-        by sibling product environments. By default will inherit 
+        by sibling product environments. By default will inherit
         global environment configuration.
-        """)
+        """, doc_domain='multiproduct')
 
     SCHEMA = [mcls._get_schema()
               for mcls in (Product, ProductResourceMap)]
@@ -119,6 +123,12 @@ class MultiProductSystem(Component):
 
     PRODUCT_POPULATE_TABLES = list(set(MIGRATE_TABLES) - set(['wiki']))
 
+    def __init__(self, *args, **kwargs):
+        import pkg_resources
+        locale_dir = pkg_resources.resource_filename(__name__, 'locale')
+        add_domain(self.env.path, locale_dir)
+        super(MultiProductSystem, self).__init__(*args, **kwargs)
+
     def get_version(self):
         """Finds the current version of the bloodhound database schema"""
         rows = self.env.db_direct_query("""
@@ -204,7 +214,7 @@ class MultiProductSystem(Component):
 
     def upgrade_environment(self, db_dummy=None):
         """Installs or updates tables to current version"""
-        self.log.debug("upgrading existing environment for %s plugin." % 
+        self.log.debug("upgrading existing environment for %s plugin." %
                        PLUGIN_NAME)
         db_installed_version = self.get_version()
         with self.env.db_direct_transaction as db:
@@ -582,6 +592,13 @@ class MultiProductSystem(Component):
                     (table[0], ','.join(cols), ','.join(['%s' for c in cols])),
                     rows)
 
+        # Import default pages in product wiki
+        wikiadmin = WikiAdmin(ProductEnvironment(self.env, product.prefix))
+        pages = ('TitleIndex', 'RecentChanges', 'InterTrac', 'InterWiki')
+        for page in pages:
+            filename = resource_filename('trac.wiki', 'default-pages/' + page)
+            wikiadmin.import_page(filename, page)
+
     def resource_changed(self, resource, old_values, context):
         return
 
@@ -614,8 +631,9 @@ class MultiProductSystem(Component):
     # ITicketFieldProvider methods
     def get_select_fields(self):
         """Product select fields"""
-        return [(35, {'name': 'product', 'label': N_('Product'),
-                      'cls': Product, 'pk': 'prefix', 'optional': False})]
+        return [(35, {'name': 'product', 'label': _('Product'),
+                      'cls': Product, 'pk': 'prefix', 'optional': False,
+                      'value': self.default_product})]
 
     def get_radio_fields(self):
         """Product radio fields"""
@@ -661,12 +679,12 @@ class MultiProductSystem(Component):
         elif neighborhood._realm == 'product':
             prefix = neighborhood._id
         else:
-            raise ResourceNotFound(_(u'Unsupported neighborhood %(realm)s', 
+            raise ResourceNotFound(_(u'Unsupported neighborhood %(realm)s',
                                      realm=neighborhood._realm))
         try:
             return lookup_product_env(self.env, prefix)
         except LookupError:
-            raise ResourceNotFound(_(u'Unknown product prefix %(prefix)s', 
+            raise ResourceNotFound(_(u'Unknown product prefix %(prefix)s',
                                      prefix=prefix))
 
     def manager_exists(self, neighborhood):
@@ -680,7 +698,7 @@ class MultiProductSystem(Component):
             if not prefix:
                 # Global environment
                 return True
-            return Product(lookup_product_env(self.env, GLOBAL_PRODUCT), 
+            return Product(lookup_product_env(self.env, GLOBAL_PRODUCT),
                            {'prefix' : prefix})._exists
 
     # IWikiSyntaxProvider methods
@@ -691,25 +709,25 @@ class MultiProductSystem(Component):
         yield (r'(?<!\S)!?(?P<pid>%s)%s(?P<ptarget>%s:(?:%s)|%s|%s(?:%s*%s)?)' %
                     (IDENTIFIER,
                      PRODUCT_SYNTAX_DELIMITER_RE,
-                     WikiParser.LINK_SCHEME, WikiParser.QUOTED_STRING, 
-                     WikiParser.QUOTED_STRING, WikiParser.SHREF_TARGET_FIRST, 
+                     WikiParser.LINK_SCHEME, WikiParser.QUOTED_STRING,
+                     WikiParser.QUOTED_STRING, WikiParser.SHREF_TARGET_FIRST,
                      WikiParser.SHREF_TARGET_MIDDLE, WikiParser.SHREF_TARGET_LAST),
-               lambda f, m, fm : 
-                    self._format_link(f, 'product', 
-                                      '%s:%s' % (fm.group('pid'), 
+               lambda f, m, fm :
+                    self._format_link(f, 'product',
+                                      '%s:%s' % (fm.group('pid'),
                                                  unquote_label(fm.group('ptarget'))),
                                       fm.group(0), fm))
         if self.env[ProductTicketModule] is not None:
             yield (r"(?<!\S)!?(?P<jtp>%s)-(?P<jtt>\d+)(?P<jtf>[?#]\S+)?" %
                         (IDENTIFIER,),
-                   lambda f, m, fm : 
-                        self._format_link(f, 'product', 
-                                          '%s:ticket:%s' % 
-                                                (fm.group('jtp'), 
+                   lambda f, m, fm :
+                        self._format_link(f, 'product',
+                                          '%s:ticket:%s' %
+                                                (fm.group('jtp'),
                                                  fm.group('jtt') +
-                                                 (fm.group('jtf') or '')), 
+                                                 (fm.group('jtf') or '')),
                                           m, fm))
- 
+
     def get_link_resolvers(self):
         yield ('global', self._format_link)
         yield ('product', self._format_link)
@@ -726,8 +744,8 @@ class MultiProductSystem(Component):
                 # Note: add_warning() is used intead of returning a list of
                 # error tuples, since the latter results in trac rendering
                 # errors (ticket's change.date is not populated)
-                add_warning(req, _('The user "%s" does not exist.' %
-                    ticket['owner']))
+                add_warning(req, _('The user "%s" does not exist.') %
+                    ticket['owner'])
         return []
 
 
@@ -740,7 +758,7 @@ class MultiProductSystem(Component):
         env = self.env
         if isinstance(env, ProductEnvironment):
             if (prefix is not None and env.product.prefix == prefix) \
-                    or (prefix is None and env.name == name): 
+                    or (prefix is None and env.name == name):
                 product_env = env
             env = env.parent
         try:
@@ -748,11 +766,11 @@ class MultiProductSystem(Component):
                 if prefix is not None:
                     product_env = ProductEnvironment(env, to_unicode(prefix))
                 else:
-                    product = Product.select(env, 
+                    product = Product.select(env,
                                              where={'name' : to_unicode(name)})
                     if not product:
                         raise LookupError("Missing product")
-                    product_env = ProductEnvironment(env, 
+                    product_env = ProductEnvironment(env,
                                                      to_unicode(product[0]))
         except LookupError:
             pass
@@ -769,7 +787,7 @@ class MultiProductSystem(Component):
                 params.append( ('prefix', prefix) )
             if name:
                 params.append( ('name', name) )
-            return tag.a(label, class_='missing product', 
+            return tag.a(label, class_='missing product',
                     href=env.href('products', params),
                     rel='nofollow')
         return tag.a(label, class_='missing product')
@@ -779,7 +797,7 @@ class MultiProductSystem(Component):
         expr = link.split(':', 1)
         if ns == 'product' and len(expr) == 1:
             # product:prefix form
-            return self._render_link(formatter.context, None, label, 
+            return self._render_link(formatter.context, None, label,
                                      params + fragment, expr[0])
         elif ns == 'global' or (ns == 'product' and expr[0] == ''):
             # global scope
@@ -787,8 +805,8 @@ class MultiProductSystem(Component):
             target_env = self.env.parent \
                             if isinstance(self.env, ProductEnvironment) \
                             else self.env
-            return self._make_sublink(target_env, sublink, formatter, ns, 
-                                      target, label, fullmatch, 
+            return self._make_sublink(target_env, sublink, formatter, ns,
+                                      target, label, fullmatch,
                                       extra=params + fragment)
         else:
             # product:prefix:realm:id:...
@@ -797,15 +815,15 @@ class MultiProductSystem(Component):
                 target_env = lookup_product_env(self.env, prefix)
             except LookupError:
                 return tag.a(label, class_='missing product')
-            # TODO: Check for nested product links 
-            # e.g. product:p1:product:p2:ticket:1 
+            # TODO: Check for nested product links
+            # e.g. product:p1:product:p2:ticket:1
             return self._make_sublink(target_env, sublink, formatter, ns,
                                       target, label, fullmatch,
                                       extra=params + fragment)
 
     FakePermClass = FakePerm
 
-    def _make_sublink(self, env, sublink, formatter, ns, target, label, 
+    def _make_sublink(self, env, sublink, formatter, ns, target, label,
                       fullmatch, extra=''):
         parent_match = {'ns' : ns,
                         'target' : target,
@@ -815,7 +833,7 @@ class MultiProductSystem(Component):
                         'fullmatch' : fullmatch,
                         }
 
-        # Tweak nested context to work in target product/global scope 
+        # Tweak nested context to work in target product/global scope
         subctx = formatter.context.child()
         subctx.href = resolve_product_href(to_env=env, at_env=self.env)
         try:
@@ -833,11 +851,11 @@ class MultiProductSystem(Component):
         subformatter.auto_quote = True
         ctxtag = '[%s] ' % (env.product.prefix,) \
                     if isinstance(env, ProductEnvironment) \
-                    else '<global> ' 
+                    else '<global> '
         subformatter.enhance_link = lambda link : (
-                                link(title=ctxtag + link.attrib.get('title')) 
-                                if isinstance(link, Element) 
-                                    and 'title' in link.attrib 
+                                link(title=ctxtag + link.attrib.get('title'))
+                                if isinstance(link, Element)
+                                    and 'title' in link.attrib
                                 else link)
         link = subformatter.match(sublink + extra)
         if link:
@@ -850,7 +868,7 @@ class MultiProductSystem(Component):
 
 
 PRODUCT_SYNTAX_DELIMITER = MultiProductSystem.short_syntax_delimiter
-PRODUCT_SYNTAX_DELIMITER_RE = ''.join('[%s]' % c 
+PRODUCT_SYNTAX_DELIMITER_RE = ''.join('[%s]' % c
                                       for c in PRODUCT_SYNTAX_DELIMITER)
 
 from multiproduct.env import ProductEnvironment, lookup_product_env, \

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/cache.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/cache.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/cache.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/cache.py Thu Feb 27 02:14:33 2014
@@ -6,7 +6,7 @@
 # March 13, 2013 updated by Olemis Lang
 #    Added keymap arg to build custom keys out of actual args
 # March 14, 2013 updated by Olemis Lang
-#    Keep cache consistency on user function failure 
+#    Keep cache consistency on user function failure
 
 import collections
 import functools

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/config.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/config.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/config.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/config.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,8 +17,6 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-"""Configuration objects for Bloodhound product environments"""
-
 __all__ = 'Configuration', 'Section'
 
 import os.path
@@ -25,6 +24,7 @@ import os.path
 from trac.config import Configuration, ConfigurationError, Option, \
         OrderedExtensionsOption, Section, _use_default
 from trac.resource import ResourceNotFound
+from trac.util import create_file
 from trac.util.text import to_unicode
 
 from multiproduct.model import ProductSetting
@@ -34,11 +34,14 @@ from multiproduct.perm import Multiprodu
 class Configuration(Configuration):
     """Product-aware settings repository equivalent to instances of
     `trac.config.Configuration` (and thus `ConfigParser` from the
-    Python Standard Library) but retrieving configuration values 
+    Python Standard Library) but retrieving configuration values
     from the database.
     """
+
+    CONFIG_LOCK_FILE = 'config.lock'
+
     def __init__(self, env, product, parents=None):
-        """Initialize configuration object with an instance of 
+        """Initialize configuration object with an instance of
         `trac.env.Environment` and product prefix.
 
         Optionally it is possible to inherit settings from parent
@@ -48,6 +51,11 @@ class Configuration(Configuration):
         self.env = env
         self.product = to_unicode(product)
         self._sections = {}
+        self._lastmtime = 0
+        self._lock_path = os.path.join(self.env.path, self.CONFIG_LOCK_FILE)
+        if not os.path.exists(self._lock_path):
+            create_file(self._lock_path)
+        self._orig_parents = parents
         self._setup_parents(parents)
 
     def __getitem__(self, name):
@@ -57,6 +65,10 @@ class Configuration(Configuration):
             self._sections[name] = Section(self, name)
         return self._sections[name]
 
+    def get_lock_file_mtime(self):
+        """Returns to modification time of the lock file."""
+        return os.path.getmtime(self._lock_path)
+
     def sections(self, compmgr=None, defaults=True):
         """Return a list of section names.
 
@@ -87,25 +99,40 @@ class Configuration(Configuration):
         return defaults and (section, option) in Option.registry
 
     def save(self):
-        """Nothing to do.
+        """Just touch config lock file.
 
-        Notice: Opposite to Trac's Configuration objects Bloodhound's
-        product configuration objects commit changes to the database 
+        Notice: In contrast to Trac's Configuration objects Bloodhound's
+        product configuration objects commit changes to the database
         immediately. Thus there's no much to do in this method.
         """
+        self.touch()
+        self._lastmtime = self.get_lock_file_mtime()
 
     def parse_if_needed(self, force=False):
-        """Just invalidate options cache.
+        """Invalidate options cache considering global lock timestamp.
 
         Notice: Opposite to Trac's Configuration objects Bloodhound's
-        product configuration objects commit changes to the database 
+        product configuration objects commit changes to the database
         immediately. Thus there's no much to do in this method.
         """
-        for section in self.sections():
-            self[section]._cache.clear()
+        changed = False
+        modtime = self.get_lock_file_mtime()
+        if force or modtime > self._lastmtime:
+            self._sections = {}
+            self._lastmtime = modtime
+            changed = True
+
+        if changed:
+            self._setup_parents(self._orig_parents)
+        else:
+            for parent in self.parents:
+                changed |= parent.parse_if_needed(force=force)
+
+        return changed
 
     def touch(self):
-        pass
+        if os.access(self._lock_path, os.W_OK):
+            os.utime(self._lock_path, None)
 
     def set_defaults(self, compmgr=None):
         """Retrieve all default values and store them explicitly in the
@@ -169,7 +196,7 @@ class Section(Section):
         for parent in self.config.parents:
             if parent[self.name].contains(key, defaults=False):
                 return True
-        return defaults and Option.registry.has_key((self.name, key))
+        return defaults and (self.name, key) in Option.registry
 
     __contains__ = contains
 
@@ -245,7 +272,7 @@ class Section(Section):
     def getpath(self, key, default=''):
         """Return a configuration value as an absolute path.
 
-        Relative paths are resolved relative to `conf` subfolder 
+        Relative paths are resolved relative to `conf` subfolder
         of the target global environment. This approach is consistent
         with TracIni path resolution.
 

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/core.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/core.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/core.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/core.py Thu Feb 27 02:14:33 2014
@@ -1,6 +1,5 @@
-#!/usr/bin/env python
 # -*- coding: UTF-8 -*-
-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/dbcursor.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/dbcursor.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/dbcursor.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/dbcursor.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,6 +17,7 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
+
 import trac.db.util
 from trac.util import concurrency
 
@@ -157,9 +159,9 @@ class ProductEnvContextManager(object):
 
         :param context: Inner database context (e.g. `QueryContextManager`,
                         `TransactionContextManager` )
-        :param env:     An instance of either `trac.env.Environment` or 
-                        `multiproduct.env.ProductEnvironment` used to 
-                        reduce the scope of database queries. If set 
+        :param env:     An instance of either `trac.env.Environment` or
+                        `multiproduct.env.ProductEnvironment` used to
+                        reduce the scope of database queries. If set
                         to `None` then SQL queries will not be translated,
                         which is equivalent to having direct database access.
         """
@@ -172,7 +174,7 @@ class ProductEnvContextManager(object):
         """
         return BloodhoundConnectionWrapper(self.db_context.__enter__(), self.env)
 
-    def __exit__(self, et, ev, tb): 
+    def __exit__(self, et, ev, tb):
         """Uninstall current product context by restoring the last one;
         then leave the inner database context.
         """

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/env.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/env.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/env.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/env.py Thu Feb 27 02:14:33 2014
@@ -17,11 +17,8 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-"""Bloodhound product environment and related APIs"""
-
 import os.path
 from urlparse import urlsplit
-from sqlite3 import OperationalError
 
 from trac.config import BoolOption, ConfigSection, Option
 from trac.core import Component, ComponentManager, ExtensionPoint, implements, \
@@ -293,8 +290,8 @@ class EnvironmentStub(trac.test.Environm
 
     @staticmethod
     def enable_component_in_config(env, cls):
-        """Keep track of enabled state in configuration as well 
-        during test runs. This is closer to reality than 
+        """Keep track of enabled state in configuration as well
+        during test runs. This is closer to reality than
         inherited `enable_component` method.
         """
         env.config['components'].set(env._component_name(cls), 'enabled')
@@ -309,8 +306,8 @@ class EnvironmentStub(trac.test.Environm
 
     @staticmethod
     def disable_component_in_config(env, component):
-        """Keep track of disabled state in configuration as well 
-        during test runs. This is closer to reality than 
+        """Keep track of disabled state in configuration as well
+        during test runs. This is closer to reality than
         inherited `disable_component` method.
         """
         if isinstance(component, type):
@@ -324,8 +321,7 @@ class EnvironmentStub(trac.test.Environm
             del env._rules
         except AttributeError:
             pass
-        # FIXME: Shall we ?
-        #env.config.save()
+        env.config.save()
 
     def reset_db(self, default_data=None):
         multiproduct_schema = self._multiproduct_schema_enabled
@@ -344,7 +340,7 @@ class ProductEnvironment(Component, Comp
 
     Bloodhound encapsulates access to product resources stored inside a
     Trac environment via product environments. They are compatible lightweight
-    irepresentations of top level environment. 
+    irepresentations of top level environment.
 
     Product environments contain among other things:
 
@@ -359,9 +355,18 @@ class ProductEnvironment(Component, Comp
 
     See https://issues.apache.org/bloodhound/wiki/Proposals/BEP-0003
     """
-    
+
     class __metaclass__(ComponentMeta):
 
+        def select_global_env(f):
+            """Replaces env with env.parent where appropriate"""
+            # Keep the signature of __call__ method
+            def __call__(self, env, *args, **kwargs):
+                g_env = env.parent if isinstance(env,
+                                                 ProductEnvironment) else env
+                return f(self, g_env, *args, **kwargs)
+            return __call__
+
         def product_env_keymap(args, kwds, kwd_mark):
             # Remove meta-reference to self (i.e. product env class)
             args = args[1:]
@@ -377,14 +382,15 @@ class ProductEnvironment(Component, Comp
                     kwds['product'] = product.prefix
             return default_keymap(args, kwds, kwd_mark)
 
+        @select_global_env
         @lru_cache(maxsize=100, keymap=product_env_keymap)
         def __call__(self, *args, **kwargs):
-            """Return an existing instance of there is a hit 
+            """Return an existing instance if there is a hit
             in the global LRU cache, otherwise create a new instance.
             """
             return ComponentMeta.__call__(self, *args, **kwargs)
 
-        del product_env_keymap
+        del product_env_keymap, select_global_env
 
     implements(trac.env.ISystemInfoProvider, IPermissionRequestor)
 
@@ -393,10 +399,10 @@ class ProductEnvironment(Component, Comp
 
     @property
     def product_setup_participants(self):
-            return [
-                component for component in self.setup_participants
-                if component not in self.multi_product_support_components
-            ]
+        return [
+            component for component in self.setup_participants
+            if component not in self.multi_product_support_components
+        ]
 
     components_section = ConfigSection('components',
         """This section is used to enable or disable components
@@ -413,11 +419,11 @@ class ProductEnvironment(Component, Comp
 
     _base_url = Option('trac', 'base_url', '',
         """Reference URL for the Trac deployment.
-        
+
         This is the base URL that will be used when producing
         documents that will be used outside of the web browsing
         context, like for example when inserting URLs pointing to Trac
-        resources in notification e-mails.""")
+        resources in notification e-mails.""", doc_domain='multiproduct')
 
     @property
     def base_url(self):
@@ -427,16 +433,17 @@ class ProductEnvironment(Component, Comp
         return base_url
 
     _base_url_for_redirect = BoolOption('trac', 'use_base_url_for_redirect',
-            False, 
+            False,
         """Optionally use `[trac] base_url` for redirects.
-        
+
         In some configurations, usually involving running Trac behind
         a HTTP proxy, Trac can't automatically reconstruct the URL
         that is used to access it. You may need to use this option to
         force Trac to use the `base_url` setting also for
         redirects. This introduces the obvious limitation that this
         environment will only be usable when accessible from that URL,
-        as redirects are frequently used. ''(since 0.10.5)''""")
+        as redirects are frequently used. ''(since 0.10.5)''""",
+                                        doc_domain='multiproduct')
 
     @property
     def project_name(self):
@@ -460,7 +467,8 @@ class ProductEnvironment(Component, Comp
         return self.parent.project_url
 
     project_admin = Option('project', 'admin', '',
-        """E-Mail address of the product's leader / administrator.""")
+        """E-Mail address of the product's leader / administrator.""",
+                           doc_domain='multiproduct')
 
     @property
     def project_footer(self):
@@ -470,23 +478,24 @@ class ProductEnvironment(Component, Comp
         return self.parent.project_footer
 
     project_icon = Option('project', 'icon', 'common/trac.ico',
-        """URL of the icon of the product.""")
+        """URL of the icon of the product.""", doc_domain='multiproduct')
 
     log_type = Option('logging', 'log_type', 'inherit',
         """Logging facility to use.
 
-        Should be one of (`inherit`, `none`, `file`, `stderr`, 
-        `syslog`, `winlog`).""")
+        Should be one of (`inherit`, `none`, `file`, `stderr`,
+        `syslog`, `winlog`).""", doc_domain='multiproduct')
 
     log_file = Option('logging', 'log_file', 'trac.log',
         """If `log_type` is `file`, this should be a path to the
         log-file.  Relative paths are resolved relative to the `log`
-        directory of the environment.""")
+        directory of the environment.""", doc_domain='multiproduct')
 
     log_level = Option('logging', 'log_level', 'DEBUG',
         """Level of verbosity in log.
 
-        Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""")
+        Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""",
+                       doc_domain='multiproduct')
 
     log_format = Option('logging', 'log_format', None,
         """Custom logging format.
@@ -509,7 +518,7 @@ class ProductEnvironment(Component, Comp
         Example:
         `($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s`
 
-        ''(since 0.10.5)''""")
+        ''(since 0.10.5)''""", doc_domain='multiproduct')
 
     def __init__(self, env, product, create=False):
         """Initialize the product environment.
@@ -522,7 +531,7 @@ class ProductEnvironment(Component, Comp
             cls = self.__class__
             raise TypeError("Initializer must be called with " \
                 "trac.env.Environment instance as first argument " \
-                "(got %s instance instead)" % 
+                "(got %s instance instead)" %
                          (cls.__module__ + '.' + cls.__name__, ))
 
         ComponentManager.__init__(self)
@@ -579,14 +588,14 @@ class ProductEnvironment(Component, Comp
                     (self.__class__.__name__, attrnm))
 
     def __repr__(self):
-        return "<%s %s at %s>" % (self.__class__.__name__, 
+        return "<%s %s at %s>" % (self.__class__.__name__,
                                  repr(self.product.prefix),
                                  hex(id(self)))
 
     @lazy
     def path(self):
-        """The subfolder `./products/<product prefix>` relative to the 
-        top-level directory of the global environment will be the root of 
+        """The subfolder `./products/<product prefix>` relative to the
+        top-level directory of the global environment will be the root of
         product file system area.
         """
         folder = os.path.join(self.parent.path, 'products', self.product.prefix)
@@ -625,8 +634,27 @@ class ProductEnvironment(Component, Comp
 
     is_component_enabled_local = trac.env.Environment.is_component_enabled.im_func
 
+    def is_enabled(self, cls):
+        """Return whether the given component class is enabled."""
+        modtime = max(self.config.get_lock_file_mtime(),
+                      self.config._lastmtime)
+        if modtime > self._config_mtime:
+            self.enabled.clear()
+            try:
+                del self._rules
+            except AttributeError:
+                pass
+            # FIXME : Improve cache hits by tracking global env last mtime
+            self.parent.enabled.clear()
+            try:
+                del self.parent._rules
+            except AttributeError:
+                pass
+        self._config_mtime = modtime
+        return super(ProductEnvironment, self).is_enabled(cls)
+
     def is_component_enabled(self, cls):
-        """Implemented to only allow activation of components already 
+        """Implemented to only allow activation of components already
         activated in the global environment that are in turn not disabled in
         the configuration.
 
@@ -637,11 +665,11 @@ class ProductEnvironment(Component, Comp
         the `plugins` directory of the environment.
         """
         if cls is self.__class__:
-            # Prevent lookups in parent env ... will always fail 
+            # Prevent lookups in parent env ... will always fail
             return True
-        # FIXME : Maybe checking for ComponentManager is too drastic 
+        # FIXME : Maybe checking for ComponentManager is too drastic
         elif issubclass(cls, ComponentManager):
-            # Avoid clashes with overridden Environment's options 
+            # Avoid clashes with overridden Environment's options
             return False
         elif self.parent[cls] is None:
             return False
@@ -779,7 +807,7 @@ class ProductEnvironment(Component, Comp
             del self._log_handler
 
     def create(self, options=[]):
-        """Placeholder for compatibility when trying to create the basic 
+        """Placeholder for compatibility when trying to create the basic
         directory structure of the environment, etc ...
 
         This method does nothing at all.
@@ -797,6 +825,7 @@ class ProductEnvironment(Component, Comp
         else:
             parents = [self.parent.config]
         self.config = Configuration(self.parent, self.product.prefix, parents)
+        self._config_mtime = 0
         self.setup_log()
 
     def setup_log(self):
@@ -806,18 +835,18 @@ class ProductEnvironment(Component, Comp
         logfile = self.log_file
         format = self.log_format
 
-        self.parent.log.debug("Log type '%s' for product '%s'", 
+        self.parent.log.debug("Log type '%s' for product '%s'",
                 logtype, self.product.prefix)
 
         # Force logger inheritance on identical configuration
-        if (logtype, logfile, format) == (self.parent.log_type, 
+        if (logtype, logfile, format) == (self.parent.log_type,
                 self.parent.log_file, self.parent.log_format):
             logtype = 'inherit'
 
         if logtype == 'inherit':
             self.log = self.parent.log
             self._log_handler = self.parent._log_handler
-            self.parent.log.warning("Inheriting parent logger for product '%s'",
+            self.parent.log.info("Inheriting parent logger for product '%s'",
                     self.product.prefix)
         else:
             if logtype == 'file' and not os.path.isabs(logfile):
@@ -833,8 +862,8 @@ class ProductEnvironment(Component, Comp
                 logtype, logfile, self.log_level, logid, format=format)
 
         from trac import core, __version__ as VERSION
-        self.log.info('-' * 32 + 
-                        ' product %s environment startup [Trac %s] ' + 
+        self.log.info('-' * 32 +
+                        ' product %s environment startup [Trac %s] ' +
                         '-' * 32,
                       self.product.prefix,
                       get_pkginfo(core).get('version', VERSION))
@@ -899,7 +928,7 @@ class ProductEnvironment(Component, Comp
     def lookup_env(cls, env, prefix=None, name=None):
         """Instantiate environment according to product prefix or name
 
-        @throws LookupError if no product matches neither prefix nor name 
+        @throws LookupError if no product matches neither prefix nor name
         """
         if isinstance(env, ProductEnvironment):
             global_env = env.parent
@@ -930,11 +959,11 @@ class ProductEnvironment(Component, Comp
 
     @classmethod
     def resolve_href(cls, to_env, at_env):
-        """Choose absolute or relative href when generating links to 
+        """Choose absolute or relative href when generating links to
         a product (or global) environment.
 
-        @param at_env:        href expansion is taking place in the 
-                              scope of this environment 
+        @param at_env:        href expansion is taking place in the
+                              scope of this environment
         @param to_env:        generated URLs point to resources in
                               this environment
         """

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/hooks.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/hooks.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/hooks.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/hooks.py Thu Feb 27 02:14:33 2014
@@ -1,3 +1,5 @@
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -37,13 +39,14 @@ class MultiProductEnvironmentFactory(Env
     def open_environment(self, environ, env_path, global_env, use_cache=False):
         environ.setdefault('SCRIPT_NAME', '')  # bh:ticket:594
 
-        env = pid = None
+        env = pid = product_path = None
         path_info = environ.get('PATH_INFO')
         if not path_info:
             return env
         m = PRODUCT_RE.match(path_info)
         if m:
             pid = m.group('pid')
+            product_path = m.group('pathinfo') or ''
 
         def create_product_env(product_prefix, script_name, path_info):
             if not global_env._abs_href:
@@ -64,11 +67,12 @@ class MultiProductEnvironmentFactory(Env
                 environ['PATH_INFO'] = path_info
             return env
 
-        if pid:
+        if pid and not (product_path in ('', '/') and
+                        environ.get('QUERY_STRING')):
             env = create_product_env(pid,
                                      environ['SCRIPT_NAME'] + '/products/' +
-                                     pid,
-                                     m.group('pathinfo') or '')
+                                     pid, product_path)
+            env.config.parse_if_needed()
 
         return env
 

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/model.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/model.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/model.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/model.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,7 +17,6 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-"""Models to support multi-product"""
 from datetime import datetime
 from itertools import izip
 
@@ -74,8 +74,12 @@ class Product(ModelBase):
         now = datetime.now(utc)
         comment = 'Product %s renamed to %s' % (old_name, new_name)
         if old_name != new_name:
+            env = self._env.parent or self._env
+            if self._data['prefix']:
+                from multiproduct.env import ProductEnvironment
+                env = ProductEnvironment(env, self._data['prefix'])
             for t in Product.get_tickets(self._env, self._data['prefix']):
-                ticket = Ticket(self._env, t['id'], db)
+                ticket = Ticket(env, t['id'], db)
                 ticket.save_changes(author, comment, now)
 
     @classmethod
@@ -145,6 +149,6 @@ class ProductSetting(ModelBase):
         """Retrieve configuration sections defined for a product
         """
         # FIXME: Maybe something more ORM-ish should be added in ModelBase
-        return [row[0] for row in env.db_query("""SELECT DISTINCT section 
-                FROM bloodhound_productconfig WHERE product = %s""", 
+        return [row[0] for row in env.db_query("""SELECT DISTINCT section
+                FROM bloodhound_productconfig WHERE product = %s""",
                 (product,))]

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/perm.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/perm.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/perm.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/perm.py Thu Feb 27 02:14:33 2014
@@ -1,5 +1,5 @@
-from functools import wraps
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -19,11 +19,13 @@ from functools import wraps
 
 """Permission components for Bloodhound product environments"""
 
-__all__ = 'ProductPermissionPolicy',
+from functools import wraps
 
 from trac.core import Component, implements
 from trac.perm import IPermissionPolicy, PermissionSystem, PermissionError
 
+__all__ = 'ProductPermissionPolicy',
+
 #--------------------------
 # Permission components
 #--------------------------
@@ -61,7 +63,7 @@ class SudoPermissionContext(object):
     """Allows a permitted user (by default `PRODUCT_ADMIN`) to execute
     a command as if temporarily granted with `TRAC_ADMIN` or other specific
     permission. There is also support to revoke some actions unconditionally.
-    
+
     These objects will act as context managers wrapping the permissions cache
     of the target request object. Entering the same context more than once
     is not supported and will result in unexpected behavior.
@@ -81,8 +83,8 @@ class SudoPermissionContext(object):
             self._expanded = False
         self._perm = None
         self.req = req
-        self.require_actions = frozenset(('PRODUCT_ADMIN',) if require is None 
-                                         else ([require] 
+        self.require_actions = frozenset(('PRODUCT_ADMIN',) if require is None
+                                         else ([require]
                                                if isinstance(require, basestring)
                                                else require))
 
@@ -170,8 +172,8 @@ class SudoPermissionContext(object):
     def has_permission(self, action, realm_or_resource=None, id=False,
                        version=False):
         return action in self.grant or \
-               (action not in self.revoke and 
-                self.perm.has_permission(action, realm_or_resource, id, 
+               (action not in self.revoke and
+                self.perm.has_permission(action, realm_or_resource, id,
                                          version))
 
     __contains__ = has_permission

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/product_admin.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/product_admin.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/product_admin.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/product_admin.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,8 +17,6 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-"""Admin panels for product management"""
-
 from trac.admin.api import IAdminCommandProvider, AdminCommandError,\
     AdminCommandManager
 from trac.admin.console import TracAdmin, TRAC_VERSION
@@ -27,20 +26,20 @@ from trac.config import *
 from trac.perm import PermissionSystem
 from trac.resource import ResourceNotFound
 from trac.ticket.admin import TicketAdminPanel, _save_config
-from trac.util import lazy
+from trac.util import getuser, lazy
 from trac.util.text import print_table, to_unicode, printerr, printout
-from trac.util.translation import _, N_, gettext, ngettext
 from trac.web.api import HTTPNotFound, IRequestFilter, IRequestHandler
 from trac.web.chrome import Chrome, add_notice, add_warning
 
 from multiproduct.env import ProductEnvironment
 from multiproduct.model import Product
 from multiproduct.perm import sudo
+from multiproduct.util import ReplacementComponent
+from multiproduct.util.translation import _, N_, gettext, ngettext
 
 import multiproduct.versioncontrol
 import trac.versioncontrol.admin
 from trac.versioncontrol import DbRepositoryProvider, RepositoryManager
-from multiproduct.util import ReplacementComponent
 
 #--------------------------
 # Product admin panel
@@ -50,8 +49,8 @@ class ProductAdminPanel(TicketAdminPanel
     """The Product Admin Panel"""
     _type = 'products'
     _label = ('Product','Products')
-    
-    def get_admin_commands(self): 
+
+    def get_admin_commands(self):
         if not isinstance(self.env, ProductEnvironment):
             yield ('product add', '<prefix> <owner> <name>',
                    'Add a new product',
@@ -73,10 +72,10 @@ class ProductAdminPanel(TicketAdminPanel
         if isinstance(req.perm.env, ProductEnvironment):
             return None
         return super(ProductAdminPanel, self).get_admin_panels(req)
-    
+
     def _render_admin_panel(self, req, cat, page, product):
         req.perm.require('PRODUCT_VIEW')
-        
+
         name = req.args.get('name')
         description = req.args.get('description','')
         prefix = req.args.get('prefix') if product is None else product
@@ -86,7 +85,7 @@ class ProductAdminPanel(TicketAdminPanel
                       'description':description,
                       'owner':owner,
                       }
-        
+
         # Detail view?
         if product:
             prod = Product(self.env, keys)
@@ -94,12 +93,12 @@ class ProductAdminPanel(TicketAdminPanel
                 if req.args.get('save'):
                     req.perm.require('PRODUCT_MODIFY')
                     prod.update_field_dict(field_data)
-                    prod.update()
+                    prod.update(req.authname)
                     add_notice(req, _('Your changes have been saved.'))
                     req.redirect(req.href.admin(cat, page))
                 elif req.args.get('cancel'):
                     req.redirect(req.href.admin(cat, page))
-            
+
             Chrome(self.env).add_wiki_toolbars(req)
             data = {'view': 'detail', 'product': prod}
         else:
@@ -127,11 +126,11 @@ class ProductAdminPanel(TicketAdminPanel
                             raise TracError(_('Invalid product id.'))
                         raise TracError(_("Product %(id)s already exists.",
                                           id=prefix))
-                
+
                 # Remove product
                 elif req.args.get('remove'):
                     raise TracError(_('Product removal is not allowed!'))
-                
+
                 # Set default product
                 elif req.args.get('apply'):
                     prefix = req.args.get('default')
@@ -142,7 +141,14 @@ class ProductAdminPanel(TicketAdminPanel
                                         prefix)
                         _save_config(self.config, req, self.log)
                         req.redirect(req.href.admin(cat, page))
-            
+
+                # Clear default product
+                elif req.args.get('clear'):
+                    self.log.info("Clearing default product")
+                    self.config.set('ticket', 'default_product', '')
+                    _save_config(self.config, req, self.log)
+                    req.redirect(req.href.admin(cat, page))
+
             products = Product.select(self.env)
             data = {'view': 'list',
                     'products': products,
@@ -181,7 +187,7 @@ class ProductAdminPanel(TicketAdminPanel
     def _do_product_chown(self, prefix, owner):
         product = self.load_product(prefix)
         product._data['owner'] = owner
-        product.update()
+        product.update(getuser())
 
     def _do_product_list(self):
         if not isinstance(self.env, ProductEnvironment):
@@ -195,7 +201,7 @@ class ProductAdminPanel(TicketAdminPanel
     def _do_product_rename(self, prefix, newname):
         product = self.load_product(prefix)
         product._data['name'] = newname
-        product.update()
+        product.update(getuser())
 
 
 #--------------------------
@@ -205,8 +211,8 @@ class ProductAdminPanel(TicketAdminPanel
 class IProductAdminAclContributor(Interface):
     """Interface implemented by components contributing with entries to the
     access control white list in order to enable admin panels in product
-    context. 
-    
+    context.
+
     **Notice** that deny entries configured by users in the blacklist
     (i.e. using TracIni `admin_blacklist` option in `multiproduct` section)
     will override these entries.
@@ -227,7 +233,7 @@ class ProductAdminModule(Component):
 
     acl_contributors = ExtensionPoint(IProductAdminAclContributor)
 
-    raw_blacklist = ListOption('multiproduct', 'admin_blacklist', 
+    raw_blacklist = ListOption('multiproduct', 'admin_blacklist',
         doc="""Do not show any product admin panels in this list even if
         allowed by white list. Value must be a comma-separated list of
         `cat:id` strings respectively identifying the section and identifier
@@ -253,7 +259,7 @@ class ProductAdminModule(Component):
                     else:
                         self.log.warning('Invalid panel %s in white list',
                                          panel_id)
-    
+
             # Blacklist entries will override those in white list
             warnings = []
             for panelref in self.raw_blacklist:
@@ -295,7 +301,7 @@ class ProductAdminModule(Component):
             mgr = self.product_admincmd_mgr(args[0])
             return mgr.complete_command(args[1:])
 
-    GLOBAL_COMMANDS = ('deploy', 'help', 'hotcopy', 'initenv', 'upgrade')
+    GLOBAL_COMMANDS = ('deploy', 'hotcopy', 'initenv', 'upgrade')
 
     def _do_product_admin(self, prefix, *args):
         mgr = self.product_admincmd_mgr(prefix)
@@ -326,7 +332,15 @@ class ProductAdminModule(Component):
                 env = mgr.env
                 TracAdmin.print_doc(TracAdmin.all_docs(env), short=True)
         else:
-            mgr.execute_command(*args)
+            try:
+                mgr.execute_command(*args)
+            except AdminCommandError, e:
+                printerr(_("Error: %(msg)s", msg=to_unicode(e)))
+                if e.show_usage:
+                    print
+                    self._do_product_admin(prefix, 'help', *args[:2])
+            except:
+                raise
 
     # IRequestFilter methods
     def pre_process_request(self, req, handler):
@@ -457,5 +471,3 @@ class ProductRepositoryAdminPanel(Replac
         return 'repository_links.html', data
 
 trac.versioncontrol.admin.RepositoryAdminPanel = ProductRepositoryAdminPanel
-
-

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/admin_products.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/admin_products.html?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/admin_products.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/admin_products.html Thu Feb 27 02:14:33 2014
@@ -1,4 +1,4 @@
-<!--
+<!--!
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
   distributed with this work for additional information
@@ -23,7 +23,8 @@
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:xi="http://www.w3.org/2001/XInclude"
       xmlns:py="http://genshi.edgewall.org/"
-      xmlns:i18n="http://genshi.edgewall.org/i18n">
+      xmlns:i18n="http://genshi.edgewall.org/i18n"
+      i18n:domain="multiproduct">
   <xi:include href="admin.html" />
   <head>
     <title>Products</title>
@@ -123,6 +124,7 @@ $product.description</textarea>
             <div class="buttons">
               <input type="submit" name="remove" value="${_('Remove selected items')}" />
               <input type="submit" name="apply" value="${_('Apply changes')}" />
+              <input type="submit" name="clear" value="${_('Clear default')}" />
             </div>
             <p class="help">
               You can remove all items from this list to completely hide this

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_delete.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_delete.html?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_delete.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_delete.html Thu Feb 27 02:14:33 2014
@@ -23,6 +23,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:py="http://genshi.edgewall.org/"
       xmlns:i18n="http://genshi.edgewall.org/i18n"
+      i18n:domain="multiproduct"
       xmlns:xi="http://www.w3.org/2001/XInclude">
   <xi:include href="layout.html" />
   <head>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_edit.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_edit.html?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_edit.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_edit.html Thu Feb 27 02:14:33 2014
@@ -23,6 +23,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:py="http://genshi.edgewall.org/"
       xmlns:i18n="http://genshi.edgewall.org/i18n"
+      i18n:domain="multiproduct"
       xmlns:xi="http://www.w3.org/2001/XInclude">
   <xi:include href="layout.html" />
   <head>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_list.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_list.html?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_list.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_list.html Thu Feb 27 02:14:33 2014
@@ -23,6 +23,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:py="http://genshi.edgewall.org/"
       xmlns:i18n="http://genshi.edgewall.org/i18n"
+      i18n:domain="multiproduct"
       xmlns:xi="http://www.w3.org/2001/XInclude">
   <xi:include href="layout.html" />
   <head>
@@ -47,9 +48,9 @@
       </div>
 
       <div py:if="'PRODUCT_CREATE' in perm" class="btn-group span8">
-        <form method="get" action="${href.products()}">
+        <form name="new" method="get" action="${href.products()}">
           <input type="hidden" name="action" value="new" />
-          <input class="btn" type="submit" value="${_('Add new product')}" />
+          <input id="add" class="btn" type="submit" value="${_('Add new product')}" />
         </form>
       </div>
     </div>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_view.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_view.html?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_view.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/product_view.html Thu Feb 27 02:14:33 2014
@@ -23,6 +23,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:py="http://genshi.edgewall.org/"
       xmlns:i18n="http://genshi.edgewall.org/i18n"
+      i18n:domain="multiproduct"
       xmlns:xi="http://www.w3.org/2001/XInclude">
   <xi:include href="layout.html" />
   <head>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/repository_links.html
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/repository_links.html?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/repository_links.html (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/templates/repository_links.html Thu Feb 27 02:14:33 2014
@@ -1,4 +1,4 @@
-<!--
+<!--!
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
   distributed with this work for additional information
@@ -23,7 +23,8 @@
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:xi="http://www.w3.org/2001/XInclude"
       xmlns:py="http://genshi.edgewall.org/"
-      xmlns:i18n="http://genshi.edgewall.org/i18n">
+      xmlns:i18n="http://genshi.edgewall.org/i18n"
+      i18n:domain="multiproduct">
   <xi:include href="admin.html" />
   <head>
     <title>Repository Links</title>

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/__init__.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/__init__.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/__init__.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/__init__.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,5 +17,4 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-"""multiproduct ticket related functionality and overrides"""
 from multiproduct.ticket.web_ui import ProductTicketModule, ProductReportModule

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/batch.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/batch.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/batch.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/batch.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -17,16 +18,17 @@
 #  under the License.
 
 from trac.ticket.batch import BatchModifyModule
-from trac.util.translation import _
 from trac.web.chrome import add_script_data
 from multiproduct.env import ProductEnvironment
+from multiproduct.util.translation import _
 
 
 class ProductBatchModifyModule(BatchModifyModule):
+
     def add_template_data(self, req, data, tickets):
         if isinstance(self.env, ProductEnvironment):
-            super(ProductBatchModifyModule, self).add_template_data(
-                req, data, tickets)
+            super(ProductBatchModifyModule, self).add_template_data(req, data,
+                                                                    tickets)
             return
 
         data['batch_modify'] = True
@@ -39,12 +41,13 @@ class ProductBatchModifyModule(BatchModi
         data['action_controls'] = []
         global_env = ProductEnvironment.lookup_global_env(self.env)
         cache = {}
-        for k,v in tickets_by_product.iteritems():
-            batchmdl = cache.get(k or '')
-            if batchmdl is None:
+        for k, v in tickets_by_product.iteritems():
+            batch_module = cache.get(k or '')
+            if batch_module is None:
                 env = ProductEnvironment(global_env, k) if k else global_env
-                cache[k] = batchmdl = ProductBatchModifyModule(env)
-            data['action_controls'] += batchmdl._get_action_controls(req, v)
+                cache[k] = batch_module = ProductBatchModifyModule(env)
+            data['action_controls'] += batch_module._get_action_controls(req,
+                                                                         v)
         batch_list_modes = [
             {'name': _("add"), 'value': "+"},
             {'name': _("remove"), 'value': "-"},
@@ -52,7 +55,7 @@ class ProductBatchModifyModule(BatchModi
             {'name': _("set to"), 'value': "="},
         ]
         add_script_data(req, batch_list_modes=batch_list_modes,
-                             batch_list_properties=self._get_list_fields())
+                        batch_list_properties=self._get_list_fields())
 
 import trac.ticket.batch
 trac.ticket.batch.BatchModifyModule = ProductBatchModifyModule

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/query.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/query.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/query.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/query.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,8 +17,6 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-""" Multi product support for ticket queries."""
-
 from __future__ import with_statement
 
 import re
@@ -35,7 +34,6 @@ from trac.ticket.api import TicketSystem
 from trac.ticket.query import Query, QueryModule, TicketQueryMacro, QueryValueError
 from trac.util.datefmt import from_utimestamp, utc, to_timestamp
 from trac.util.text import shorten_line
-from trac.util.translation import _, tag_
 from trac.web import parse_arg_list, arg_list_to_args
 from trac.web.chrome import Chrome, add_stylesheet, add_link, web_context, \
     add_script_data, add_script, add_ctxtnav, add_warning
@@ -43,7 +41,8 @@ from trac.resource import Resource
 
 from multiproduct.dbcursor import GLOBAL_PRODUCT
 from multiproduct.env import lookup_product_env, resolve_product_href, \
-    ProductEnvironment
+                             ProductEnvironment
+from multiproduct.util.translation import _, tag_
 
 
 class ProductQuery(Query):

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/web_ui.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/web_ui.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/web_ui.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/ticket/web_ui.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,8 +17,6 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-""" Multi product support for tickets."""
-
 import re
 
 from genshi.builder import tag
@@ -31,18 +30,18 @@ from trac.ticket.api import TicketSystem
 from trac.resource import Resource, get_resource_shortname, ResourceNotFound
 from trac.search import search_to_sql, shorten_result
 from trac.util.datefmt import from_utimestamp
-from trac.util.translation import _, tag_
-from trac.wiki.parser import WikiParser
 
 from multiproduct.api import MultiProductSystem, PRODUCT_SYNTAX_DELIMITER_RE
-from multiproduct.env import lookup_product_env, ProductEnvironment
-from multiproduct.util import IDENTIFIER
+from multiproduct.env import ProductEnvironment
 from multiproduct.model import Product
+from multiproduct.util import IDENTIFIER
+from multiproduct.util.translation import _, tag_
 from multiproduct.web_ui import ProductModule
 
+
 class ProductTicketModule(TicketModule):
     """Product Overrides for the TicketModule"""
-    
+
     # IRequestHandler methods
     #def match_request(self, req):
     # override not yet required
@@ -50,13 +49,21 @@ class ProductTicketModule(TicketModule):
     def process_request(self, req):
         """Override for TicketModule process_request"""
         ticketid = req.args.get('id')
-        productid = req.args.get('productid','')
+        productid = req.args.get('productid', '')
+
         if not ticketid:
             # if /newticket is executed in global scope (from QCT), redirect
             # the request to /products/<first_product_in_DB>/newticket
-            if not productid and not isinstance(self.env, ProductEnvironment):
+            if not productid and \
+                    not isinstance(self.env, ProductEnvironment):
+                default_product = self.env.config.get('ticket',
+                                                      'default_product')
                 products = Product.select(self.env, {'fields': ['prefix']})
-                req.redirect(req.href.products(products[0].prefix, 'newticket'))
+                prefixes = [prod.prefix for prod in products]
+                if not default_product or default_product not in prefixes:
+                    default_product = products[0].prefix
+                req.redirect(req.href.products(default_product, 'newticket'))
+
             return self._process_newticket_request(req)
 
         if req.path_info in ('/newticket', '/products'):
@@ -73,27 +80,27 @@ class ProductTicketModule(TicketModule):
         # executed in global scope -> assume ticketid=UID, redirect to product
         with self.env.db_direct_query as db:
             rows = db("""SELECT id,product FROM ticket WHERE uid=%s""",
-                    (ticketid,))
+                      (ticketid,))
             if not rows:
                 msg = "Ticket with uid %(uid)s does not exist."
                 raise ResourceNotFound(_(msg, uid=ticketid),
-                        _("Invalid ticket number"))
+                                       _("Invalid ticket number"))
             tid, prefix = rows[0]
             req.redirect(req.href.products(prefix, 'ticket', tid))
 
     # INavigationContributor methods
-    
+
     #def get_active_navigation_item(self, req):
     # override not yet required
 
     def get_navigation_items(self, req):
         """Overriding TicketModules New Ticket nav item"""
         return
-    
+
     # ISearchSource methods
     #def get_search_filters(self, req):
     # override not yet required
-    
+
     def get_search_results(self, req, terms, filters):
         """Overriding search results for Tickets"""
         if not 'ticket' in filters:
@@ -101,7 +108,7 @@ class ProductTicketModule(TicketModule):
         ticket_realm = Resource('ticket')
         with self.env.db_query as db:
             sql, args = search_to_sql(db, ['summary', 'keywords',
-                                           'description', 'reporter', 'cc', 
+                                           'description', 'reporter', 'cc',
                                            db.cast('id', 'text')], terms)
             sql2, args2 = search_to_sql(db, ['newvalue'], terms)
             sql3, args3 = search_to_sql(db, ['value'], terms)
@@ -110,10 +117,10 @@ class ProductTicketModule(TicketModule):
                 productsql = "product='%s' AND" % req.args.get('product')
             else:
                 productsql = ""
-            
+
             for summary, desc, author, type, tid, ts, status, resolution in \
                     db("""SELECT summary, description, reporter, type, id,
-                                 time, status, resolution 
+                                 time, status, resolution
                           FROM ticket
                           WHERE (%s id IN (
                               SELECT id FROM ticket WHERE %s
@@ -136,12 +143,13 @@ class ProductTicketModule(TicketModule):
                                     summary, status, resolution, type)),
                            from_utimestamp(ts), author,
                            shorten_result(desc, terms))
-        
+
         # Attachments
-        for result in AttachmentModule(self.env).get_search_results(
-            req, ticket_realm, terms):
+        for result in AttachmentModule(self.env) \
+                      .get_search_results(req, ticket_realm, terms):
             yield result
 
+
 class ProductReportModule(ReportModule):
     """Multiproduct replacement for ReportModule"""
 
@@ -162,9 +170,9 @@ class ProductReportModule(ReportModule):
         # FIXME: yield from
         for s in super(ProductReportModule, self).get_wiki_syntax():
             yield s
-        # Previously unmatched prefix 
-        yield (r"!?\{(?P<prp>%s(?:\s+|(?:%s)))[0-9]+\}" % 
-                    (IDENTIFIER, PRODUCT_SYNTAX_DELIMITER_RE),
+        # Previously unmatched prefix
+        yield (r"!?\{(?P<prp>%s(?:\s+|(?:%s)))[0-9]+\}"
+               % (IDENTIFIER, PRODUCT_SYNTAX_DELIMITER_RE),
                lambda x, y, z: self._format_link(x, 'report', y[1:-1], y, z))
         # Absolute product report syntax
         yield (r"!?\{(?P<prns>global:|product:%s(?:\s+|:))"
@@ -172,8 +180,8 @@ class ProductReportModule(ReportModule):
                lambda x, y, z: (self._format_mplink(x, 'report', y[1:-1], y, z)))
 
     def _format_link(self, formatter, ns, target, label, fullmatch=None):
-        intertrac = formatter.shorthand_intertrac_helper(ns, target, label,
-                                                         fullmatch)
+        intertrac = \
+            formatter.shorthand_intertrac_helper(ns, target, label, fullmatch)
         if intertrac:
             return intertrac
 
@@ -194,7 +202,7 @@ class ProductReportModule(ReportModule):
             if not prns:
                 # Forwarded from _format_link, inherit current context
                 product_id = fullmatch.group('it_' + ns) or \
-                             fullmatch.group('prp') 
+                             fullmatch.group('prp')
                 if product_id:
                     product_ns = 'product'
                     substeps = [product_id.strip()]
@@ -211,8 +219,8 @@ class ProductReportModule(ReportModule):
             report_id = fullmatch.group('prid') or \
                         re.match(r'^.*?(\d+)$', target).group(1)
             substeps += [ns, report_id]
-            
-            return mpsys._format_link(formatter, product_ns, 
+
+            return mpsys._format_link(formatter, product_ns,
                                       u':'.join(substeps),
                                       label, fullmatch)
         else:

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/versioncontrol.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/versioncontrol.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/versioncontrol.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/versioncontrol.py Thu Feb 27 02:14:33 2014
@@ -1,3 +1,5 @@
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -15,8 +17,6 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-"""Bloodhound version control support"""
-
 import os.path
 
 from trac.util.concurrency import threading
@@ -108,4 +108,3 @@ class DbRepositoryProvider(ReplacementCo
 
 trac.versioncontrol.api.DbRepositoryProvider = DbRepositoryProvider
 trac.versioncontrol.DbRepositoryProvider = DbRepositoryProvider
-

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/web_ui.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/web_ui.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/web_ui.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/multiproduct/web_ui.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,5 @@
-
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,16 +17,10 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-"""ProductModule
-
-Provides request filtering to capture product related paths
-"""
-
 import re
 
 from trac.core import Component, TracError, implements
 from trac.resource import Neighborhood, Resource, ResourceNotFound
-from trac.util.translation import _
 from trac.web.api import HTTPNotFound, IRequestHandler, IRequestFilter
 from trac.web.chrome import (
     Chrome, INavigationContributor, add_link, add_notice, add_warning,
@@ -36,12 +31,12 @@ from multiproduct.env import resolve_pro
 from multiproduct.hooks import PRODUCT_RE
 from multiproduct.model import Product
 from multiproduct.env import ProductEnvironment
-
+from multiproduct.util.translation import _
 
 # requests to the following URLs will be skipped in the global scope
 # (no more redirection to default product)
 IGNORED_REQUESTS_RE = \
-    re.compile(r'^/(?P<section>milestone|roadmap|diff|search|'
+    re.compile(r'^/(?P<section>milestone|roadmap|search|'
                r'(raw-|zip-)?attachment/(ticket|milestone))(?P<pathinfo>.*)')
 
 class ProductModule(Component):
@@ -53,7 +48,7 @@ class ProductModule(Component):
     def pre_process_request(self, req, handler):
         if not isinstance(self.env, ProductEnvironment) and \
            IGNORED_REQUESTS_RE.match(req.path_info):
-           return None
+            return None
         return handler
 
     def post_process_request(req, template, data, content_type):
@@ -172,7 +167,7 @@ class ProductModule(Component):
             else:
                 req.perm.require('PRODUCT_MODIFY')
                 product.update_field_dict(field_data)
-                product.update()
+                product.update(req.authname)
                 add_notice(req, _('Your changes have been saved.'))
         else:
             req.perm.require('PRODUCT_CREATE')

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.cfg
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.cfg?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.cfg (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.cfg Thu Feb 27 02:14:33 2014
@@ -23,3 +23,24 @@ tag_svn_revision = true
 [sdist]
 formats = gztar,bztar,ztar,tar,zip
 
+[extract_messages]
+add_comments = TRANSLATOR:
+copyright_holder = Apache Software Foundation
+msgid_bugs_address = dev@bloodhound.apache.org
+output_file = multiproduct/locale/messages.pot
+keywords = _ ngettext:1,2 N_ tag_ tagn_:1,2 Option:4 BoolOption:4 IntOption:4 ListOption:6 ExtensionOption:5 PathOption:4
+no-wrap = true
+
+[init_catalog]
+input_file = multiproduct/locale/messages.pot
+output_dir = multiproduct/locale
+domain = multiproduct
+
+[compile_catalog]
+directory = multiproduct/locale
+domain = multiproduct
+
+[update_catalog]
+input_file = multiproduct/locale/messages.pot
+output_dir = multiproduct/locale
+domain = multiproduct

Modified: bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.py
URL: http://svn.apache.org/viewvc/bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.py?rev=1572404&r1=1572403&r2=1572404&view=diff
==============================================================================
--- bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.py (original)
+++ bloodhound/branches/bep_0007_embeddable_objects/bloodhound_multiproduct/setup.py Thu Feb 27 02:14:33 2014
@@ -1,4 +1,6 @@
-
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+#
 #  Licensed to the Apache Software Foundation (ASF) under one
 #  or more contributor license agreements.  See the NOTICE file
 #  distributed with this work for additional information
@@ -16,7 +18,6 @@
 #  specific language governing permissions and limitations
 #  under the License.
 
-"""setup for multi product plugin"""
 import sys, codecs
 from pkg_resources import parse_version
 from setuptools import setup
@@ -33,6 +34,25 @@ if __name__ == '__main__':
             del sys.argv[ac]
             break
 
+extra = {}
+try:
+    from trac.util.dist import get_l10n_js_cmdclass
+    cmdclass = get_l10n_js_cmdclass()
+    if cmdclass:
+        extra['cmdclass'] = cmdclass
+        extractors = [
+            ('**.py',                'trac.dist:extract_python', None),
+            ('**/templates/**.html', 'genshi', None),
+            ('**/templates/**.txt',  'genshi', {
+                'template_class': 'genshi.template:TextTemplate'
+            }),
+        ]
+        extra['message_extractors'] = {
+            'multiproduct': extractors,
+        }
+except ImportError:
+    pass
+
 setup(
     name = 'BloodhoundMultiProduct',
     version = '0.8.0',
@@ -40,8 +60,10 @@ setup(
     author = "Apache Bloodhound",
     license = "Apache License v2",
     url = "https://bloodhound.apache.org/",
-    packages = ['multiproduct', 'multiproduct.ticket', 'tests',],
-    package_data = {'multiproduct' : ['templates/*.html',]},
+    packages = ['multiproduct', 'multiproduct.ticket', 'multiproduct.util',
+                'tests',],
+    package_data = {'multiproduct' : ['templates/*.html','locale/*/LC_MESSAGES/*.mo']},
+    install_requires = ['sqlparse'],
     entry_points = {'trac.plugins': [
             'multiproduct.model = multiproduct.model',
             'multiproduct.perm = multiproduct.perm',
@@ -52,6 +74,6 @@ setup(
             'multiproduct.web_ui = multiproduct.web_ui',
         ],},
     test_suite='tests.test_suite',
-    tests_require=['unittest2' if parse_version(sys.version) < parse_version('2.7') else '']
+    tests_require=['unittest2' if parse_version(sys.version) < parse_version('2.7') else ''],
+    **extra
 )
-