← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~launchpad/launchpad/translation-sharing-status into lp:launchpad

 

Henning Eggers has proposed merging lp:~launchpad/launchpad/translation-sharing-status into lp:launchpad.

Requested reviews:
  Launchpad UI Reviewers (launchpad-ui-reviewers): ui
  Launchpad code reviewers (launchpad-reviewers): code
Related bugs:
  Bug #732633 in Launchpad itself: "Need "web 1.0" page for displaying/editing translations sharing info"
  https://bugs.launchpad.net/launchpad/+bug/732633

For more details, see:
https://code.launchpad.net/~launchpad/launchpad/translation-sharing-status/+merge/53419

Details
=======

This branch adds the new +sharing-details page that displays information about the sharing configuration and the current sharing state for each sharing template in a source package.

Here are some screenshots of the page:
http://people.canonical.com/~henninge/screenshots/sharing-details-page-unconfigured.png
http://people.canonical.com/~henninge/screenshots/sharing-details-page-incomplete.png
http://people.canonical.com/~henninge/screenshots/sharing-details-page-notice.png
http://people.canonical.com/~henninge/screenshots/sharing-details-page.png

The first part is a checklist that can be followed to set up upstream translation sharing. It indicates if each step has been completed an provides links to the respective places on Launchpad where these configurations can be performed.

The second part is a table of all templates in both the sourcepackage and the upstream project. The information is useful for maintainers to have an overview if all templates are sharing properly and if any of them may need updating.

Implementation details
----------------------

We (adeuring, heninge) worked on this branch together, so it is owned by ~launchpad. Will be interesting to see how that works through our merge machinery ...

The edit links in the checklist use menu items from the upstream product series. Thus the icon as well as the permissions are as they are defined in that menu. One item did not have an icon, so this was added.

The rest of the implementation is pretty straight forward: view class, template, page test. The view is hidden behind a feature flag, it will raise NotFound if the flag is not set.

Test
----

bin/test -vvvcm lp.translations.browser.tests.test_sharing_details


-- 
https://code.launchpad.net/~launchpad/launchpad/translation-sharing-status/+merge/53419
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~launchpad/launchpad/translation-sharing-status into lp:launchpad.
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2011-03-11 04:11:40 +0000
+++ lib/lp/testing/factory.py	2011-03-15 12:12:55 +0000
@@ -1080,10 +1080,10 @@
         if sourcepackagename is None or isinstance(sourcepackagename, str):
             sourcepackagename = self.makeSourcePackageName(sourcepackagename)
         if distroseries is None:
-            distribution = None
             if in_ubuntu:
-                distribution = getUtility(ILaunchpadCelebrities).ubuntu
-            distroseries = self.makeDistroSeries(distribution=distribution)
+                distroseries = self.makeUbuntuDistroSeries()
+            else:
+                distroseries = self.makeDistroSeries()
         if packaging_type is None:
             packaging_type = PackagingType.PRIME
         if owner is None:

=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml	2011-02-14 04:19:37 +0000
+++ lib/lp/translations/browser/configure.zcml	2011-03-15 12:12:55 +0000
@@ -605,6 +605,13 @@
         for="lp.registry.interfaces.sourcepackage.ISourcePackage"
         class="lp.translations.browser.translations.TranslationsRedirectView"
         permission="zope.Public"/>
+    <browser:page
+        for="lp.registry.interfaces.sourcepackage.ISourcePackage"
+        name="+sharing-details"
+        class="lp.translations.browser.sourcepackage.SourcePackageTranslationSharingDetailsView"
+        permission="zope.Public"
+        template="../templates/sourcepackage-sharing-details.pt"
+        layer="lp.translations.publisher.TranslationsLayer"/>
     <browser:pages
         for="lp.registry.interfaces.sourcepackage.ISourcePackage"
         permission="zope.Public"

=== modified file 'lib/lp/translations/browser/productseries.py'
--- lib/lp/translations/browser/productseries.py	2011-03-02 17:49:15 +0000
+++ lib/lp/translations/browser/productseries.py	2011-03-15 12:12:55 +0000
@@ -85,7 +85,9 @@
     @enabled_with_permission('launchpad.Edit')
     def settings(self):
         """Return a link to configure the translations settings."""
-        return Link('+translations-settings', 'Settings', site='translations')
+        return Link(
+            '+translations-settings', 'Settings',
+            site='translations', icon='edit')
 
     @enabled_with_permission('launchpad.Edit')
     def requestbzrimport(self):

=== modified file 'lib/lp/translations/browser/sourcepackage.py'
--- lib/lp/translations/browser/sourcepackage.py	2011-03-08 09:59:36 +0000
+++ lib/lp/translations/browser/sourcepackage.py	2011-03-15 12:12:55 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Browser views for translation pages for sourcepackages."""
@@ -8,8 +8,11 @@
 __all__ = [
     'SourcePackageTranslationsExportView',
     'SourcePackageTranslationsView',
+    'SourcePackageTranslationSharingStatus',
     ]
 
+from zope.publisher.interfaces import NotFound
+
 from canonical.launchpad.webapp import (
     canonical_url,
     enabled_with_permission,
@@ -17,19 +20,33 @@
     NavigationMenu,
     )
 from canonical.launchpad.webapp.authorization import check_permission
+from canonical.launchpad.webapp.menu import structured
+from canonical.launchpad.webapp.publisher import LaunchpadView
+from lp.app.enums import ServiceUsage
 from lp.registry.interfaces.sourcepackage import ISourcePackage
+from lp.services.features import getFeatureFlag
 from lp.translations.browser.poexportrequest import BaseExportView
 from lp.translations.browser.translations import TranslationsMixin
 from lp.translations.browser.translationsharing import (
     TranslationSharingDetailsMixin,
     )
+from lp.translations.interfaces.translations import (
+    TranslationsBranchImportMode,
+    )
 from lp.translations.utilities.translationsharinginfo import (
     has_upstream_template,
     get_upstream_sharing_info,
     )
 
 
+class SharingDetailsPermissionsMixin:
+
+    def can_edit_sharing_details(self):
+        return check_permission('launchpad.Edit', self.context.distroseries)
+
+
 class SourcePackageTranslationsView(TranslationsMixin,
+                                    SharingDetailsPermissionsMixin,
                                     TranslationSharingDetailsMixin):
 
     @property
@@ -56,9 +73,6 @@
         """See `TranslationSharingDetailsMixin`."""
         return self.context
 
-    def can_edit_sharing_details(self):
-        return check_permission('launchpad.Edit', self.context.distroseries)
-
 
 class SourcePackageTranslationsMenu(NavigationMenu):
     usedfor = ISourcePackage
@@ -100,3 +114,117 @@
     @property
     def label(self):
         return "Download translations for %s" % self.download_description
+
+
+class SourcePackageTranslationSharingDetailsView(
+                                            LaunchpadView,
+                                            SharingDetailsPermissionsMixin):
+    """Details about translation sharing."""
+
+    page_title = "Sharing details"
+
+    def initialize(self):
+        if not getFeatureFlag('translations.sharing_information.enabled'):
+            raise NotFound(self.context, '+sharing-details')
+        super(SourcePackageTranslationSharingDetailsView, self).initialize()
+        has_no_upstream_templates = (
+            self.is_configuration_complete and
+            not has_upstream_template(self.context))
+        if has_no_upstream_templates:
+            self.request.response.addInfoNotification(
+                structured(
+                'No upstream templates have been found yet. Please follow '
+                'the import process by going to the '
+                '<a href="%s">Translation Import Queue</a> of the '
+                'upstream project series.' %(
+                canonical_url(
+                    self.context.productseries, rootsite='translations',
+                    view_name="+imports"))))
+
+    @property
+    def is_packaging_configured(self):
+        """Is a packaging link defined for this branch?"""
+        return self.context.direct_packaging is not None
+
+    @property
+    def no_item_class(self):
+        """CSS class for 'no' items."""
+        css_class = "sprite no"
+        if self.is_packaging_configured:
+            return css_class
+        else:
+            return css_class + " lowlight"
+
+    @property
+    def has_upstream_branch(self):
+        """Does the upstream series have a source code branch?"""
+        if not self.is_packaging_configured:
+            return False
+        return self.context.direct_packaging.productseries.branch is not None
+
+    @property
+    def is_upstream_translations_enabled(self):
+        """Are Launchpad translations enabled for the upstream series?"""
+        if not self.is_packaging_configured:
+            return False
+        product = self.context.direct_packaging.productseries.product
+        return product.translations_usage in (
+            ServiceUsage.LAUNCHPAD, ServiceUsage.EXTERNAL)
+
+    @property
+    def is_upstream_synchronization_enabled(self):
+        """Is automatic synchronization of upstream translations enabled?"""
+        if not self.is_packaging_configured:
+            return False
+        series = self.context.direct_packaging.productseries
+        return (
+            series.translations_autoimport_mode ==
+            TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
+
+    @property
+    def is_configuration_complete(self):
+        """Is anything missing in the set up for translation sharing?"""
+        # A check if the required packaging link exists is implicitly
+        # done in the implementation of the other properties.
+        return (
+            self.has_upstream_branch and
+            self.is_upstream_translations_enabled and
+            self.is_upstream_synchronization_enabled)
+
+    def template_info(self):
+        """Details about translation templates.
+
+        :return: A list of dictionaries containing details about each
+            template. Each dictionary contains:
+                'name': The name of the template
+                'package_template': The package template (may be None)
+                'upstream_template': The corresponding upstream template
+                    (may be None)
+                'status': one of the string 'linking', 'shared',
+                    'only in Ubuntu', 'only in upstream'
+        """
+        info = {}
+        templates_on_this_side = self.context.getCurrentTranslationTemplates()
+        for template in templates_on_this_side:
+            info[template.name] = {
+                'name': template.name,
+                'package_template': template,
+                'upstream_template': None,
+                'status': 'only in Ubuntu',
+                }
+        if self.is_configuration_complete:
+            upstream_templates = (
+                self.context.productseries.getCurrentTranslationTemplates())
+            for template in upstream_templates:
+                if template.name in info:
+                    info[template.name]['upstream_template'] = template
+                    info[template.name]['status'] = 'shared'
+                else:
+                    info[template.name] = {
+                        'name': template.name,
+                        'package_template': None,
+                        'upstream_template': template,
+                        'status': 'only in upstream',
+                        }
+        info = info.values()
+        return sorted(info, key=lambda template: template['name'])

=== added file 'lib/lp/translations/browser/tests/test_sharing_details.py'
--- lib/lp/translations/browser/tests/test_sharing_details.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/browser/tests/test_sharing_details.py	2011-03-15 12:12:55 +0000
@@ -0,0 +1,450 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+from canonical.launchpad.testing.pages import (
+    extract_text,
+    find_tag_by_id,
+    get_feedback_messages,
+    )
+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+from canonical.testing.layers import (
+    DatabaseFunctionalLayer,
+    )
+from lp.app.enums import ServiceUsage
+from lp.services.features.testing import FeatureFixture
+from lp.testing import (
+    BrowserTestCase,
+    person_logged_in,
+    TestCaseWithFactory,
+    )
+from lp.translations.browser.sourcepackage import (
+    SourcePackageTranslationSharingDetailsView,
+    )
+from lp.translations.interfaces.translations import (
+    TranslationsBranchImportMode,
+    )
+
+
+class ConfigureUpstreamProjectMixin:
+    """Provide a method for project configuration."""
+
+    def configureUpstreamProject(self, productseries,
+            set_upstream_branch=False,
+            translations_usage=ServiceUsage.UNKNOWN,
+            translation_import_mode=TranslationsBranchImportMode.NO_IMPORT):
+        """Configure the productseries and its product as an upstream project.
+        """
+        with person_logged_in(productseries.product.owner):
+            if set_upstream_branch:
+                productseries.branch = self.factory.makeBranch(
+                    product=productseries.product)
+            productseries.product.translations_usage = translations_usage
+            productseries.translations_autoimport_mode = (
+                translation_import_mode)
+
+
+class TestSourcePackageTranslationSharingDetailsView(TestCaseWithFactory,
+                                            ConfigureUpstreamProjectMixin):
+    """Tests for SourcePackageTranslationSharingStatus."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestSourcePackageTranslationSharingDetailsView, self).setUp()
+        self.useFixture(FeatureFixture(
+            {'translations.sharing_information.enabled': 'on'}))
+        distroseries = self.factory.makeUbuntuDistroSeries()
+        self.sourcepackage = self.factory.makeSourcePackage(
+            distroseries=distroseries)
+        self.ubuntu_only_template = self.factory.makePOTemplate(
+            sourcepackage=self.sourcepackage, name='ubuntu-only')
+        self.shared_template_ubuntu_side = self.factory.makePOTemplate(
+            sourcepackage=self.sourcepackage, name='shared-template')
+        self.productseries = self.factory.makeProductSeries()
+        self.shared_template_upstream_side = self.factory.makePOTemplate(
+            productseries=self.productseries, name='shared-template')
+        self.upstream_only_template = self.factory.makePOTemplate(
+            productseries=self.productseries, name='upstream-only')
+        self.view = SourcePackageTranslationSharingDetailsView(
+            self.sourcepackage, LaunchpadTestRequest())
+        self.view.initialize()
+
+    def configureSharing(self,
+            set_upstream_branch=False,
+            translations_usage=ServiceUsage.UNKNOWN,
+            translation_import_mode=TranslationsBranchImportMode.NO_IMPORT):
+        """Configure translation sharing, at least partially.
+
+        A packaging link is always set; the remaining configuration is
+        done only if explicitly specified.
+        """
+        self.sourcepackage.setPackaging(
+            self.productseries, self.productseries.owner)
+        self.configureUpstreamProject(
+            self.productseries, set_upstream_branch, translations_usage,
+            translation_import_mode)
+
+    def test_is_packaging_configured__not_configured(self):
+        # If a sourcepackage is not linked to a product series,
+        # SourcePackageTranslationSharingStatus.is_packaging_configured
+        # returns False.
+        self.assertFalse(self.view.is_packaging_configured)
+
+    def test_is_packaging_configured__configured(self):
+        # If a sourcepackage is linked to a product series,
+        # SourcePackageTranslationSharingStatus.is_packaging_configured
+        # returns True.
+        self.configureSharing()
+        self.assertTrue(self.view.is_packaging_configured)
+
+    def test_has_upstream_branch__no_packaging_link(self):
+        # If the source package is not linked to an upstream series,
+        # SourcePackageTranslationSharingStatus.has_upstream_branch
+        # returns False.
+        self.assertFalse(self.view.has_upstream_branch)
+
+    def test_has_upstream_branch__no_branch_exists(self):
+        # If the upstream product series does not have any source
+        # code branch,
+        # SourcePackageTranslationSharingStatus.has_upstream_branch
+        # returns False.
+        self.configureSharing()
+        self.assertFalse(self.view.has_upstream_branch)
+
+    def test_has_upstream_branch__branch_exists(self):
+        # If the upstream product series has at least one  source
+        # code branch,
+        # SourcePackageTranslationSharingStatus.has_upstream_branch
+        # returns True.
+        self.configureSharing(set_upstream_branch=True)
+        self.assertTrue(self.view.has_upstream_branch)
+
+    def test_is_upstream_translations_enabled__no_packaging_link(self):
+        # If the source package is not linked to an upstream series,
+        # is_upstream_translations_enabled returns False.
+        self.assertFalse(self.view.is_upstream_translations_enabled)
+
+    def test_is_upstream_translations_enabled__when_unknown(self):
+        # If it is unknown what the upstream project uses for
+        # translations, is_upstream_translations_enabled returns False.
+        self.configureSharing(translations_usage=ServiceUsage.UNKNOWN)
+        self.assertFalse(self.view.is_upstream_translations_enabled)
+
+    def test_is_upstream_translations_enabled__when_launchpad(self):
+        # If the upstream product series uses Launchpad for
+        # translations, is_upstream_translations_enabled returns True.
+        self.configureSharing(translations_usage=ServiceUsage.LAUNCHPAD)
+        self.assertTrue(self.view.is_upstream_translations_enabled)
+
+    def test_is_upstream_translations_enabled__when_external(self):
+        # If the upstream product series uses an external tool for
+        # translations, is_upstream_translations_enabled returns True.
+        self.configureSharing(translations_usage=ServiceUsage.EXTERNAL)
+        self.assertTrue(self.view.is_upstream_translations_enabled)
+
+    def test_is_upstream_translations_enabled__when_not_applicable(self):
+        # If the upstream product series does not do translations at all,
+        # is_upstream_translations_enabled returns False.
+        self.configureSharing(translations_usage=ServiceUsage.NOT_APPLICABLE)
+        self.assertFalse(self.view.is_upstream_translations_enabled)
+
+    def test_is_upstream_synchronization_enabled__no_packaging_link(self):
+        # If the source package is not linked to an upstream series,
+        # is_upstream_synchronization_enabled returns False.
+        self.assertFalse(self.view.is_upstream_synchronization_enabled)
+
+    def test_is_upstream_synchronization_enabled__no_import(self):
+        # If the source package is not linked to an upstream series,
+        # is_upstream_synchronization_enabled returns False.
+        self.configureSharing(
+            translation_import_mode=TranslationsBranchImportMode.NO_IMPORT)
+        self.assertFalse(self.view.is_upstream_synchronization_enabled)
+
+    def test_is_upstream_synchronization_enabled__import_templates(self):
+        # If the source package is not linked to an upstream series,
+        # is_upstream_synchronization_enabled returns False.
+        self.configureSharing(
+            translation_import_mode=
+                TranslationsBranchImportMode.IMPORT_TEMPLATES)
+        self.assertFalse(self.view.is_upstream_synchronization_enabled)
+
+    def test_is_upstream_synchronization_enabled__import_translations(self):
+        # If the source package is not linked to an upstream series,
+        # is_upstream_synchronization_enabled returns False.
+        self.configureSharing(
+            translation_import_mode=
+                TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
+        self.assertTrue(self.view.is_upstream_synchronization_enabled)
+
+    def test_is_configuration_complete__nothing_configured(self):
+        # If none of the conditions for translation sharing are
+        # fulfilled (the default test setup), is_configuration_complete
+        # is False.
+        self.assertFalse(self.view.is_configuration_complete)
+
+    def test_is_configuration_complete__only_packaging_set(self):
+        # If the packaging link is set but the other conditions for
+        # translation sharing are not fulfilled, is_configuration_complete
+        # is False.
+        self.configureSharing()
+        self.assertFalse(self.view.is_configuration_complete)
+
+    def test_is_configuration_complete__packaging_upstream_branch_set(self):
+        # If the packaging link is set and if an upstream branch is
+        # configuerd but if the other conditions are not fulfilled,
+        # is_configuration_complete is False.
+        self.configureSharing(set_upstream_branch=True)
+        self.assertFalse(self.view.is_configuration_complete)
+
+    def test_is_configuration_complete__packaging_transl_enabled(self):
+        # If the packaging link is set and if an upstream series
+        # uses Launchpad translations but if the other conditions
+        # are not fulfilled, is_configuration_complete is False.
+        self.configureSharing(translations_usage=ServiceUsage.LAUNCHPAD)
+        self.assertFalse(self.view.is_configuration_complete)
+
+    def test_is_configuration_complete__no_auto_sync(self):
+        # If
+        #   - a packaging link is set
+        #   - a branch is set for the upstream series
+        #   - the upstream series uses Launchpad translations
+        # but if the upstream series does not synchronize translations
+        # then is_configuration_complete is False.
+        self.configureSharing(
+            set_upstream_branch=True,
+            translations_usage=ServiceUsage.LAUNCHPAD)
+        self.assertFalse(self.view.is_configuration_complete)
+
+    def test_is_configuration_complete__all_conditions_fulfilled(self):
+        # If
+        #   - a packaging link is set
+        #   - a branch is set for the upstream series
+        #   - the upstream series uses Launchpad translations
+        #   - the upstream series synchronizes translations
+        # then is_configuration_complete is True.
+        self.configureSharing(
+            set_upstream_branch=True,
+            translations_usage=ServiceUsage.LAUNCHPAD,
+            translation_import_mode=
+                TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
+        self.assertTrue(self.view.is_configuration_complete)
+
+    def test_template_info__no_sharing(self):
+        # If translation sharing is not configured,
+        # SourcePackageTranslationSharingDetailsView.info returns
+        # only data about templates in Ubuntu.
+        expected = [
+            {
+                'name': 'shared-template',
+                'status': 'only in Ubuntu',
+                'package_template': self.shared_template_ubuntu_side,
+                'upstream_template': None,
+                },
+            {
+                'name': 'ubuntu-only',
+                'status': 'only in Ubuntu',
+                'package_template': self.ubuntu_only_template,
+                'upstream_template': None,
+                },
+            ]
+        self.assertEqual(expected, self.view.template_info())
+
+    def test_template_info___sharing(self):
+        # If translation sharing is configured,
+        # SourcePackageTranslationSharingDetailsView.info returns
+        # only data about templates in Ubuntu and about upstream
+        # templates.
+        self.configureSharing(
+            set_upstream_branch=True,
+            translations_usage=ServiceUsage.LAUNCHPAD,
+            translation_import_mode=
+                TranslationsBranchImportMode.IMPORT_TRANSLATIONS)
+        expected = [
+            {
+                'name': 'shared-template',
+                'status': 'shared',
+                'package_template': self.shared_template_ubuntu_side,
+                'upstream_template': self.shared_template_upstream_side,
+                },
+            {
+                'name': 'ubuntu-only',
+                'status': 'only in Ubuntu',
+                'package_template': self.ubuntu_only_template,
+                'upstream_template': None,
+                },
+            {
+                'name': 'upstream-only',
+                'status': 'only in upstream',
+                'package_template': None,
+                'upstream_template': self.upstream_only_template,
+                },
+            ]
+        self.assertEqual(expected, self.view.template_info())
+
+
+class TestSourcePackageSharingDetailsPage(BrowserTestCase,
+                                          ConfigureUpstreamProjectMixin):
+    """Test for the sharing details page of a source package."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestSourcePackageSharingDetailsPage, self).setUp()
+        self.useFixture(FeatureFixture(
+            {'translations.sharing_information.enabled': 'on'}))
+
+    def _makeSourcePackage(self):
+        """Make a source package in Ubuntu."""
+        distroseries = self.factory.makeUbuntuDistroSeries()
+        return self.factory.makeSourcePackage(distroseries=distroseries)
+
+    def _makeFullyConfiguredSharing(self):
+        """Remove some redundant code from the tests."""
+        packaging = self.factory.makePackagingLink(in_ubuntu=True)
+        productseries = packaging.productseries
+        sourcepackage = packaging.sourcepackage
+        self.configureUpstreamProject(
+            productseries,
+            set_upstream_branch=True,
+            translations_usage=ServiceUsage.LAUNCHPAD,
+            translation_import_mode=(
+                TranslationsBranchImportMode.IMPORT_TRANSLATIONS))
+        return (sourcepackage, productseries)
+
+    def test_checklist_unconfigured(self):
+        # Without a packaging link, sharing is completely unconfigured
+        sourcepackage = self._makeSourcePackage()
+        browser = self.getViewBrowser(
+            sourcepackage, no_login=True, rootsite="translations",
+            view_name="+sharing-details")
+        checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
+        self.assertIsNot(None, checklist)
+        self.assertTextMatchesExpressionIgnoreWhitespace("""
+            Translation sharing configuration is incomplete.
+            No upstream project series has been linked. Change upstream link
+            No source branch exists for the upstream series.
+            Translations are not enabled on the upstream series.
+            Automatic synchronization of translations is not enabled.""",
+            extract_text(checklist))
+
+    def test_checklist_partly_configured(self):
+        # Linking a source package takes care of one item.
+        packaging = self.factory.makePackagingLink(in_ubuntu=True)
+        browser = self.getViewBrowser(
+            packaging.sourcepackage, no_login=True, rootsite="translations",
+            view_name="+sharing-details")
+        checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
+        self.assertIsNot(None, checklist)
+        self.assertTextMatchesExpressionIgnoreWhitespace("""
+            Translation sharing configuration is incomplete.
+            Linked upstream series is .+ trunk series.
+                Change upstream link Remove upstream link
+            No source branch exists for the upstream series.
+            Translations are not enabled on the upstream series.
+            Automatic synchronization of translations is not enabled.""",
+            extract_text(checklist))
+
+    def test_checklist_fully_configured(self):
+        # A fully configured sharing setup.
+        sourcepackage = self._makeFullyConfiguredSharing()[0]
+        browser = self.getViewBrowser(
+            sourcepackage, no_login=True, rootsite="translations",
+            view_name="+sharing-details")
+        checklist = find_tag_by_id(browser.contents, 'sharing-checklist')
+        self.assertIsNot(None, checklist)
+        self.assertTextMatchesExpressionIgnoreWhitespace("""
+            Translation sharing with upstream is active.
+            Linked upstream series is .+ trunk series.
+                Change upstream link Remove upstream link
+            Upstream source branch is .+[.]
+            Translations are enable on the upstream project.
+            Automatic synchronization of translations is enabled.""",
+            extract_text(checklist))
+
+    def test_potlist_only_ubuntu(self):
+        # Without a packaging link, only Ubuntu templates are listed.
+        sourcepackage = self._makeSourcePackage()
+        self.factory.makePOTemplate(
+            name='foo-template', sourcepackage=sourcepackage)
+        browser = self.getViewBrowser(
+            sourcepackage, no_login=True, rootsite="translations",
+            view_name="+sharing-details")
+        tbody = find_tag_by_id(
+            browser.contents, 'template-table').find('tbody')
+        self.assertIsNot(None, tbody)
+        self.assertTextMatchesExpressionIgnoreWhitespace("""
+            foo-template  only in Ubuntu  0  \d+ second(s)? ago""",
+            extract_text(tbody))
+
+    def test_potlist_sharing(self):
+        # With sharing configured, templates on both sides are listed.
+        sourcepackage, productseries = self._makeFullyConfiguredSharing()
+        template_name = 'foo-template'
+        self.factory.makePOTemplate(
+            name=template_name, sourcepackage=sourcepackage)
+        self.factory.makePOTemplate(
+            name=template_name, productseries=productseries)
+        browser = self.getViewBrowser(
+            sourcepackage, no_login=True, rootsite="translations",
+            view_name="+sharing-details")
+        tbody = find_tag_by_id(
+            browser.contents, 'template-table').find('tbody')
+        self.assertIsNot(None, tbody)
+        self.assertTextMatchesExpressionIgnoreWhitespace("""
+            foo-template  shared
+            0  \d+ second(s)? ago  0  \d+ second(s)? ago""",
+            extract_text(tbody))
+
+    def test_potlist_only_upstream(self):
+        # A template that is only present in upstream is called
+        # "only in upstream".
+        sourcepackage, productseries = self._makeFullyConfiguredSharing()
+        template_name = 'foo-template'
+        self.factory.makePOTemplate(
+            name=template_name, productseries=productseries)
+        browser = self.getViewBrowser(
+            sourcepackage, no_login=True, rootsite="translations",
+            view_name="+sharing-details")
+        tbody = find_tag_by_id(
+            browser.contents, 'template-table').find('tbody')
+        self.assertIsNot(None, tbody)
+        self.assertTextMatchesExpressionIgnoreWhitespace("""
+            foo-template  only in upstream  0  \d+ second(s)? ago""",
+            extract_text(tbody))
+
+    def test_message_no_templates(self):
+        # When sharing is fully configured but no upstream templates are
+        # found, a message is displayed.
+        sourcepackage = self._makeFullyConfiguredSharing()[0]
+        browser = self.getViewBrowser(
+            sourcepackage, no_login=True, rootsite="translations",
+            view_name="+sharing-details")
+        self.assertEqual(
+            ["No upstream templates have been found yet. Please follow "
+             "the import process by going to the Translation Import Queue "
+             "of the upstream project series."],
+            get_feedback_messages(browser.contents))
+
+    def test_no_message_with_templates(self):
+        # When sharing is fully configured and templates are found, no
+        # message should be displayed.
+        sourcepackage, productseries = self._makeFullyConfiguredSharing()
+        self.factory.makePOTemplate(productseries=productseries)
+        browser = self.getViewBrowser(
+            sourcepackage, no_login=True, rootsite="translations",
+            view_name="+sharing-details")
+        self.assertEqual([], get_feedback_messages(browser.contents))
+
+    def test_no_message_with_incomplate_sharing(self):
+        # When sharing is not fully configured and templates are found, no
+        # message should be displayed.
+        packaging = self.factory.makePackagingLink(in_ubuntu=True)
+        productseries = packaging.productseries
+        sourcepackage = packaging.sourcepackage
+        self.factory.makePOTemplate(productseries=productseries)
+        browser = self.getViewBrowser(
+            sourcepackage, no_login=True, rootsite="translations",
+            view_name="+sharing-details")
+        self.assertEqual([], get_feedback_messages(browser.contents))

=== added file 'lib/lp/translations/templates/sourcepackage-sharing-details.pt'
--- lib/lp/translations/templates/sourcepackage-sharing-details.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/templates/sourcepackage-sharing-details.pt	2011-03-15 12:12:55 +0000
@@ -0,0 +1,121 @@
+<html
+  xmlns="http://www.w3.org/1999/xhtml";
+  xmlns:tal="http://xml.zope.org/namespaces/tal";
+  xmlns:metal="http://xml.zope.org/namespaces/metal";
+  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+  metal:use-macro="view/macro:page/main_only"
+  i18n:domain="launchpad"
+>
+  <body>
+    <div metal:fill-slot="heading">
+      <h1>Translation sharing details</h1>
+    </div>
+    <div metal:fill-slot="main">
+      <dl id="sharing-checklist">
+        <dt><span tal:condition="not:view/is_configuration_complete">
+            Translation sharing configuration is incomplete.
+          </span>
+          <span tal:condition="view/is_configuration_complete">
+            Translation sharing with upstream is active.
+          </span>
+        </dt>
+        <dd>
+          <ul>
+            <li class="sprite no"
+                tal:condition="not:view/is_packaging_configured">
+                No upstream project series has been linked.
+                <a tal:replace="structure context/menu:overview/edit_packaging/fmt:icon" />
+            </li>
+            <li class="sprite yes"
+                tal:condition="view/is_packaging_configured">
+              Linked upstream series is
+              <a tal:replace="structure context/productseries/fmt:link">
+                Gimp trunk</a>.
+              <a tal:replace="structure context/menu:overview/edit_packaging/fmt:icon" />
+              <a tal:replace="structure context/menu:overview/remove_packaging/fmt:icon" />
+            </li>
+            <li tal:attributes="class view/no_item_class"
+                tal:condition="not:view/has_upstream_branch">
+              No source branch exists for the upstream series.
+              <a tal:condition="view/is_packaging_configured"
+                 tal:replace="structure context/productseries/menu:overview/set_branch/fmt:icon" />
+            </li>
+            <li class="sprite yes"
+                tal:condition="view/has_upstream_branch">
+              Upstream source branch is
+              <a tal:replace="structure context/productseries/branch/fmt:link">
+                lp:gimp</a>.
+              <a tal:condition="view/is_packaging_configured"
+                 tal:replace="structure context/productseries/menu:overview/set_branch/fmt:icon" />
+            </li>
+            <li tal:attributes="class view/no_item_class"
+                tal:condition="not:view/is_upstream_translations_enabled">
+              Translations are not enabled on the upstream series.
+              <a tal:condition="view/is_packaging_configured"
+                 tal:replace="structure context/productseries/product/menu:translations/settings/fmt:icon" />
+            </li>
+            <li class="sprite yes"
+                tal:condition="view/is_upstream_translations_enabled">
+              Translations are enable on the upstream project.
+              <a tal:condition="view/is_packaging_configured"
+                 tal:replace="structure context/productseries/product/menu:translations/settings/fmt:icon" />
+            </li>
+            <li tal:attributes="class view/no_item_class"
+                tal:condition="not:view/is_upstream_synchronization_enabled">
+              Automatic synchronization of translations is not enabled.
+              <a tal:condition="view/is_packaging_configured"
+                 tal:replace="structure context/productseries/menu:translations/settings/fmt:icon" />
+            </li>
+            <li class="sprite yes"
+                tal:condition="view/is_upstream_synchronization_enabled">
+              Automatic synchronization of translations is enabled.
+              <a tal:condition="view/is_packaging_configured"
+                 tal:replace="structure context/productseries/menu:translations/settings/fmt:icon" />
+            </li>
+          </ul>
+        </dd>
+      </dl>
+      <table class="sortable listing" id="template-table">
+        <thead>
+          <tr>
+            <th class="name_column" rowspan="2">Template name</th>
+            <th class="state_column" rowspan="2">State</th>
+            <th style="text-align: center" colspan="2">Ubuntu</th>
+            <th style="text-align: center" colspan="3">Upstream</th>
+          </tr>
+          <tr>
+            <th class="length_ubuntu_column">Length</th>
+            <th class="updated_ubuntu_column">Updated</th>
+            <th class="length_upstream_column">Length</th>
+            <th class="updated_upstream_column">Updated</th>
+            <th class="upstream_link_column">Upstream template</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr tal:repeat="info view/template_info">
+            <td class="name_column">
+              <a tal:content="info/name"
+                 tal:attributes="href info/package_template/fmt:url"
+                 tal:omit-tag="not:info/package_template">gimp20</a></td>
+            <td class="state_column" tal:content="info/status">sharing</td>
+            <td class="length_ubuntu_column">
+                <tal:content tal:condition="info/package_template"
+                             tal:content="info/package_template/messagecount">82</tal:content></td>
+            <td class="updated_ubuntu_column">
+                <tal:content tal:condition="info/package_template"
+                             tal:content="info/package_template/date_last_updated/fmt:approximatedate">2 minutes ago</tal:content></td>
+            <td class="length_upstream_column">
+                <tal:content tal:condition="info/upstream_template"
+                             tal:content="info/upstream_template/messagecount">85</tal:content></td>
+            <td class="updated_upstream_column">
+                <tal:content tal:condition="info/upstream_template"
+                             tal:content="info/upstream_template/date_last_updated/fmt:approximatedate">2 minutes ago</tal:content></td>
+            <td class="upstream_link_column">
+              <a tal:condition="info/upstream_template"
+                 tal:attributes="href info/upstream_template/fmt:url">View upstream</a></td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </body>
+</html>


Follow ups