Anonymous avatar Anonymous committed a89d562 Draft

#492, product neighborhoods, patch t492_r1464573_product_neighborhoods.diff applied (from Olemis)

git-svn-id: https://svn.apache.org/repos/asf/bloodhound/trunk@1466781 13f79535-47bb-0310-9956-ffa450edef68

Comments (0)

Files changed (7)

bloodhound_multiproduct/multiproduct/api.py

 from trac.config import Option, PathOption
 from trac.core import Component, TracError, implements, Interface
 from trac.db import Table, Column, DatabaseManager, Index
-from trac.env import IEnvironmentSetupParticipant
+from trac.env import IEnvironmentSetupParticipant, Environment
 from trac.perm import IPermissionRequestor, PermissionCache
-from trac.resource import IResourceManager
+from trac.resource import IExternalResourceConnector, IResourceChangeListener,\
+                          IResourceManager, ResourceNotFound
 from trac.ticket.api import ITicketFieldProvider
 from trac.util.text import to_unicode, unquote_label, unicode_unquote
 from trac.util.translation import _, N_
 from trac.web.main import FakePerm, FakeSession
 from trac.wiki.api import IWikiSyntaxProvider
 from trac.wiki.parser import WikiParser
-from trac.resource import IResourceChangeListener
 
+from multiproduct.dbcursor import GLOBAL_PRODUCT
 from multiproduct.model import Product, ProductResourceMap, ProductSetting
 from multiproduct.util import EmbeddedLinkFormatter, IDENTIFIER
 
 class MultiProductSystem(Component):
     """Creates the database tables and template directories"""
 
-    implements(IEnvironmentSetupParticipant, ITemplateProvider,
-               IPermissionRequestor, ITicketFieldProvider, IResourceManager,
-               ISupportMultiProductEnvironment, IWikiSyntaxProvider,
-               IResourceChangeListener)
+    implements(IEnvironmentSetupParticipant, IExternalResourceConnector,
+               IPermissionRequestor, IResourceChangeListener, IResourceManager,
+               ISupportMultiProductEnvironment, ITemplateProvider, 
+               ITicketFieldProvider, IWikiSyntaxProvider)
 
     product_base_url = Option('multiproduct', 'product_base_url', '',
         """A pattern used to generate the base URL of product environments,
         products = Product.select(self.env, where={'name' : resource.id})
         return bool(products)
 
+    # IExternalResourceConnector methods
+    def get_supported_neighborhoods(self):
+        """Neighborhoods for `product` and `global` environments.
+        """
+        yield 'product'
+        yield 'global'
+
+    def load_manager(self, neighborhood):
+        """Load global environment or product environment given its prefix
+        """
+        if neighborhood._realm == 'global':
+            # FIXME: ResourceNotFound if neighborhood ID != None ?
+            prefix = GLOBAL_PRODUCT
+        elif neighborhood._realm == 'product':
+            prefix = neighborhood._id
+        else:
+            raise ResourceNotFound(_('Unsupported neighborhood %(realm)s', 
+                                     realm=neighborhood._realm))
+        try:
+            return lookup_product_env(self.env, prefix)
+        except LookupError:
+            raise ResourceNotFound(_('Unknown product prefix %(prefix)s', 
+                                     prefix=prefix))
+
+    def manager_exists(self, neighborhood):
+        """Check whether the target environment exists physically.
+        """
+        if neighborhood._realm == 'global':
+            # Global environment
+            return isinstance(self.env, (Environment, ProductEnvironment))
+        elif neighborhood._realm == 'product':
+            prefix = neighborhood._id
+            if not prefix:
+                # Global environment
+                return True
+            return Product(lookup_product_env(self.env, GLOBAL_PRODUCT), 
+                           {'prefix' : prefix})._exists
+
     # IWikiSyntaxProvider methods
 
     short_syntax_delimiter = u'->'

bloodhound_multiproduct/multiproduct/env.py

         else:
             global_env = env
 
+        # FIXME: Update if multiproduct.dbcursor.GLOBAL_PRODUCT != ''
         if not prefix and not name:
             return global_env
         elif isinstance(env, ProductEnvironment) and \

bloodhound_multiproduct/tests/resource.py

+# -*- 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
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+
+from datetime import datetime
+import os.path
+import shutil
+from StringIO import StringIO
+import tempfile
+import unittest
+
+from trac.attachment import Attachment
+from trac import resource 
+import trac.ticket.report       # report resources ?
+import trac.ticket.roadmap      # milestone resources
+import trac.ticket.api          # ticket resources
+from trac.ticket.model import Ticket
+from trac.ticket.tests.api import TicketSystemTestCase 
+from trac.util.datefmt import utc
+import trac.wiki.api            # wiki resources
+from trac.wiki.model import WikiPage
+
+from multiproduct.api import MultiProductSystem
+from multiproduct.env import ProductEnvironment
+from tests.env import MultiproductTestCase
+
+class ProductResourceTestCase(MultiproductTestCase):
+    def setUp(self):
+        self._mp_setup()
+        self.global_env = self.env
+        self._load_product_from_data(self.global_env, u'xü')
+
+        self.env = ProductEnvironment(self.global_env, self.default_product)
+        self.env1 = ProductEnvironment(self.global_env, u'xü')
+
+        self._load_default_data(self.global_env)
+        self._load_default_data(self.env1)
+
+        # Enable product system component in product context
+        self.env.enable_component(MultiProductSystem)
+
+    def tearDown(self):
+        self.global_env.reset_db()
+        self.global_env = self.env = None
+
+
+class ProductAttachmentResourceTestCase(ProductResourceTestCase):
+    def setUp(self):
+        ProductResourceTestCase.setUp(self)
+        self.global_env.path = os.path.join(tempfile.gettempdir(),
+                                            'trac-tempenv')
+        if os.path.exists(self.global_env.path):
+            shutil.rmtree(self.global_env.path)
+        os.mkdir(self.global_env.path)
+
+        attachment = Attachment(self.global_env, 'ticket', 1)
+        attachment.description = 'Global Bar'
+        attachment.insert('foo.txt', StringIO(''), 0)
+
+        attachment = Attachment(self.env1, 'ticket', 1)
+        attachment.description = 'Product Bar'
+        attachment.insert('foo.txt', StringIO(''), 0)
+        self.resource = resource.Resource('ticket', 
+                                          1).child('attachment', 'foo.txt')
+
+    def tearDown(self):
+        shutil.rmtree(self.global_env.path)
+        ProductResourceTestCase.tearDown(self)
+
+    def test_global_neighborhood_attachments(self):
+        target = resource.Neighborhood('global', None).child(self.resource)
+
+        self.assertEquals("[global:] Attachment 'foo.txt' in [global:] Ticket #1", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals("[global:] Attachment 'foo.txt' in [global:] Ticket #1", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals("[global:] foo.txt ([global:] Ticket #1)", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals('Global Bar', 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/attachment/ticket/1/foo.txt', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+    def test_product_neighborhood_attachments(self):
+        target = resource.Neighborhood('product', u'xü').child(self.resource)
+
+        self.assertEquals(u"[product:xü] Attachment 'foo.txt' in [product:xü] Ticket #1", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals(u"[product:xü] Attachment 'foo.txt' in [product:xü] Ticket #1", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals(u"[product:xü] foo.txt ([product:xü] Ticket #1)", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals('Product Bar', 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/products/x%C3%BC/attachment/ticket/1/foo.txt', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+
+class ProductMilestoneResourceTestCase(ProductResourceTestCase):
+    resource = resource.Resource('milestone', 'milestone1')
+
+    def test_global_neighborhood_milestone(self):
+        target = resource.Neighborhood('global', None).child(self.resource)
+
+        self.assertEquals("[global:] Milestone milestone1", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals("[global:] Milestone milestone1", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals("milestone1", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals("[global:] Milestone milestone1", 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/milestone/milestone1', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+    def test_product_neighborhood_milestone(self):
+        target = resource.Neighborhood('product', u'xü').child(self.resource)
+
+        self.assertEquals(u"[product:xü] Milestone milestone1", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals(u"[product:xü] Milestone milestone1", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals(u"milestone1", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals(u"[product:xü] Milestone milestone1", 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/products/x%C3%BC/milestone/milestone1', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+
+# FIXME: No resource manager for reports in core ?
+class ProductReportResourceTestCase(ProductResourceTestCase):
+    resource = resource.Resource('report', 1)
+
+    def test_global_neighborhood_report(self):
+        target = resource.Neighborhood('global', None).child(self.resource)
+
+        self.assertEquals("[global:] report:1", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals("[global:] report:1", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals("[global:] report:1", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals('[global:] report:1 at version None', 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/report/1', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+    def test_product_neighborhood_report(self):
+        target = resource.Neighborhood('product', u'xü').child(self.resource)
+
+        self.assertEquals(u"[product:xü] report:1", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals(u"[product:xü] report:1", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals(u"[product:xü] report:1", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals(u"[product:xü] report:1 at version None", 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/products/x%C3%BC/report/1', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+
+class ProductTicketResourceTestCase(ProductResourceTestCase):
+    def _new_ticket(self, env, ticket_dict):
+        ticket = Ticket(env)
+        ticket.populate(ticket_dict)
+        return ticket.insert()
+
+    def setUp(self):
+        ProductResourceTestCase.setUp(self)
+
+    def test_global_neighborhood_ticket(self):
+        nbh = resource.Neighborhood('global', None)
+        data = dict(summary='Ticket summary', description='Ticket description',
+                    type='enhancement', status='new')
+        target = nbh.child('ticket', self._new_ticket(self.global_env, data))
+
+        self.assertEquals("[global:] Ticket #1", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals("[global:] Ticket #1", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals("[global:] #1", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals('enhancement: Ticket summary (new)', 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/ticket/1', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+    def test_product_neighborhood_ticket(self):
+        nbh = resource.Neighborhood('product', u'xü')
+        data = dict(summary='Ticket summary', description='Ticket description',
+                    type='task', status='accepted')
+        target = nbh.child('ticket', self._new_ticket(self.env1, data))
+
+        self.assertEquals(u"[product:xü] Ticket #1", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals(u"[product:xü] Ticket #1", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals(u"[product:xü] #1", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals(u"task: Ticket summary (accepted)", 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/products/x%C3%BC/ticket/1', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+
+#class ProductVcsResourceTestCase(ProductResourceTestCase):
+#    def setUp(self):
+#        pass
+#
+#    def tearDown(self):
+#        pass
+#
+#    def test_global_neighborhood_versioncontrol(self):
+#        raise NotImplementedError()
+#
+#    def test_product_neighborhood_versioncontrol(self):
+#        raise NotImplementedError()
+
+
+class ProductWikiResourceTestCase(ProductResourceTestCase):
+    resource = resource.Resource('wiki', 'TestPage', version=2)
+
+    def setUp(self):
+        ProductResourceTestCase.setUp(self)
+
+        page = WikiPage(self.global_env)
+        page.name = 'TestPage'
+        page.text = 'Bla bla'
+        t = datetime(2001, 1, 1, 1, 1, 1, 0, utc)
+        page.save('joe', 'Testing global', '::1', t)
+        page.text = 'Bla bla bla'
+        t = datetime(2002, 2, 2, 2, 2, 2, 0, utc)
+        page.save('joe', 'Testing global 2', '::1', t)
+
+        page = WikiPage(self.env1)
+        page.name = 'TestPage'
+        page.text = 'alb alB'
+        t = datetime(2011, 1, 1, 1, 1, 1, 0, utc)
+        page.save('mary', 'Testing product', '::1', t)
+        page.text = 'Bla bla bla'
+        t = datetime(2012, 2, 2, 2, 2, 2, 0, utc)
+        page.save('mary', 'Testing product 2', '::1', t)
+
+    def test_global_neighborhood_wiki(self):
+        target = resource.Neighborhood('global', None).child(self.resource)
+
+        self.assertEquals("TestPage", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals("TestPage", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals("TestPage", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals("TestPage", 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/wiki/TestPage?version=2', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+    def test_product_neighborhood_wiki(self):
+        target = resource.Neighborhood('product', u'xü').child(self.resource)
+
+        self.assertEquals(u"TestPage", 
+                          resource.get_resource_description(self.env, target))
+        self.assertEquals(u"TestPage", 
+                          resource.get_resource_name(self.env, target))
+        self.assertEquals(u"TestPage", 
+                          resource.get_resource_shortname(self.env, target))
+        self.assertEquals(u"TestPage", 
+                          resource.get_resource_summary(self.env, target))
+        self.assertEquals('http://example.org/trac.cgi/products/x%C3%BC/wiki/TestPage?version=2', 
+                          resource.get_resource_url(self.env, 
+                                                    target, self.env.href))
+
+
+def test_suite():
+    return unittest.TestSuite([
+        unittest.makeSuite(ProductAttachmentResourceTestCase, 'test'),
+        unittest.makeSuite(ProductMilestoneResourceTestCase, 'test'),
+        unittest.makeSuite(ProductReportResourceTestCase, 'test'),
+        unittest.makeSuite(ProductTicketResourceTestCase, 'test'),
+#        unittest.makeSuite(ProductVcsResourceTestCase, 'test'),
+        unittest.makeSuite(ProductWikiResourceTestCase, 'test'),
+    ])
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

trac/trac/attachment.py

     def get_resource_description(self, resource, format=None, **kwargs):
         if not resource.parent:
             return _("Unparented attachment %(id)s", id=resource.id)
+        nbhprefix = ResourceSystem(self.env).neighborhood_prefix(
+                resource.neighborhood)
         if format == 'compact':
-            return '%s (%s)' % (resource.id,
+            return '%s%s (%s)' % (nbhprefix, resource.id,
                     get_resource_name(self.env, resource.parent))
         elif format == 'summary':
             return Attachment(self.env, resource).description
         if resource.id:
-            return _("Attachment '%(id)s' in %(parent)s", id=resource.id,
+            desc = _("Attachment '%(id)s' in %(parent)s", id=resource.id,
                      parent=get_resource_name(self.env, resource.parent))
         else:
-            return _("Attachments of %(parent)s",
+            desc = _("Attachments of %(parent)s",
                      parent=get_resource_name(self.env, resource.parent))
+        if resource.neighborhood is not None:
+            desc = nbhprefix + desc
+        return desc
 
     def resource_exists(self, resource):
         try:

trac/trac/resource.py

         target = rsys.load_component_manager(neighborhood, compmgr)
         return rsys if target is compmgr else (componentclass or cls)(target)
 
+    def neighborhood_prefix(self, neighborhood):
+        return '' if neighborhood is None \
+                  else '[%s:%s] ' % (neighborhood._realm,
+                                     neighborhood._id or '') 
+
     # -- Utilities to trigger resources event notifications
 
     def resource_created(self, resource, context=None):
         manager = rsys.get_resource_manager(resource.realm)
         if manager and hasattr(manager, 'get_resource_description'):
             return manager.get_resource_description(resource, format, **kwargs)
-    name = u'%s:%s' % (resource.realm, resource.id)
+    nbhprefix = rsys.neighborhood_prefix(resource.neighborhood) 
+
+    name = u'%s%s:%s' % (nbhprefix, resource.realm, resource.id)
     if format == 'summary':
         name = _('%(name)s at version %(version)s',
-                 name=name, version=resource.version) + \
-               '' if resource.neighborhood is None else _(' (external)') 
+                 name=name, version=resource.version)
     return name
 
 def get_resource_name(env, resource):

trac/trac/ticket/api.py

 from trac.config import *
 from trac.core import *
 from trac.perm import IPermissionRequestor, PermissionCache, PermissionSystem
-from trac.resource import IResourceManager
+from trac.resource import IResourceManager, ResourceSystem
 from trac.util import Ranges, as_int
 from trac.util.text import shorten_line
 from trac.util.translation import _, N_, gettext
 
     def get_resource_description(self, resource, format=None, context=None,
                                  **kwargs):
+        nbhprefix = ResourceSystem(self.env).neighborhood_prefix(
+                resource.neighborhood)
         if format == 'compact':
-            return '#%s' % resource.id
+            return '%s#%s' % (nbhprefix, resource.id)
         elif format == 'summary':
             from trac.ticket.model import Ticket
             ticket = Ticket(self.env, resource.id)
             args = [ticket[f] for f in ('summary', 'status', 'resolution',
                                         'type')]
             return self.format_summary(*args)
-        return _("Ticket #%(shortname)s", shortname=resource.id)
+        return nbhprefix + _("Ticket #%(shortname)s", shortname=resource.id)
 
     def format_summary(self, summary, status=None, resolution=None, type=None):
         summary = shorten_line(summary)

trac/trac/ticket/roadmap.py

 
     def get_resource_description(self, resource, format=None, context=None,
                                  **kwargs):
+        nbhprefix = ResourceSystem(self.env).neighborhood_prefix(
+                resource.neighborhood)
         desc = resource.id
         if format != 'compact':
-            desc =  _('Milestone %(name)s', name=resource.id)
+            desc =  nbhprefix + _('Milestone %(name)s', name=resource.id)
         if context:
             return self._render_link(context, resource.id, desc)
         else:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.