← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~mwhudson/launchpad/apoca-publish-code into lp:launchpad/devel

 

Michael Hudson has proposed merging lp:~mwhudson/launchpad/apoca-publish-code into lp:launchpad/devel with lp:~mwhudson/launchpad/code-publisher-in-lp.code as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)


Following on from lp:~mwhudson/launchpad/code-publisher-in-lp.code, this branch moves all application specific publication code into lp.$application.publisher modules.

I'm afraid I expect it's a bit of a beast to review -- a mix of a load of literally mechanical import fixups and a little bit of deep stuff.  Let me know if you want me to prepare this branch in another, easier to review, way.
-- 
https://code.launchpad.net/~mwhudson/launchpad/apoca-publish-code/+merge/30490
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~mwhudson/launchpad/apoca-publish-code into lp:launchpad/devel.
=== modified file 'lib/canonical/configure.zcml'
--- lib/canonical/configure.zcml	2010-07-21 05:53:56 +0000
+++ lib/canonical/configure.zcml	2010-07-21 05:53:58 +0000
@@ -85,14 +85,14 @@
     <browser:defaultView
         for="canonical.launchpad.interfaces.ILaunchpadRoot"
         name="specs"
-        layer="canonical.launchpad.layers.BlueprintsLayer"
+        layer="lp.blueprints.publisher.BlueprintsLayer"
         />
     <browser:page
         name=""
         for="canonical.launchpad.interfaces.ILaunchpadRoot"
         class="canonical.launchpad.browser.launchpad.LaunchpadImageFolder"
         permission="zope.Public"
-        layer="canonical.launchpad.layers.BlueprintsLayer"
+        layer="lp.blueprints.publisher.BlueprintsLayer"
         />
 
     <!-- virtual host: code -->
@@ -113,42 +113,42 @@
     <browser:defaultView
         for="canonical.launchpad.interfaces.ILaunchpadRoot"
         name="translations"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         />
     <browser:page
         name=""
         for="canonical.launchpad.interfaces.ILaunchpadRoot"
         class="canonical.launchpad.browser.launchpad.LaunchpadImageFolder"
         permission="zope.Public"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         />
 
     <!-- virtual host: bugs -->
     <browser:defaultView
         for="canonical.launchpad.interfaces.ILaunchpadRoot"
         name="bugs"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         />
     <browser:page
         name=""
         for="canonical.launchpad.interfaces.ILaunchpadRoot"
         class="canonical.launchpad.browser.launchpad.LaunchpadImageFolder"
         permission="zope.Public"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         />
 
     <!-- virtual host: answers -->
     <browser:defaultView
         for="canonical.launchpad.interfaces.ILaunchpadRoot"
         name="questions"
-        layer="canonical.launchpad.layers.AnswersLayer"
+        layer="lp.answers.publisher.AnswersLayer"
         />
     <browser:page
         name=""
         for="canonical.launchpad.interfaces.ILaunchpadRoot"
         class="canonical.launchpad.browser.launchpad.LaunchpadImageFolder"
         permission="zope.Public"
-        layer="canonical.launchpad.layers.AnswersLayer"
+        layer="lp.answers.publisher.AnswersLayer"
         />
 
     <include package="canonical.widgets" />

=== modified file 'lib/canonical/launchpad/doc/webapp-publication.txt'
--- lib/canonical/launchpad/doc/webapp-publication.txt	2010-07-21 05:53:56 +0000
+++ lib/canonical/launchpad/doc/webapp-publication.txt	2010-07-21 05:53:58 +0000
@@ -356,30 +356,14 @@
     ProtocolErrorPublication: status=405
       Allow: GET HEAD POST
 
-    >>> print_request_and_publication('answers.launchpad.dev')
-    AnswersBrowserRequest
-    AnswersPublication
-
     >>> print_request_and_publication('api.launchpad.dev')
     WebServiceClientRequest
     WebServicePublication
 
-    >>> print_request_and_publication('blueprints.launchpad.dev')
-    BlueprintBrowserRequest
-    BlueprintPublication
-
-    >>> print_request_and_publication('bugs.launchpad.dev')
-    BugsBrowserRequest
-    BugsPublication
-
     >>> print_request_and_publication('feeds.launchpad.dev')
     FeedsBrowserRequest
     FeedsPublication
 
-    >>> print_request_and_publication('translations.launchpad.dev')
-    TranslationsBrowserRequest
-    TranslationsPublication
-
 The web service RequestPublicationFactory responds to the six most
 common HTTP methods, but it will only accept a MIME type of
 application/json.
@@ -404,27 +388,15 @@
     WebServiceClientRequest
     WebServicePublication
 
-When a request for '/api' is made to one of the application virtualhost,
-it is also handled by the web service request and publication:
+When a request for '/api' is made to one of the application
+virtualhosts, such as the application root, it is also handled by the
+web service request and publication:
 
     >>> print_request_and_publication(
     ...     'launchpad.dev', method='GET',
     ...     extra_environment={'PATH_INFO': '/api'})
     WebServiceClientRequest
     WebServicePublication
-    >>> for subdomain in [
-    ...     'answers', 'blueprints', 'bugs', 'translations']:
-    ...     print_request_and_publication(
-    ...         '%s.launchpad.dev' % subdomain, method='GET',
-    ...         extra_environment={'PATH_INFO': '/api'})
-    WebServiceClientRequest
-    WebServicePublication
-    WebServiceClientRequest
-    WebServicePublication
-    WebServiceClientRequest
-    WebServicePublication
-    WebServiceClientRequest
-    WebServicePublication
 
 Requests for '/api' on other hosts like feeds are handled like
 other requests on these hosts:

=== modified file 'lib/canonical/launchpad/layers.py'
--- lib/canonical/launchpad/layers.py	2010-07-21 05:53:56 +0000
+++ lib/canonical/launchpad/layers.py	2010-07-21 05:53:58 +0000
@@ -28,23 +28,6 @@
     """The `LaunchpadLayer` layer."""
 
 
-class TranslationsLayer(LaunchpadLayer):
-    """The `TranslationsLayer` layer."""
-
-
-class BugsLayer(LaunchpadLayer):
-    """The `BugsLayer` layer."""
-
-
-class BlueprintLayer(LaunchpadLayer):
-    """The `BlueprintLayer` layer."""
-BlueprintsLayer = BlueprintLayer
-
-
-class AnswersLayer(LaunchpadLayer):
-    """The `AnswersLayer` layer."""
-
-
 class DebugLayer(Interface):
     """The `DebugLayer` layer.
 

=== modified file 'lib/canonical/launchpad/webapp/servers.py'
--- lib/canonical/launchpad/webapp/servers.py	2010-07-21 05:53:56 +0000
+++ lib/canonical/launchpad/webapp/servers.py	2010-07-21 05:53:58 +0000
@@ -1015,52 +1015,6 @@
 class MainLaunchpadPublication(LaunchpadBrowserPublication):
     """The publication used for the main Launchpad site."""
 
-# ---- blueprint
-
-class BlueprintBrowserRequest(LaunchpadBrowserRequest):
-    implements(canonical.launchpad.layers.BlueprintLayer)
-
-class BlueprintPublication(LaunchpadBrowserPublication):
-    """The publication used for the Blueprint site."""
-
-# ---- translations
-
-class TranslationsPublication(LaunchpadBrowserPublication):
-    """The publication used for the Translations site."""
-
-class TranslationsBrowserRequest(LaunchpadBrowserRequest):
-    implements(canonical.launchpad.layers.TranslationsLayer)
-
-    def __init__(self, body_instream, environ, response=None):
-        super(TranslationsBrowserRequest, self).__init__(
-            body_instream, environ, response)
-        # Some of the responses from translations vary based on language.
-        self.response.setHeader(
-            'Vary', 'Cookie, Authorization, Accept-Language')
-
-# ---- bugs
-
-class BugsPublication(LaunchpadBrowserPublication):
-    """The publication used for the Bugs site."""
-
-class BugsBrowserRequest(LaunchpadBrowserRequest):
-    implements(canonical.launchpad.layers.BugsLayer)
-
-# ---- answers
-
-class AnswersPublication(LaunchpadBrowserPublication):
-    """The publication used for the Answers site."""
-
-class AnswersBrowserRequest(LaunchpadBrowserRequest):
-    implements(canonical.launchpad.layers.AnswersLayer)
-
-    def __init__(self, body_instream, environ, response=None):
-        super(AnswersBrowserRequest, self).__init__(
-            body_instream, environ, response)
-        # Many of the responses from Answers vary based on language.
-        self.response.setHeader(
-            'Vary', 'Cookie, Authorization, Accept-Language')
-
 
 class AccountPrincipalMixin:
     """Mixin for publication that works with person-less accounts."""
@@ -1475,11 +1429,6 @@
     factories = [
         VWSHRP('mainsite', LaunchpadBrowserRequest, MainLaunchpadPublication,
                handle_default_host=True),
-        VWSHRP('blueprints', BlueprintBrowserRequest, BlueprintPublication),
-        VWSHRP('translations', TranslationsBrowserRequest,
-               TranslationsPublication),
-        VWSHRP('bugs', BugsBrowserRequest, BugsPublication),
-        VWSHRP('answers', AnswersBrowserRequest, AnswersPublication),
         VHRP('feeds', FeedsBrowserRequest, FeedsPublication),
         WebServiceRequestPublicationFactory(
             'api', WebServiceClientRequest, WebServicePublication),

=== modified file 'lib/canonical/launchpad/webapp/tests/test_servers.py'
--- lib/canonical/launchpad/webapp/tests/test_servers.py	2010-07-14 14:11:15 +0000
+++ lib/canonical/launchpad/webapp/tests/test_servers.py	2010-07-21 05:53:58 +0000
@@ -21,9 +21,8 @@
 from lp.testing import TestCase
 
 from canonical.launchpad.webapp.servers import (
-    AnswersBrowserRequest, ApplicationServerSettingRequestFactory,
-    BugsBrowserRequest, BugsPublication, LaunchpadBrowserRequest,
-    TranslationsBrowserRequest, VHostWebServiceRequestPublicationFactory,
+    ApplicationServerSettingRequestFactory, LaunchpadBrowserRequest,
+    VHostWebServiceRequestPublicationFactory,
     VirtualHostRequestPublicationFactory, WebServiceRequestPublicationFactory,
     WebServiceClientRequest, WebServicePublication, WebServiceTestRequest)
 
@@ -99,10 +98,17 @@
 
 class TestVhostWebserviceFactory(WebServiceTestCase):
 
+    class VHostTestBrowserRequest(LaunchpadBrowserRequest):
+        pass
+
+    class VHostTestPublication(LaunchpadBrowserRequest):
+        pass
+
     def setUp(self):
         super(TestVhostWebserviceFactory, self).setUp()
+        # XXX We have to use a real hostname.
         self.factory = VHostWebServiceRequestPublicationFactory(
-            'bugs', BugsBrowserRequest, BugsPublication)
+            'bugs', self.VHostTestBrowserRequest, self.VHostTestPublication)
 
     def wsgi_env(self, path, method='GET'):
         """Simulate a WSGI application environment."""
@@ -113,21 +119,21 @@
             }
 
     @property
-    def working_api_path(self):
-        """A path to the webservice API that should work every time."""
+    def api_path(self):
+        """Requests to this path should be treated as webservice requests."""
         return '/' + getUtility(IWebServiceConfiguration).path_override
 
     @property
-    def failing_api_path(self):
-        """A path that should not work with the webservice API."""
+    def non_api_path(self):
+        """Requests to this path should not be treated as webservice requests.
+        """
         return '/foo'
 
     def test_factory_produces_webservice_objects(self):
         """The factory should produce WebService request and publication
         objects for requests to the /api root URL.
         """
-        env = self.wsgi_env(
-            '/' + getUtility(IWebServiceConfiguration).path_override)
+        env = self.wsgi_env(self.api_path)
 
         # Necessary preamble and sanity check.  We need to call
         # the factory's canHandle() method with an appropriate
@@ -153,7 +159,7 @@
         specified in it's constructor if the request is not bound for the
         web service.
         """
-        env = self.wsgi_env('/foo')
+        env = self.wsgi_env(self.non_api_path)
         self.assert_(self.factory.canHandle(env),
             "Sanity check: The factory should be able to handle requests.")
 
@@ -162,12 +168,12 @@
         # We need to unwrap the real request factory.
         request_factory = wrapped_factory.requestfactory
 
-        self.assertEqual(request_factory, BugsBrowserRequest,
-            "Requests to normal paths should return a Bugs "
+        self.assertEqual(request_factory, self.VHostTestBrowserRequest,
+            "Requests to normal paths should return a VHostTest "
             "request object.")
         self.assertEqual(
-            publication_factory, BugsPublication,
-            "Requests to normal paths should return a Bugs "
+            publication_factory, self.VHostTestPublication,
+            "Requests to normal paths should return a VHostTest "
             "publication object.")
 
     def test_factory_processes_webservice_http_methods(self):
@@ -177,7 +183,7 @@
         allowed_methods = WebServiceRequestPublicationFactory.default_methods
 
         for method in allowed_methods:
-            env = self.wsgi_env(self.working_api_path, method)
+            env = self.wsgi_env(self.api_path, method)
             self.assert_(self.factory.canHandle(env),
                 "Sanity check")
             # Returns a tuple of (request_factory, publication_factory).
@@ -198,7 +204,7 @@
         denied_methods = set(ws_methods) - set(vhost_methods)
 
         for method in denied_methods:
-            env = self.wsgi_env(self.failing_api_path, method)
+            env = self.wsgi_env(self.non_api_path, method)
             self.assert_(self.factory.canHandle(env),
                 "Sanity check")
             # Returns a tuple of (request_factory, publication_factory).
@@ -221,9 +227,6 @@
         self.assert_(
             self.factory.isWebServicePath('/api'),
             "The factory should handle URLs that start with /api.")
-        self.assert_(
-            self.factory.isWebServicePath('/api/'),
-            "The factory should handle URLs that start with /api.")
 
         self.assert_(
             self.factory.isWebServicePath('/api/foo'),
@@ -385,26 +388,6 @@
         self.assertEquals(self.request.getNearest(IThing), (None, None))
 
 
-class TestAnswersBrowserRequest(TestCase):
-    """Tests for the Answers request class."""
-
-    def test_response_should_vary_based_on_language(self):
-        request = AnswersBrowserRequest(StringIO.StringIO(''), {})
-        self.assertEquals(
-            request.response.getHeader('Vary'),
-            'Cookie, Authorization, Accept-Language')
-
-
-class TestTranslationsBrowserRequest(TestCase):
-    """Tests for the Translations request class."""
-
-    def test_response_should_vary_based_on_language(self):
-        request = TranslationsBrowserRequest(StringIO.StringIO(''), {})
-        self.assertEquals(
-            request.response.getHeader('Vary'),
-            'Cookie, Authorization, Accept-Language')
-
-
 class TestLaunchpadBrowserRequest(TestCase):
 
     def prepareRequest(self, form):

=== modified file 'lib/canonical/launchpad/zcml/marketing.zcml'
--- lib/canonical/launchpad/zcml/marketing.zcml	2010-07-21 05:53:56 +0000
+++ lib/canonical/launchpad/zcml/marketing.zcml	2010-07-21 05:53:58 +0000
@@ -26,14 +26,14 @@
     <!-- Marketing material for Answers. -->
     <browser:renamed-page
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.AnswersLayer"
+        layer="lp.answers.publisher.AnswersLayer"
         name="+about"
         new_name="+tour/community-support"
         rootsite="mainsite"
         />
     <browser:renamed-page
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.AnswersLayer"
+        layer="lp.answers.publisher.AnswersLayer"
         name="+faq"
         new_name="+tour/community-support"
         rootsite="mainsite"
@@ -43,20 +43,20 @@
         new_name="+tour/community-support"
         rootsite="mainsite"
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.AnswersLayer"
+        layer="lp.answers.publisher.AnswersLayer"
         />
 
     <!-- Marketing material for Blueprints. -->
     <browser:renamed-page
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.BlueprintsLayer"
+        layer="lp.blueprints.publisher.BlueprintsLayer"
         name="+about"
         new_name="+tour/feature-tracking"
         rootsite="mainsite"
         />
     <browser:renamed-page
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.BlueprintsLayer"
+        layer="lp.blueprints.publisher.BlueprintsLayer"
         name="+faq"
         new_name="+tour/feature-tracking"
         rootsite="mainsite"
@@ -64,7 +64,7 @@
     <browser:renamed-page
         name="+tour"
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.BlueprintsLayer"
+        layer="lp.blueprints.publisher.BlueprintsLayer"
         new_name="+tour/feature-tracking"
         rootsite="mainsite"
         />
@@ -72,14 +72,14 @@
     <!-- Marketing material for Bugs. -->
     <browser:renamed-page
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         name="+about"
         new_name="+tour/bugs"
         rootsite="mainsite"
         />
     <browser:renamed-page
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         name="+faq"
         new_name="+tour/bugs"
         rootsite="mainsite"
@@ -87,7 +87,7 @@
     <browser:renamed-page
         name="+tour"
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         new_name="+tour/bugs"
         rootsite="mainsite"
         />
@@ -119,14 +119,14 @@
     <!-- Marketing material for Translations. -->
     <browser:renamed-page
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         name="+about"
         new_name="+tour/translation"
         rootsite="mainsite"
         />
     <browser:renamed-page
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         name="+faq"
         new_name="+tour/translation"
         rootsite="mainsite"
@@ -134,7 +134,7 @@
     <browser:renamed-page
         name="+tour"
         for="canonical.launchpad.webapp.interfaces.ILaunchpadApplication"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         new_name="+tour/translation"
         rootsite="mainsite"
         />

=== modified file 'lib/lp/answers/browser/configure.zcml'
--- lib/lp/answers/browser/configure.zcml	2009-11-22 17:28:49 +0000
+++ lib/lp/answers/browser/configure.zcml	2010-07-21 05:53:58 +0000
@@ -14,7 +14,7 @@
        IQuestionCollection. -->
   <browser:defaultView
     for="lp.answers.interfaces.questioncollection.IQuestionCollection"
-    layer="canonical.launchpad.layers.AnswersLayer"
+    layer="lp.answers.publisher.AnswersLayer"
     name="+tickets"
     />
   <browser:page
@@ -351,7 +351,7 @@
 
   <browser:defaultView
     for="canonical.launchpad.interfaces.IPerson"
-    layer="canonical.launchpad.layers.AnswersLayer"
+    layer="lp.answers.publisher.AnswersLayer"
     name="+questions"
     />
 

=== modified file 'lib/lp/answers/browser/tests/views.txt'
--- lib/lp/answers/browser/tests/views.txt	2010-04-16 15:06:55 +0000
+++ lib/lp/answers/browser/tests/views.txt	2010-07-21 05:53:58 +0000
@@ -860,7 +860,7 @@
 the question listing table. Products display the default columns of
 Summary, Created, Submitter, Assignee, and Status.
 
-    >>> from canonical.launchpad.layers import AnswersLayer
+    >>> from lp.answers.publisher import AnswersLayer
     >>> from canonical.launchpad.testing.pages import (
     ...     extract_text, find_tag_by_id)
 

=== modified file 'lib/lp/answers/configure.zcml'
--- lib/lp/answers/configure.zcml	2010-02-17 11:19:42 +0000
+++ lib/lp/answers/configure.zcml	2010-07-21 05:53:58 +0000
@@ -10,6 +10,10 @@
 
   <include package=".browser" />
 
+  <publisher
+     name="answers"
+     factory="lp.answers.publisher.answers_request_publication_factory"/>
+
     <subscriber
         for=".interfaces.question.IQuestion
            lazr.lifecycle.interfaces.IObjectCreatedEvent"
@@ -235,7 +239,7 @@
 
 
   <lp:help-folder
-      folder="help" type="canonical.launchpad.layers.AnswersLayer" />
+      folder="help" type="lp.answers.publisher.AnswersLayer" />
   <adapter
       name="answers"
       provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"

=== added file 'lib/lp/answers/publisher.py'
--- lib/lp/answers/publisher.py	1970-01-01 00:00:00 +0000
+++ lib/lp/answers/publisher.py	2010-07-21 05:53:58 +0000
@@ -0,0 +1,41 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Answers's custom publication."""
+
+__metaclass__ = type
+__all__ = [
+    'AnswersBrowserRequest',
+    'AnswersLayer',
+    'answers_request_publication_factory',
+    ]
+
+
+from zope.interface import implements
+from zope.publisher.interfaces.browser import (
+    IBrowserRequest, IDefaultBrowserLayer)
+
+from canonical.launchpad.webapp.publication import LaunchpadBrowserPublication
+from canonical.launchpad.webapp.servers import (
+    LaunchpadBrowserRequest, VHostWebServiceRequestPublicationFactory)
+
+
+class AnswersLayer(IBrowserRequest, IDefaultBrowserLayer):
+    """The Answers layer."""
+
+
+class AnswersBrowserRequest(LaunchpadBrowserRequest):
+    """Instances of AnswersBrowserRequest provide `AnswersLayer`."""
+    implements(AnswersLayer)
+
+    def __init__(self, body_instream, environ, response=None):
+        super(AnswersBrowserRequest, self).__init__(
+            body_instream, environ, response)
+        # Many of the responses from Answers vary based on language.
+        self.response.setHeader(
+            'Vary', 'Cookie, Authorization, Accept-Language')
+
+
+def answers_request_publication_factory():
+    return VHostWebServiceRequestPublicationFactory(
+        'answers', AnswersBrowserRequest, LaunchpadBrowserPublication)

=== added file 'lib/lp/answers/tests/test_publisher.py'
--- lib/lp/answers/tests/test_publisher.py	1970-01-01 00:00:00 +0000
+++ lib/lp/answers/tests/test_publisher.py	2010-07-21 05:53:58 +0000
@@ -0,0 +1,54 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for answers's custom publications."""
+
+__metaclass__ = type
+
+import StringIO
+import unittest
+
+from canonical.config import config
+from canonical.launchpad.layers import WebServiceLayer
+from canonical.testing.layers import FunctionalLayer
+
+from lp.testing import TestCase
+from lp.testing.publication import get_request_and_publication
+
+from lp.answers.publisher import AnswersLayer, AnswersBrowserRequest
+
+
+class TestRegistration(TestCase):
+    """Answers' publication customizations are installed correctly."""
+
+    layer = FunctionalLayer
+
+    def test_answers_request_provides_answers_layer(self):
+        # The request constructed for requests to the answers hostname
+        # provides AnswersLayer.
+        request, publication = get_request_and_publication(
+            host=config.vhost.answers.hostname)
+        self.assertProvides(request, AnswersLayer)
+
+    def test_answers_host_has_api(self):
+        # Requests to /api on the answers domain are treated as web service
+        # requests.
+        request, publication = get_request_and_publication(
+            host=config.vhost.answers.hostname,
+            extra_environment={'PATH_INFO': '/api/1.0'})
+        # XXX MichaelHudson, 2010-07-20, bug=607664: WebServiceLayer only
+        # actually provides WebServiceLayer in the sense of verifyObject after
+        # traversal has started.
+        self.assertTrue(WebServiceLayer.providedBy(request))
+
+    def test_response_should_vary_based_on_language(self):
+        # Responses to requests to answers pages have the 'Vary' header set to
+        # include Accept-Language.
+        request = AnswersBrowserRequest(StringIO.StringIO(''), {})
+        self.assertEquals(
+            request.response.getHeader('Vary'),
+            'Cookie, Authorization, Accept-Language')
+
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromName(__name__)

=== modified file 'lib/lp/app/tests/test_help.py'
--- lib/lp/app/tests/test_help.py	2010-07-21 05:53:56 +0000
+++ lib/lp/app/tests/test_help.py	2010-07-21 05:53:58 +0000
@@ -10,15 +10,17 @@
 
 
 from canonical.testing.layers import FunctionalLayer
-from canonical.launchpad.layers import (
-    AnswersLayer, BlueprintsLayer, BugsLayer, LaunchpadLayer,
-    TranslationsLayer)
+from canonical.launchpad.layers import LaunchpadLayer
 from canonical.launchpad.testing.systemdocs import create_view
 from canonical.launchpad.webapp.interfaces import ILaunchpadApplication
 
 from canonical.lazr.folder import ExportedFolder
 
+from lp.answers.publisher import AnswersLayer
+from lp.blueprints.publisher import BlueprintsLayer
+from lp.bugs.publisher import BugsLayer
 from lp.code.publisher import CodeLayer
+from lp.translations.publisher import TranslationsLayer
 
 # The root of the tree
 ROOT = os.path.realpath(

=== modified file 'lib/lp/blueprints/browser/configure.zcml'
--- lib/lp/blueprints/browser/configure.zcml	2010-02-17 11:19:42 +0000
+++ lib/lp/blueprints/browser/configure.zcml	2010-07-21 05:53:58 +0000
@@ -18,7 +18,7 @@
     <browser:defaultView
         for="lp.blueprints.interfaces.sprint.ISprint"
         name="+specs"
-        layer="canonical.launchpad.layers.BlueprintLayer"/>
+        layer="lp.blueprints.publisher.BlueprintsLayer"/>
     <browser:url
         for="lp.blueprints.interfaces.sprint.ISprint"
         path_expression="name"
@@ -562,7 +562,7 @@
     <browser:defaultView
         for="lp.registry.interfaces.distroseries.IDistroSeries"
         name="+specs"
-        layer="canonical.launchpad.layers.BlueprintLayer"/>
+        layer="lp.blueprints.publisher.BlueprintsLayer"/>
     <browser:page
         name="+addspec"
         for="lp.registry.interfaces.distroseries.IDistroSeries"

=== modified file 'lib/lp/blueprints/browser/tests/test_specificationtarget.py'
--- lib/lp/blueprints/browser/tests/test_specificationtarget.py	2009-11-08 19:34:52 +0000
+++ lib/lp/blueprints/browser/tests/test_specificationtarget.py	2010-07-21 05:53:58 +0000
@@ -5,11 +5,11 @@
 
 import unittest
 
-from canonical.launchpad.layers import BlueprintLayer
 from canonical.testing.layers import DatabaseFunctionalLayer
 
 from lp.blueprints.interfaces.specificationtarget import (
     IHasSpecifications, ISpecificationTarget)
+from lp.blueprints.publisher import BlueprintsLayer
 from lp.testing import login_person, TestCaseWithFactory
 from lp.testing.views import create_view
 
@@ -58,7 +58,7 @@
     def verify_involvment(self, context):
         self.assertTrue(IHasSpecifications.providedBy(context))
         view = create_view(
-            context, '+specs', layer=BlueprintLayer, principal=self.user)
+            context, '+specs', layer=BlueprintsLayer, principal=self.user)
         self.assertTrue(
             '<div id="involvement" class="portlet involvement">' in view())
 
@@ -78,7 +78,7 @@
         context = self.factory.makePerson(name='pistachio')
         self.assertTrue(IHasSpecifications.providedBy(context))
         view = create_view(
-            context, '+specs', layer=BlueprintLayer, principal=self.user)
+            context, '+specs', layer=BlueprintsLayer, principal=self.user)
         self.assertFalse(
             '<div id="involvement" class="portlet involvement">' in view())
 

=== modified file 'lib/lp/blueprints/configure.zcml'
--- lib/lp/blueprints/configure.zcml	2009-09-23 03:36:55 +0000
+++ lib/lp/blueprints/configure.zcml	2010-07-21 05:53:58 +0000
@@ -12,8 +12,12 @@
     <include
         package=".browser"/>
 
+    <publisher
+       name="blueprints"
+       factory="lp.blueprints.publisher.blueprints_request_publication_factory"/>
+
     <lp:help-folder
-        folder="help" type="canonical.launchpad.layers.BlueprintsLayer" />
+        folder="help" type="lp.blueprints.publisher.BlueprintsLayer" />
 
     <!-- Sprint -->
 

=== added file 'lib/lp/blueprints/publisher.py'
--- lib/lp/blueprints/publisher.py	1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/publisher.py	2010-07-21 05:53:58 +0000
@@ -0,0 +1,34 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Blueprints' custom publication."""
+
+__metaclass__ = type
+__all__ = [
+    'BlueprintsBrowserRequest',
+    'BlueprintsLayer',
+    'blueprints_request_publication_factory',
+    ]
+
+
+from zope.interface import implements
+from zope.publisher.interfaces.browser import (
+    IBrowserRequest, IDefaultBrowserLayer)
+
+from canonical.launchpad.webapp.publication import LaunchpadBrowserPublication
+from canonical.launchpad.webapp.servers import (
+    LaunchpadBrowserRequest, VHostWebServiceRequestPublicationFactory)
+
+
+class BlueprintsLayer(IBrowserRequest, IDefaultBrowserLayer):
+    """The Blueprints layer."""
+
+
+class BlueprintsBrowserRequest(LaunchpadBrowserRequest):
+    """Instances of BlueprintsBrowserRequest provide `BlueprintsLayer`."""
+    implements(BlueprintsLayer)
+
+
+def blueprints_request_publication_factory():
+    return VHostWebServiceRequestPublicationFactory(
+        'blueprints', BlueprintsBrowserRequest, LaunchpadBrowserPublication)

=== added file 'lib/lp/blueprints/tests/test_publisher.py'
--- lib/lp/blueprints/tests/test_publisher.py	1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/tests/test_publisher.py	2010-07-21 05:53:58 +0000
@@ -0,0 +1,45 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for blueprints' custom publications."""
+
+__metaclass__ = type
+
+import unittest
+
+from canonical.config import config
+from canonical.launchpad.layers import WebServiceLayer
+from canonical.testing.layers import FunctionalLayer
+
+from lp.testing import TestCase
+from lp.testing.publication import get_request_and_publication
+
+from lp.blueprints.publisher import BlueprintsLayer
+
+
+class TestRegistration(TestCase):
+    """Blueprints's publication customizations are installed correctly."""
+
+    layer = FunctionalLayer
+
+    def test_blueprints_request_provides_blueprints_layer(self):
+        # The request constructed for requests to the blueprints hostname
+        # provides BlueprintsLayer.
+        request, publication = get_request_and_publication(
+            host=config.vhost.blueprints.hostname)
+        self.assertProvides(request, BlueprintsLayer)
+
+    def test_blueprints_host_has_api(self):
+        # Requests to /api on the blueprints domain are treated as web service
+        # requests.
+        request, publication = get_request_and_publication(
+            host=config.vhost.blueprints.hostname,
+            extra_environment={'PATH_INFO': '/api/1.0'})
+        # XXX MichaelHudson, 2010-07-20, bug=607664: WebServiceLayer only
+        # actually provides WebServiceLayer in the sense of verifyObject after
+        # traversal has started.
+        self.assertTrue(WebServiceLayer.providedBy(request))
+
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromName(__name__)

=== modified file 'lib/lp/bugs/browser/malone.py'
--- lib/lp/bugs/browser/malone.py	2009-09-04 15:32:52 +0000
+++ lib/lp/bugs/browser/malone.py	2010-07-21 05:53:58 +0000
@@ -13,7 +13,6 @@
 from zope.component import getUtility
 from zope.security.interfaces import Unauthorized
 
-import canonical.launchpad.layers
 
 from canonical.launchpad.webapp import (
     Link, Navigation, canonical_url, stepto)
@@ -25,6 +24,7 @@
 from lp.bugs.interfaces.bugtracker import IBugTrackerSet
 from lp.bugs.interfaces.cve import ICveSet
 from lp.bugs.interfaces.malone import IMaloneApplication
+from lp.bugs.publisher import BugsLayer
 from lp.registry.interfaces.distribution import IDistributionSet
 from lp.registry.interfaces.product import IProductSet
 
@@ -33,7 +33,7 @@
 
     usedfor = IMaloneApplication
 
-    newlayer = canonical.launchpad.layers.BugsLayer
+    newlayer = BugsLayer
 
     @stepto('bugs')
     def bugs(self):

=== modified file 'lib/lp/bugs/browser/tests/test_configure_bugtracker_links.py'
--- lib/lp/bugs/browser/tests/test_configure_bugtracker_links.py	2010-06-17 15:26:05 +0000
+++ lib/lp/bugs/browser/tests/test_configure_bugtracker_links.py	2010-07-21 05:53:58 +0000
@@ -8,9 +8,10 @@
 import unittest
 
 from canonical.launchpad.ftests import ANONYMOUS, login
-from canonical.launchpad.layers import BugsLayer
 from canonical.testing import LaunchpadFunctionalLayer
 
+from lp.bugs.publisher import BugsLayer
+
 from lp.testing import login_person, TestCaseWithFactory
 from lp.testing.views import create_initialized_view
 

=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml	2010-07-12 16:32:31 +0000
+++ lib/lp/bugs/configure.zcml	2010-07-21 05:53:58 +0000
@@ -12,8 +12,12 @@
     <include
         package=".browser"/>
 
+    <publisher
+       name="bugs"
+       factory="lp.bugs.publisher.bugs_request_publication_factory"/>
+
     <lp:help-folder
-        folder="help" type="canonical.launchpad.layers.BugsLayer" />
+        folder="help" type="lp.bugs.publisher.BugsLayer" />
 
     <class
         class="canonical.launchpad.database.BugActivity">

=== added file 'lib/lp/bugs/publisher.py'
--- lib/lp/bugs/publisher.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/publisher.py	2010-07-21 05:53:58 +0000
@@ -0,0 +1,34 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Bugs' custom publication."""
+
+__metaclass__ = type
+__all__ = [
+    'BugsBrowserRequest',
+    'BugsLayer',
+    'bugs_request_publication_factory',
+    ]
+
+
+from zope.interface import implements
+from zope.publisher.interfaces.browser import (
+    IBrowserRequest, IDefaultBrowserLayer)
+
+from canonical.launchpad.webapp.publication import LaunchpadBrowserPublication
+from canonical.launchpad.webapp.servers import (
+    LaunchpadBrowserRequest, VHostWebServiceRequestPublicationFactory)
+
+
+class BugsLayer(IBrowserRequest, IDefaultBrowserLayer):
+    """The Bugs layer."""
+
+
+class BugsBrowserRequest(LaunchpadBrowserRequest):
+    """Instances of BugBrowserRequest provide `BugsLayer`."""
+    implements(BugsLayer)
+
+
+def bugs_request_publication_factory():
+    return VHostWebServiceRequestPublicationFactory(
+        'bugs', BugsBrowserRequest, LaunchpadBrowserPublication)

=== added file 'lib/lp/bugs/tests/test_publisher.py'
--- lib/lp/bugs/tests/test_publisher.py	1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/tests/test_publisher.py	2010-07-21 05:53:58 +0000
@@ -0,0 +1,45 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for bugs' custom publications."""
+
+__metaclass__ = type
+
+import unittest
+
+from canonical.config import config
+from canonical.launchpad.layers import WebServiceLayer
+from canonical.testing.layers import FunctionalLayer
+
+from lp.testing import TestCase
+from lp.testing.publication import get_request_and_publication
+
+from lp.bugs.publisher import BugsLayer
+
+
+class TestRegistration(TestCase):
+    """Bugs publication customizations are installed correctly."""
+
+    layer = FunctionalLayer
+
+    def test_bugs_request_provides_bugs_layer(self):
+        # The request constructed for requests to the bugs hostname provides
+        # BugsLayer.
+        request, publication = get_request_and_publication(
+            host=config.vhost.bugs.hostname)
+        self.assertProvides(request, BugsLayer)
+
+    def test_bugs_host_has_api(self):
+        # Requests to /api on the bugs domain are treated as web service
+        # requests.
+        request, publication = get_request_and_publication(
+            host=config.vhost.bugs.hostname,
+            extra_environment={'PATH_INFO': '/api/1.0'})
+        # XXX MichaelHudson, 2010-07-20, bug=607664: WebServiceLayer only
+        # actually provides WebServiceLayer in the sense of verifyObject after
+        # traversal has started.
+        self.assertTrue(WebServiceLayer.providedBy(request))
+
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromName(__name__)

=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml	2010-07-21 05:53:56 +0000
+++ lib/lp/registry/browser/configure.zcml	2010-07-21 05:53:58 +0000
@@ -57,7 +57,7 @@
         name="+index"/>
     <browser:defaultView
         for="lp.registry.interfaces.distroseries.IDistroSeries"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         name="+bugs-index"/>
     <browser:page
         for="lp.registry.interfaces.distroseries.IDistroSeries"
@@ -294,15 +294,15 @@
     <browser:defaultView
         for="lp.registry.interfaces.projectgroup.IProjectGroup"
         name="+bugs"
-        layer="canonical.launchpad.layers.BugsLayer"/>
+        layer="lp.bugs.publisher.BugsLayer"/>
     <browser:defaultView
         for="lp.registry.interfaces.projectgroup.IProjectGroup"
         name="+specs"
-        layer="canonical.launchpad.layers.BlueprintLayer"/>
+        layer="lp.blueprints.publisher.BlueprintsLayer"/>
     <browser:defaultView
         for="lp.registry.interfaces.projectgroup.IProjectGroup"
         name="+questions"
-        layer="canonical.launchpad.layers.AnswersLayer"/>
+        layer="lp.answers.publisher.AnswersLayer"/>
     <browser:pages
         for="lp.registry.interfaces.projectgroup.IProjectGroup"
         class="lp.registry.browser.project.ProjectView"
@@ -408,7 +408,7 @@
     <browser:defaultView
         for="lp.registry.interfaces.projectgroup.IProjectGroupSeries"
         name="+specs"
-        layer="canonical.launchpad.layers.BlueprintLayer"/>
+        layer="lp.blueprints.publisher.BlueprintsLayer"/>
     <browser:menus
         classes="
             ProjectActionMenu
@@ -428,11 +428,11 @@
         name="+index"/>
     <browser:defaultView
         for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
-        layer="canonical.launchpad.layers.AnswersLayer"
+        layer="lp.answers.publisher.AnswersLayer"
         name="+questions"/>
     <browser:defaultView
         for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         name="+bugs"/>
     <browser:url
         for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
@@ -762,11 +762,11 @@
             name="+index"/>
         <browser:defaultView
             for="lp.registry.interfaces.person.IPerson"
-            layer="canonical.launchpad.layers.BlueprintLayer"
+            layer="lp.blueprints.publisher.BlueprintsLayer"
             name="+specs"/>
         <browser:defaultView
             for="lp.registry.interfaces.person.IPerson"
-            layer="canonical.launchpad.layers.BugsLayer"
+            layer="lp.bugs.publisher.BugsLayer"
             name="+bugs"/>
         <adapter
           factory="lp.registry.browser.person.PersonXHTMLRepresentation"
@@ -1353,18 +1353,18 @@
     <browser:defaultView
         for="lp.registry.interfaces.product.IProduct"
         name="+specs"
-        layer="canonical.launchpad.layers.BlueprintLayer"/>
+        layer="lp.blueprints.publisher.BlueprintsLayer"/>
     <browser:defaultView
         for="lp.registry.interfaces.product.IProduct"
         name="+code-index"
         layer="lp.code.publisher.CodeLayer"/>
     <browser:defaultView
         for="lp.registry.interfaces.product.IProduct"
-        layer="canonical.launchpad.layers.AnswersLayer"
+        layer="lp.answers.publisher.AnswersLayer"
         name="+questions"/>
     <browser:defaultView
         for="lp.registry.interfaces.product.IProduct"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         name="+bugs-index"/>
     <browser:navigation
         module="lp.registry.browser.product"
@@ -1596,10 +1596,10 @@
     <browser:defaultView
         for="lp.registry.interfaces.productseries.IProductSeries"
         name="+specs"
-        layer="canonical.launchpad.layers.BlueprintLayer"/>
+        layer="lp.blueprints.publisher.BlueprintsLayer"/>
     <browser:defaultView
         for="lp.registry.interfaces.productseries.IProductSeries"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         name="+bugs-index"/>
     <browser:page
         name="+get-involved"
@@ -1739,14 +1739,14 @@
     <browser:defaultView
         for="lp.registry.interfaces.distribution.IDistribution"
         name="+specs"
-        layer="canonical.launchpad.layers.BlueprintLayer"/>
+        layer="lp.blueprints.publisher.BlueprintsLayer"/>
     <browser:defaultView
         for="lp.registry.interfaces.distribution.IDistribution"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         name="+bugs-index"/>
     <browser:defaultView
         for="lp.registry.interfaces.distribution.IDistribution"
-        layer="canonical.launchpad.layers.AnswersLayer"
+        layer="lp.answers.publisher.AnswersLayer"
         name="+questions"/>
     <browser:navigation
         module="lp.registry.browser.distribution"
@@ -1946,11 +1946,11 @@
         name="+index"/>
     <browser:defaultView
         for="lp.registry.interfaces.sourcepackage.ISourcePackage"
-        layer="canonical.launchpad.layers.BugsLayer"
+        layer="lp.bugs.publisher.BugsLayer"
         name="+bugs"/>
     <browser:defaultView
         for="lp.registry.interfaces.sourcepackage.ISourcePackage"
-        layer="canonical.launchpad.layers.AnswersLayer"
+        layer="lp.answers.publisher.AnswersLayer"
         name="+questions"/>
     <browser:navigation
         module="lp.registry.browser.sourcepackage"

=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml	2010-04-19 08:11:52 +0000
+++ lib/lp/translations/browser/configure.zcml	2010-07-21 05:53:58 +0000
@@ -15,7 +15,7 @@
     <browser:defaultView
         for="canonical.launchpad.interfaces.IRosettaApplication"
         name="+index"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:navigation
         module="lp.translations.browser.translations"
         classes="
@@ -25,7 +25,7 @@
         facet="translations"
         permission="zope.Public"
         class="lp.translations.browser.translations.RosettaApplicationView"
-        layer="canonical.launchpad.layers.TranslationsLayer">
+        layer="lp.translations.publisher.TranslationsLayer">
         <browser:page
             name="+index"
             template="../templates/rosetta-index.pt"/>
@@ -44,7 +44,7 @@
       facet="translations"
       permission="zope.Public"
       class="lp.translations.browser.translations.TranslatableProductsView"
-      layer="canonical.launchpad.layers.TranslationsLayer"
+      layer="lp.translations.publisher.TranslationsLayer"
       name="+products-with-translations"
       template="../templates/rosetta-products.pt"/>
     <browser:pages
@@ -66,48 +66,48 @@
         permission="zope.Public"
         name="+barchart"
         template="../templates/rosettastats-barchart.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="*"
         name="+translations-macros"
         facet="translations"
         permission="zope.Public"
         template="../templates/translations-macros.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="lp.translations.interfaces.translationgroup.ITranslationPolicy"
         facet="translations"
         permission="zope.Public"
         name="+portlet-translation-groups-and-permission"
         template="../templates/hastranslationgroup-portlet-translation-groups-and-permission.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <facet
         facet="translations">
         <browser:defaultView
             for="lp.translations.interfaces.translator.ITranslator"
             name="+admin"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+admin"
             for="lp.translations.interfaces.translator.ITranslator"
             permission="launchpad.Admin"
             class="lp.translations.browser.translator.TranslatorAdminView"
             template="../../app/templates/generic-edit.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+edit"
             for="lp.translations.interfaces.translator.IEditTranslator"
             permission="launchpad.Edit"
             class="lp.translations.browser.translator.TranslatorEditView"
             template="../../app/templates/generic-edit.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+remove"
             for="lp.translations.interfaces.translator.ITranslator"
             permission="launchpad.Edit"
             class="lp.translations.browser.translator.TranslatorRemoveView"
             template="../../app/templates/generic-edit.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
     </facet>
     <facet
         facet="translations">
@@ -119,7 +119,7 @@
         <browser:defaultView
             for="lp.translations.interfaces.translationimportqueue.ITranslationImportQueueEntry"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:url
             for="lp.translations.interfaces.translationimportqueue.ITranslationImportQueueEntry"
             path_expression="string:${id}"
@@ -130,12 +130,12 @@
             class="lp.translations.browser.translationimportqueue.TranslationImportQueueEntryView"
             permission="launchpad.Admin"
             template="../templates/translationimportqueueentry-index.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.translations.interfaces.translationimportqueue.ITranslationImportQueueEntry"
             facet="translations"
             permission="zope.Public"
-            layer="canonical.launchpad.layers.TranslationsLayer"
+            layer="lp.translations.publisher.TranslationsLayer"
             name="+portlet-details"
             class="lp.translations.browser.translationimportqueue.TranslationImportQueueEntryView"
             template="../templates/translationimportqueueentry-portlet-details.pt"/>
@@ -146,7 +146,7 @@
         <browser:defaultView
             for="lp.translations.interfaces.translationimportqueue.ITranslationImportQueue"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.translations.interfaces.translationimportqueue.ITranslationImportQueue"
             name="+index"
@@ -154,7 +154,7 @@
             facet="translations"
             permission="zope.Public"
             template="../templates/translationimportqueue-index.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
     </facet>
     <browser:url
         for="lp.translations.interfaces.distroserieslanguage.IDistroSeriesLanguage"
@@ -167,12 +167,12 @@
     <browser:defaultView
         for="lp.translations.interfaces.distroserieslanguage.IDistroSeriesLanguage"
         name="+index"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:pages
         for="lp.translations.interfaces.distroserieslanguage.IDistroSeriesLanguage"
         permission="zope.Public"
         facet="translations"
-        layer="canonical.launchpad.layers.TranslationsLayer">
+        layer="lp.translations.publisher.TranslationsLayer">
         <browser:page
             name="+rosetta-status-legend"
             template="../templates/rosetta-status-legend.pt"/>
@@ -184,7 +184,7 @@
         template="../templates/serieslanguage-index.pt"
         class="lp.translations.browser.serieslanguage.DistroSeriesLanguageView"
         facet="translations"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <facet
         facet="translations">
         <browser:navigation
@@ -204,19 +204,19 @@
         <browser:defaultView
             for="lp.translations.interfaces.pofile.IPOFile"
             name="+translate"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.translations.interfaces.pofile.IPOFile"
             name="+details"
             permission="zope.Public"
             class="lp.translations.browser.pofile.POFileDetailsView"
-            layer="canonical.launchpad.layers.TranslationsLayer"
+            layer="lp.translations.publisher.TranslationsLayer"
             template="../templates/pofile-details.pt"/>
         <browser:pages
             for="lp.translations.interfaces.pofile.IPOFile"
             permission="zope.Public"
             class="lp.translations.browser.pofile.POFileView"
-            layer="canonical.launchpad.layers.TranslationsLayer">
+            layer="lp.translations.publisher.TranslationsLayer">
             <!-- POFile Portlets -->
             <browser:page
                 name="+contributors"
@@ -237,7 +237,7 @@
             class="lp.translations.browser.pofile.POFileUploadView"
             for="lp.translations.interfaces.pofile.IPOFile"
             template="../templates/pofile-upload.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+translate"
             for="lp.translations.interfaces.pofile.IPOFile"
@@ -250,21 +250,21 @@
             permission="zope.Public"
             template="../templates/pofile-translate.pt"
             class="lp.translations.browser.pofile.POFileTranslateView"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+export"
             for="lp.translations.interfaces.pofile.IPOFile"
             permission="launchpad.AnyPerson"
             template="../templates/pofile-export.pt"
             class="lp.translations.browser.pofile.POExportView"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+filter"
             for="lp.translations.interfaces.pofile.IPOFile"
             permission="zope.Public"
             template="../templates/pofile-filter.pt"
             class="lp.translations.browser.pofile.POFileFilteredView"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
     </facet>
     <browser:url
         for="lp.translations.interfaces.productserieslanguage.IProductSeriesLanguage"
@@ -282,7 +282,7 @@
         for="lp.translations.interfaces.productserieslanguage.IProductSeriesLanguage"
         permission="zope.Public"
         facet="translations"
-        layer="canonical.launchpad.layers.TranslationsLayer">
+        layer="lp.translations.publisher.TranslationsLayer">
         <browser:page
             name="+rosetta-status-legend"
             template="../templates/rosetta-status-legend.pt"/>
@@ -294,7 +294,7 @@
         template="../templates/serieslanguage-index.pt"
         class="lp.translations.browser.serieslanguage.ProductSeriesLanguageView"
         facet="translations"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <facet
         facet="translations">
         <browser:navigation
@@ -304,7 +304,7 @@
         <browser:defaultView
             for="canonical.launchpad.interfaces.ILanguage"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:menus
             module="lp.translations.browser.language"
             classes="
@@ -321,19 +321,19 @@
             permission="zope.Public"
             template="../templates/language-index.pt"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="canonical.launchpad.interfaces.ILanguage"
             class="lp.translations.browser.language.LanguageAdminView"
             permission="launchpad.Admin"
             template="../../app/templates/generic-edit.pt"
             name="+admin"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:pages
             for="canonical.launchpad.interfaces.ILanguage"
             class="lp.translations.browser.language.LanguageView"
             permission="zope.Public"
-            layer="canonical.launchpad.layers.TranslationsLayer">
+            layer="lp.translations.publisher.TranslationsLayer">
             <browser:page
                 name="+portlet-details"
                 template="../templates/language-portlet-details.pt"/>
@@ -344,7 +344,7 @@
         <browser:defaultView
             for="canonical.launchpad.interfaces.ILanguageSet"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:url
             for="canonical.launchpad.interfaces.ILanguageSet"
             path_expression="string:+languages"
@@ -356,14 +356,14 @@
             permission="zope.Public"
             template="../templates/languageset-index.pt"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="canonical.launchpad.interfaces.ILanguageSet"
             class="lp.translations.browser.language.LanguageAddView"
             permission="launchpad.Admin"
             template="../../app/templates/generic-edit.pt"
             name="+add"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:navigation
             module="lp.translations.browser.potemplate"
             classes="
@@ -373,19 +373,19 @@
         <browser:defaultView
             for="lp.translations.interfaces.potemplate.IPOTemplate"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+preferred-chart"
             for="lp.translations.interfaces.potemplate.IPOTemplate"
             class="lp.translations.browser.potemplate.POTemplateViewPreferred"
             permission="zope.Public"
             template="../templates/potemplate-chart.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:pages
             for="lp.translations.interfaces.potemplate.IPOTemplate"
             class="lp.translations.browser.potemplate.POTemplateView"
             permission="zope.Public"
-            layer="canonical.launchpad.layers.TranslationsLayer">
+            layer="lp.translations.publisher.TranslationsLayer">
             <browser:page
                 name="+index"
                 template="../templates/potemplate-index.pt"/>
@@ -408,28 +408,28 @@
             permission="launchpad.Edit"
             template="../templates/potemplate-upload.pt"
             class="lp.translations.browser.potemplate.POTemplateUploadView"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+edit"
             for="lp.translations.interfaces.potemplate.IPOTemplate"
             class="lp.translations.browser.potemplate.POTemplateEditView"
             permission="launchpad.Edit"
             template="../../app/templates/generic-edit.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+admin"
             for="lp.translations.interfaces.potemplate.IPOTemplate"
             class="lp.translations.browser.potemplate.POTemplateAdminView"
             permission="launchpad.TranslationsAdmin"
             template="../../app/templates/generic-edit.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+export"
             for="lp.translations.interfaces.potemplate.IPOTemplate"
             permission="launchpad.AnyPerson"
             template="../templates/potemplate-export.pt"
             class="lp.translations.browser.potemplate.POTemplateExportView"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:menus
             module="lp.translations.browser.potemplate"
             classes="
@@ -444,14 +444,14 @@
         <browser:defaultView
             for="lp.translations.interfaces.potemplate.IPOTemplateSubset"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+index"
             for="lp.translations.interfaces.potemplate.IPOTemplateSubset"
             permission="zope.Public"
             class="lp.translations.browser.potemplate.POTemplateSubsetView"
             attribute="__call__"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:navigation
             module="lp.translations.browser.translationgroup"
             classes="
@@ -460,7 +460,7 @@
         <browser:defaultView
             for="lp.translations.interfaces.translationgroup.ITranslationGroup"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:url
             for="lp.translations.interfaces.translationgroup.ITranslationGroup"
             path_expression="name"
@@ -470,7 +470,7 @@
             for="lp.translations.interfaces.translationgroup.ITranslationGroup"
             class="lp.translations.browser.translationgroup.TranslationGroupView"
             permission="zope.Public"
-            layer="canonical.launchpad.layers.TranslationsLayer">
+            layer="lp.translations.publisher.TranslationsLayer">
             <browser:page
                 template="../templates/translationgroup-index.pt"
                 name="+index"/>
@@ -487,27 +487,27 @@
             permission="launchpad.Edit"
             class="lp.translations.browser.translationgroup.TranslationGroupEditView"
             template="../../app/templates/generic-edit.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+appoint"
             for="lp.translations.interfaces.translationgroup.ITranslationGroup"
             permission="launchpad.Edit"
             class="lp.translations.browser.translationgroup.TranslationGroupAddTranslatorView"
             template="../../app/templates/generic-edit.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+reassign"
             for="lp.translations.interfaces.translationgroup.ITranslationGroup"
             permission="launchpad.Edit"
             class="lp.translations.browser.translationgroup.TranslationGroupReassignmentView"
             template="../templates/translationgroup-reassignment.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="*"
             permission="launchpad.Edit"
             name="+object-reassignment"
             template="../../../canonical/launchpad/templates/object-reassignment.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:url
             for="lp.translations.interfaces.translationgroup.ITranslationGroupSet"
             path_expression="string:+groups"
@@ -516,21 +516,21 @@
         <browser:defaultView
             for="lp.translations.interfaces.translationgroup.ITranslationGroupSet"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+new"
             for="lp.translations.interfaces.translationgroup.ITranslationGroupSet"
             class="lp.translations.browser.translationgroup.TranslationGroupAddView"
             permission="launchpad.Admin"
             template="../../app/templates/generic-edit.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.translations.interfaces.translationgroup.ITranslationGroupSet"
             class="lp.translations.browser.translationgroup.TranslationGroupSetView"
             permission="zope.Public"
             template="../templates/translationgroups-index.pt"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:url
             for="lp.translations.interfaces.translationmessage.ITranslationMessage"
             path_expression="string:${potmsgset/sequence}"
@@ -544,13 +544,13 @@
         <browser:defaultView
             for="lp.translations.interfaces.translationmessage.ITranslationMessage"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.translations.interfaces.translationmessage.ITranslationMessage"
             class="lp.translations.browser.translationmessage.CurrentTranslationMessageIndexView"
             permission="zope.Public"
             name="+index"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+translate"
             for="lp.translations.interfaces.translationmessage.ITranslationMessage"
@@ -562,20 +562,20 @@
             permission="zope.Public"
             template="../templates/translationmessage-translate.pt"
             class="lp.translations.browser.translationmessage.CurrentTranslationMessagePageView"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             name="+display"
             for="lp.translations.interfaces.translationmessage.ITranslationMessageSuggestions"
             permission="launchpad.AnyPerson"
             template="../templates/translationmessage-suggestions.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
 
 <!-- SourcePackage translation pages -->
 
     <browser:defaultView
         for="lp.registry.interfaces.sourcepackage.ISourcePackage"
         name="+translations"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:menus
         classes="SourcePackageTranslationsMenu"
         module="lp.translations.browser.sourcepackage"/>
@@ -586,11 +586,11 @@
         class="lp.translations.browser.hastranslationimports.HasTranslationImportsView"
         permission="zope.Public"
         template="../templates/hastranslationimports-index.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="lp.registry.interfaces.sourcepackage.ISourcePackage"
         name="+export"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         permission="launchpad.ExpensiveRequest"
         template="../templates/translations-export.pt"
         class="
@@ -609,7 +609,7 @@
         for="lp.registry.interfaces.sourcepackage.ISourcePackage"
         permission="zope.Public"
         class="lp.translations.browser.sourcepackage.SourcePackageTranslationsView"
-        layer="canonical.launchpad.layers.TranslationsLayer">
+        layer="lp.translations.publisher.TranslationsLayer">
         <browser:page
             name="+potlist"
             template="../templates/object-pots.pt"/>
@@ -623,7 +623,7 @@
     <browser:defaultView
         for="lp.registry.interfaces.productseries.IProductSeries"
         name="+translations"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:menus
         classes="ProductSeriesTranslationsMenu"
         module="lp.translations.browser.productseries"/>
@@ -636,7 +636,7 @@
         for="lp.registry.interfaces.productseries.IProductSeries"
         class="lp.translations.browser.productseries.ProductSeriesView"
         permission="zope.Public"
-        layer="canonical.launchpad.layers.TranslationsLayer" >
+        layer="lp.translations.publisher.TranslationsLayer" >
         <browser:page
             name="+translations"
             template="../templates/productseries-translations.pt"
@@ -650,7 +650,7 @@
         for="lp.registry.interfaces.productseries.IProductSeries"
         class="lp.translations.browser.productseries.ProductSeriesUploadView"
         permission="launchpad.Edit"
-        layer="canonical.launchpad.layers.TranslationsLayer">
+        layer="lp.translations.publisher.TranslationsLayer">
         <browser:page
             name="+translations-upload"
             template="../templates/productseries-translations-upload.pt"/>
@@ -661,38 +661,38 @@
         class="lp.translations.browser.hastranslationimports.HasTranslationImportsView"
         permission="zope.Public"
         template="../templates/hastranslationimports-index.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="lp.registry.interfaces.productseries.IProductSeries"
         name="+translations-settings"
         class="lp.translations.browser.productseries.ProductSeriesTranslationsSettingsView"
         permission="launchpad.Edit"
         template="../templates/productseries-translations-settings.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="lp.registry.interfaces.productseries.IProductSeries"
         name="+templates"
         class="lp.translations.browser.productseries.ProductSeriesTemplatesView"
         permission="zope.Public"
         template="../templates/object-templates.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="lp.registry.interfaces.productseries.IProductSeries"
         name="+request-bzr-import"
         class="lp.translations.browser.productseries.ProductSeriesTranslationsBzrImportView"
         permission="launchpad.Edit"
         template="../templates/productseries-translations-bzr-import.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         name="+export"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         for="lp.registry.interfaces.productseries.IProductSeries"
         permission="launchpad.AnyPerson"
         template="../templates/translations-export.pt"
         class="lp.translations.browser.productseries.ProductSeriesTranslationsExportView"/>
     <browser:page
         name="+link-translations-branch"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         for="lp.registry.interfaces.productseries.IProductSeries"
         permission="launchpad.Edit"
         template="../../app/templates/generic-edit.pt"
@@ -703,7 +703,7 @@
         <browser:defaultView
             for="lp.registry.interfaces.person.IPerson"
             name="+translations"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:menus
             module="lp.translations.browser.person"
             classes="
@@ -720,7 +720,7 @@
             class="lp.translations.browser.person.PersonTranslationView"
             permission="zope.Public"
             template="../templates/person-translations.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.registry.interfaces.person.IPerson"
             name="+licensing"
@@ -728,14 +728,14 @@
               lp.translations.browser.person.PersonTranslationRelicensingView"
             permission="launchpad.Edit"
             template="../templates/person-translations-relicensing.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.registry.interfaces.person.IPerson"
             name="+imports"
             class="lp.translations.browser.hastranslationimports.HasTranslationImportsView"
             permission="zope.Public"
             template="../templates/hastranslationimports-index.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.registry.interfaces.person.IPerson"
             class="lp.translations.browser.person.PersonTranslationView"
@@ -748,35 +748,35 @@
             class="lp.translations.browser.person.TranslationActivityView"
             permission="zope.Public"
             template="../templates/person-translation-activity.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.registry.interfaces.person.IPerson"
             name="+translations-to-review"
             class="lp.translations.browser.person.PersonTranslationReviewView"
             permission="zope.Public"
             template="../templates/person-translations-to-review.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.registry.interfaces.person.IPerson"
             name="+translations-to-review-table"
             class="lp.translations.browser.person.PersonTranslationView"
             permission="zope.Public"
             template="../templates/person-translations-to-review-table.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
         <browser:page
             for="lp.registry.interfaces.person.IPerson"
             name="+translations-to-complete-table"
             class="lp.translations.browser.person.PersonTranslationView"
             permission="zope.Public"
             template="../templates/person-translations-to-complete-table.pt"
-            layer="canonical.launchpad.layers.TranslationsLayer"/>
+            layer="lp.translations.publisher.TranslationsLayer"/>
 
 
 <!-- Product views -->
 
     <browser:defaultView
         for="lp.registry.interfaces.product.IProduct"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         name="+translations"/>
     <browser:menus
         classes="
@@ -798,19 +798,19 @@
         class="lp.translations.browser.hastranslationimports.HasTranslationImportsView"
         permission="zope.Public"
         template="../templates/hastranslationimports-index.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         name="+settings"
         for="lp.registry.interfaces.product.IProduct"
         class="lp.translations.browser.product.ProductSettingsView"
         permission="launchpad.TranslationsAdmin"
         template="../templates/set-translators.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:pages
         for="lp.registry.interfaces.product.IProduct"
         permission="zope.Public"
         class="lp.translations.browser.product.ProductView"
-        layer="canonical.launchpad.layers.TranslationsLayer">
+        layer="lp.translations.publisher.TranslationsLayer">
         <browser:page
             name="+translations"
             template="../templates/product-translations.pt"/>
@@ -830,7 +830,7 @@
     <browser:defaultView
         for="lp.registry.interfaces.projectgroup.IProjectGroup"
         name="+translations"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:menus
         classes="
             ProjectTranslationsMenu"
@@ -846,7 +846,7 @@
         name="+translations"
         class="lp.translations.browser.project.ProjectView"
         template="../templates/project-translations.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         name="+help-translate-button"
         for="lp.registry.interfaces.projectgroup.IProjectGroup"
@@ -858,14 +858,14 @@
         class="lp.translations.browser.project.ProjectSettingsView"
         permission="launchpad.TranslationsAdmin"
         template="../templates/set-translators.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
 
 <!-- Distribution -->
 
     <browser:defaultView
         for="lp.registry.interfaces.distribution.IDistribution"
         name="+translations"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:menus
         classes="
             DistributionTranslationsMenu"
@@ -881,7 +881,7 @@
         permission="zope.Public"
         name="+translations"
         template="../templates/distribution-translations.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         name="+help-translate-button"
         for="lp.registry.interfaces.distribution.IDistribution"
@@ -893,35 +893,35 @@
         class="lp.translations.browser.distribution.DistributionSettingsView"
         permission="launchpad.TranslationsAdmin"
         template="../templates/set-translators.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="lp.registry.interfaces.distribution.IDistribution"
         name="+imports"
         class="lp.translations.browser.hastranslationimports.HasTranslationImportsView"
         permission="zope.Public"
         template="../templates/hastranslationimports-index.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="lp.registry.interfaces.distribution.IDistribution"
         name="+select-language-pack-admin"
         class="lp.translations.browser.distribution.DistributionLanguagePackAdminView"
         permission="launchpad.TranslationsAdmin"
         template="../../app/templates/generic-edit.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <!-- Language pack admin portlet -->
     <browser:page
         name="+language-pack-admin-info"
         for="lp.registry.interfaces.distribution.IDistribution"
         permission="launchpad.View"
         template="../templates/distribution-language-pack-admin-info.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
 
 <!-- DistroSeries -->
 
     <browser:defaultView
         for="lp.registry.interfaces.distroseries.IDistroSeries"
         name="+translations"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:menus
         classes="
             DistroSeriesTranslationsMenu"
@@ -936,7 +936,7 @@
         for="lp.registry.interfaces.distroseries.IDistroSeries"
         class="lp.translations.browser.distroseries.DistroSeriesView"
         permission="zope.Public"
-        layer="canonical.launchpad.layers.TranslationsLayer">
+        layer="lp.translations.publisher.TranslationsLayer">
         <browser:page
             name="+translations"
             template="../templates/distroseries-translations.pt"/>
@@ -948,7 +948,7 @@
         for="lp.registry.interfaces.distroseries.IDistroSeries"
         class="lp.translations.browser.distroseries.DistroSeriesTemplatesView"
         permission="zope.Public"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         name="+templates"
         template="../templates/object-templates.pt" />
     <browser:page
@@ -962,19 +962,19 @@
         class="lp.translations.browser.distroseries.DistroSeriesTranslationsAdminView"
         name="+admin"
         template="../../app/templates/generic-edit.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="lp.registry.interfaces.distroseries.IDistroSeries"
         name="+imports"
         class="lp.translations.browser.hastranslationimports.HasTranslationImportsView"
         permission="zope.Public"
         template="../templates/hastranslationimports-index.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:page
         for="lp.registry.interfaces.distroseries.IDistroSeries"
         class="lp.translations.browser.distroseries.DistroSeriesLanguagePackView"
         permission="zope.Public"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         name="+language-packs"
         template="../templates/distroseries-language-packs.pt"/>
 
@@ -984,7 +984,7 @@
     <browser:defaultView
         for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode"
         name="+index"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:url
         for="lp.translations.interfaces.customlanguagecode.ICustomLanguageCode"
         path_expression="string:+customcode/${language_code}"
@@ -996,7 +996,7 @@
         permission="zope.Public"
         class="lp.translations.browser.customlanguagecode.CustomLanguageCodeView"
         template="../templates/customlanguagecode-index.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
 
     <browser:page
         name="+remove"
@@ -1004,21 +1004,21 @@
         permission="launchpad.Admin"
         class="lp.translations.browser.customlanguagecode.CustomLanguageCodeRemoveView"
         template="../../app/templates/generic-edit.pt"
-        layer="canonical.launchpad.layers.TranslationsLayer"/>
+        layer="lp.translations.publisher.TranslationsLayer"/>
 
 <!-- IHasCustomLanguageCodes -->
 
     <browser:page
         name="+custom-language-codes"
         for="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         class="lp.translations.browser.customlanguagecode.CustomLanguageCodesIndexView"
         template="../templates/customlanguagecodes-index.pt"
         permission="zope.Public"/>
     <browser:page
         name="+add-custom-language-code"
         for="lp.translations.interfaces.customlanguagecode.IHasCustomLanguageCodes"
-        layer="canonical.launchpad.layers.TranslationsLayer"
+        layer="lp.translations.publisher.TranslationsLayer"
         class="lp.translations.browser.customlanguagecode.CustomLanguageCodeAddView"
         template="../templates/customlanguagecode-add.pt"
         permission="launchpad.Admin"/>

=== modified file 'lib/lp/translations/browser/tests/language-views.txt'
--- lib/lp/translations/browser/tests/language-views.txt	2010-04-21 13:08:48 +0000
+++ lib/lp/translations/browser/tests/language-views.txt	2010-07-21 05:53:58 +0000
@@ -6,7 +6,7 @@
 
     >>> from zope.component import getUtility
     >>> from lp.services.worlddata.interfaces.language import ILanguageSet
-    >>> from canonical.launchpad.layers import TranslationsLayer
+    >>> from lp.translations.publisher import TranslationsLayer
 
     >>> language_set = getUtility(ILanguageSet)
     >>> portuguese = language_set.getLanguageByCode('pt_BR')

=== modified file 'lib/lp/translations/browser/tests/poexport-request-views.txt'
--- lib/lp/translations/browser/tests/poexport-request-views.txt	2009-07-02 17:16:50 +0000
+++ lib/lp/translations/browser/tests/poexport-request-views.txt	2010-07-21 05:53:58 +0000
@@ -1,7 +1,7 @@
 PO Export Request Browser Views
 -------------------------------
 
-    >>> from canonical.launchpad.layers import TranslationsLayer
+    >>> from lp.translations.publisher import TranslationsLayer
 
 Check there are no requests in the database to start with.
 

=== modified file 'lib/lp/translations/browser/tests/pofile-views.txt'
--- lib/lp/translations/browser/tests/pofile-views.txt	2010-01-20 22:41:54 +0000
+++ lib/lp/translations/browser/tests/pofile-views.txt	2010-07-21 05:53:58 +0000
@@ -15,7 +15,7 @@
     >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> from lp.registry.interfaces.sourcepackagename import (
     ...     ISourcePackageNameSet)
-    >>> from canonical.launchpad.layers import TranslationsLayer
+    >>> from lp.translations.publisher import TranslationsLayer
 
 All the tests will be submitted as coming from the No Privilege person.
 

=== modified file 'lib/lp/translations/browser/tests/potemplate-views.txt'
--- lib/lp/translations/browser/tests/potemplate-views.txt	2009-07-02 17:16:50 +0000
+++ lib/lp/translations/browser/tests/potemplate-views.txt	2010-07-21 05:53:58 +0000
@@ -13,7 +13,7 @@
     >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> from lp.registry.interfaces.sourcepackagename import (
     ...     ISourcePackageNameSet)
-    >>> from canonical.launchpad.layers import TranslationsLayer
+    >>> from lp.translations.publisher import TranslationsLayer
     >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
 
 All the tests will be submitted as comming from the No Privilege person.

=== modified file 'lib/lp/translations/browser/tests/test_translationimportqueueentry.py'
--- lib/lp/translations/browser/tests/test_translationimportqueueentry.py	2010-04-19 15:24:29 +0000
+++ lib/lp/translations/browser/tests/test_translationimportqueueentry.py	2010-07-21 05:53:58 +0000
@@ -12,12 +12,13 @@
 
 from canonical.testing import LaunchpadFunctionalLayer
 
-from canonical.launchpad.layers import TranslationsLayer, setFirstLayer
+from canonical.launchpad.layers import setFirstLayer
 from canonical.launchpad.webapp import canonical_url
 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
 from lp.testing import TestCaseWithFactory
 from lp.translations.interfaces.translationimportqueue import (
     ITranslationImportQueue)
+from lp.translations.publisher import TranslationsLayer
 
 
 class TestTranslationImportQueueEntryView(TestCaseWithFactory):

=== modified file 'lib/lp/translations/browser/tests/translationimportqueue-views.txt'
--- lib/lp/translations/browser/tests/translationimportqueue-views.txt	2009-07-02 17:16:50 +0000
+++ lib/lp/translations/browser/tests/translationimportqueue-views.txt	2010-07-21 05:53:58 +0000
@@ -5,11 +5,12 @@
 
     >>> from zope.component import getUtility, getMultiAdapter
     >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+    >>> from canonical.launchpad.layers import setFirstLayer
     >>> from lp.translations.interfaces.translationimportqueue import (
     ...     ITranslationImportQueue)
     >>> from lp.translations.browser.translationimportqueue import (
     ...     TranslationImportQueueEntryView)
-    >>> from canonical.launchpad.layers import TranslationsLayer, setFirstLayer
+    >>> from lp.translations.publisher import TranslationsLayer
 
 This view is only accessible for administrators.
 

=== modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
--- lib/lp/translations/browser/tests/translationmessage-views.txt	2010-03-22 17:18:28 +0000
+++ lib/lp/translations/browser/tests/translationmessage-views.txt	2010-07-21 05:53:58 +0000
@@ -7,7 +7,7 @@
     >>> from lp.translations.model.pofile import POFile
     >>> from lp.translations.model.translationmessage import (
     ...     TranslationMessage)
-    >>> from canonical.launchpad.layers import TranslationsLayer
+    >>> from lp.translations.publisher import TranslationsLayer
     >>> from canonical.launchpad.webapp import canonical_url
 
 All the tests will be submitted as comming from Kurem, an editor for the POFile

=== modified file 'lib/lp/translations/browser/tests/translator-views.txt'
--- lib/lp/translations/browser/tests/translator-views.txt	2009-09-17 11:28:13 +0000
+++ lib/lp/translations/browser/tests/translator-views.txt	2010-07-21 05:53:58 +0000
@@ -23,7 +23,7 @@
 
 Translator +admin view provides a nice page title and a form label.
 
-    >>> from canonical.launchpad.layers import TranslationsLayer
+    >>> from lp.translations.publisher import TranslationsLayer
     >>> view = create_initialized_view(translator, '+admin',
     ...                                layer=TranslationsLayer)
     >>> print view.label
@@ -45,7 +45,7 @@
 for a language in a TranslationGroup, and page title and form label
 describe that appropriately.
 
-    >>> from canonical.launchpad.layers import TranslationsLayer
+    >>> from lp.translations.publisher import TranslationsLayer
     >>> view = create_initialized_view(translator, '+edit',
     ...                                layer=TranslationsLayer)
     >>> print view.label
@@ -66,7 +66,7 @@
 Translator +edit view allows one to only set translation guidelines
 for a language in a TranslationGroup.
 
-    >>> from canonical.launchpad.layers import TranslationsLayer
+    >>> from lp.translations.publisher import TranslationsLayer
     >>> view = create_initialized_view(translator, '+remove',
     ...                                layer=TranslationsLayer)
     >>> print view.label

=== modified file 'lib/lp/translations/browser/translations.py'
--- lib/lp/translations/browser/translations.py	2010-02-01 21:20:57 +0000
+++ lib/lp/translations/browser/translations.py	2010-07-21 05:53:58 +0000
@@ -27,7 +27,7 @@
 from lp.registry.interfaces.product import IProductSet
 from lp.services.worlddata.interfaces.country import ICountry
 from lp.registry.interfaces.person import IPersonSet
-from canonical.launchpad.layers import TranslationsLayer
+from lp.translations.publisher import TranslationsLayer
 from canonical.launchpad.webapp import (
     LaunchpadView, Navigation, stepto, canonical_url)
 from canonical.launchpad.webapp.batching import BatchNavigator

=== modified file 'lib/lp/translations/configure.zcml'
--- lib/lp/translations/configure.zcml	2010-05-04 13:42:25 +0000
+++ lib/lp/translations/configure.zcml	2010-07-21 05:53:58 +0000
@@ -13,8 +13,12 @@
     <include
         package=".browser"/>
 
+    <publisher
+       name="translations"
+       factory="lp.translations.publisher.translations_request_publication_factory"/>
+
     <lp:help-folder
-        folder="help" type="canonical.launchpad.layers.TranslationsLayer" />
+        folder="help" type="lp.translations.publisher.TranslationsLayer" />
 
     <class
         class="lp.translations.model.vpotexport.VPOTExport">

=== modified file 'lib/lp/translations/doc/rosetta-karma.txt'
--- lib/lp/translations/doc/rosetta-karma.txt	2010-02-16 20:36:48 +0000
+++ lib/lp/translations/doc/rosetta-karma.txt	2010-07-21 05:53:58 +0000
@@ -364,7 +364,7 @@
 product from where the IPOTemplate is and he has rights to change the
 description.
 
-    >>> from canonical.launchpad.layers import TranslationsLayer
+    >>> from lp.translations.publisher import TranslationsLayer
     >>> sample_person = personset.getByEmail('test@xxxxxxxxxxxxx')
     >>> login('test@xxxxxxxxxxxxx')
     >>> form = {

=== added file 'lib/lp/translations/publisher.py'
--- lib/lp/translations/publisher.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/publisher.py	2010-07-21 05:53:58 +0000
@@ -0,0 +1,41 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Translations's custom publication."""
+
+__metaclass__ = type
+__all__ = [
+    'TranslationsBrowserRequest',
+    'TranslationsLayer',
+    'translations_request_publication_factory',
+    ]
+
+
+from zope.interface import implements
+from zope.publisher.interfaces.browser import (
+    IBrowserRequest, IDefaultBrowserLayer)
+
+from canonical.launchpad.webapp.publication import LaunchpadBrowserPublication
+from canonical.launchpad.webapp.servers import (
+    LaunchpadBrowserRequest, VHostWebServiceRequestPublicationFactory)
+
+
+class TranslationsLayer(IBrowserRequest, IDefaultBrowserLayer):
+    """The Translations layer."""
+
+
+class TranslationsBrowserRequest(LaunchpadBrowserRequest):
+    """Instances of TranslationsBrowserRequest provide `TranslationsLayer`."""
+    implements(TranslationsLayer)
+
+    def __init__(self, body_instream, environ, response=None):
+        super(TranslationsBrowserRequest, self).__init__(
+            body_instream, environ, response)
+        # Many of the responses from Translations vary based on language.
+        self.response.setHeader(
+            'Vary', 'Cookie, Authorization, Accept-Language')
+
+
+def translations_request_publication_factory():
+    return VHostWebServiceRequestPublicationFactory(
+        'translations', TranslationsBrowserRequest, LaunchpadBrowserPublication)

=== added file 'lib/lp/translations/tests/test_publisher.py'
--- lib/lp/translations/tests/test_publisher.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/tests/test_publisher.py	2010-07-21 05:53:58 +0000
@@ -0,0 +1,55 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for translations's custom publications."""
+
+__metaclass__ = type
+
+import StringIO
+import unittest
+
+from canonical.config import config
+from canonical.launchpad.layers import WebServiceLayer
+from canonical.testing.layers import FunctionalLayer
+
+from lp.testing import TestCase
+from lp.testing.publication import get_request_and_publication
+
+from lp.translations.publisher import (
+    TranslationsLayer, TranslationsBrowserRequest)
+
+
+class TestRegistration(TestCase):
+    """Translations's publication customizations are installed correctly."""
+
+    layer = FunctionalLayer
+
+    def test_translations_request_provides_translations_layer(self):
+        # The request constructed for requests to the translations hostname
+        # provides TranslationsLayer.
+        request, publication = get_request_and_publication(
+            host=config.vhost.translations.hostname)
+        self.assertProvides(request, TranslationsLayer)
+
+    def test_translations_host_has_api(self):
+        # Requests to /api on the translations domain are treated as web
+        # service requests.
+        request, publication = get_request_and_publication(
+            host=config.vhost.translations.hostname,
+            extra_environment={'PATH_INFO': '/api/1.0'})
+        # XXX MichaelHudson, 2010-07-20, bug=607664: WebServiceLayer only
+        # actually provides WebServiceLayer in the sense of verifyObject after
+        # traversal has started.
+        self.assertTrue(WebServiceLayer.providedBy(request))
+
+    def test_response_should_vary_based_on_language(self):
+        # Responses to requests to translations pages have the 'Vary' header
+        # set to include Accept-Language.
+        request = TranslationsBrowserRequest(StringIO.StringIO(''), {})
+        self.assertEquals(
+            request.response.getHeader('Vary'),
+            'Cookie, Authorization, Accept-Language')
+
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromName(__name__)


Follow ups