← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~henninge/launchpad/devel-605924-hastranslationtemplates into lp:launchpad

 

Henning Eggers has proposed merging lp:~henninge/launchpad/devel-605924-hastranslationtemplates into lp:launchpad with lp:~henninge/launchpad/devel-737422-remove-translationsharinginfo as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #605924 in Launchpad itself: "Clean up IHasTranslationTemplates"
  https://bugs.launchpad.net/launchpad/+bug/605924

For more details, see:
https://code.launchpad.net/~henninge/launchpad/devel-605924-hastranslationtemplates/+merge/54470

Details
=======

The methods in HasTranslationTemplatesMixin replaced previously existing methods in DistroSeries and ProductSeries. Therefore they copied the behavior of the old methods but that was inconsistent in some ways. This branch cleans up those inconsistencies.

This also fixes the previous branch that could not make full use of the methods because of those inconsistencies but struck ForbiddenAttribute errors because TranslationTemplateCollection does not have an Interface and thus no security configuration.


Proposed Fix
------------

Replace getObsoleteTranslationTemplates with has_obsolete_translation_templates. That's the only way it was used in production code and returned a list just to do "len(...) >0" on it. Another call site in a test can be easily replaced by providing getTranslationTemplateByName.

Remove the shortlist from getTranslationTemplateFormats because that list won't ever get long anyway. This still does not make it return a ResultSet like the others but at least it's an iterator. The only call site still needs to cast it to a list, anyway.

Remove the dependency of translation_usage and the number of translation templates. It was a loop! They depended on each other. Incredible. After some discussion and thinking I am convinced that translation_usage should be a pure configuration setting for the maintainer to express their intention about how to use LP. Anything else is just confusing.


Implementation Details
----------------------

Don't be scared by the size of the diff. I inserted an extra <div> in two templates and had to indent a whole chunk. It's about using "view/show_page_content" for the whole page instead of relying on getCurrentTranslationTemplates to pretend there are no templates based on the translation_usage (which is what is being removed here).

The pages that this branch produces may actually be slightly different depending on permissions and the value of translation_usage to what they were before this branch. But that wasn't very consistent either and could still need some cleaning up. No tests are failing.


Tests
-----

I ran all translations tests. Takes 37 minutes on my laptop.

bin/tests -vvcm lp.translations


Demo/QA
-------

Use a browser logged in as a project owner or admin and toggle translation_usage from UNKNOWN to LAUNCHPAD and other values. Use another browser logged in as some other user to see how the translations pages for DistroSeries and ProductSeries change.



= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/model/distribution.py
  lib/lp/registry/model/distroseries.py
  lib/lp/registry/model/product.py
  lib/lp/registry/model/productseries.py
  lib/lp/translations/browser/poexportrequest.py
  lib/lp/translations/browser/pofile.py
  lib/lp/translations/browser/potemplate.py
  lib/lp/translations/browser/productseries.py
  lib/lp/translations/browser/sourcepackage.py
  lib/lp/translations/doc/distroseries-language.txt
  lib/lp/translations/interfaces/hastranslationtemplates.py
  lib/lp/translations/model/hastranslationtemplates.py
  lib/lp/translations/model/potemplate.py
  lib/lp/translations/templates/distroseries-translations.pt
  lib/lp/translations/templates/productseries-translations.pt
  lib/lp/translations/tests/test_hastranslationtemplates.py
  lib/lp/translations/utilities/translation_import.py

./lib/lp/registry/model/distroseries.py
     392: E301 expected 1 blank line, found 2
./lib/lp/translations/browser/pofile.py
     788: E301 expected 1 blank line, found 2
     904: E301 expected 1 blank line, found 2
./lib/lp/translations/interfaces/hastranslationtemplates.py
      60: E301 expected 1 blank line, found 2
      74: E301 expected 1 blank line, found 2

-- 
https://code.launchpad.net/~henninge/launchpad/devel-605924-hastranslationtemplates/+merge/54470
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~henninge/launchpad/devel-605924-hastranslationtemplates into lp:launchpad.
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py	2011-03-23 07:38:39 +0000
+++ lib/lp/registry/model/distribution.py	2011-03-23 07:38:40 +0000
@@ -1822,13 +1822,11 @@
         assert sourcepackage is not None, (
             "Translations sharing policy requires a SourcePackage.")
 
-        has_upstream_translations = False
         productseries = sourcepackage.productseries
-        if productseries is not None:
-            collection = productseries.getTemplatesCollection()
-            has_upstream_translations = bool(
-                collection.restrictCurrent().select().any())
-        if productseries is None or not has_upstream_translations:
+        has_upstream_translations = (
+            productseries is not None and
+            productseries.has_current_translation_templates)
+        if not has_upstream_translations:
             # There is no known upstream template or series.  Take the
             # uploader's word for whether these are upstream translations
             # (in which case they're shared) or not.
@@ -1843,8 +1841,7 @@
             # translations for upstream.
             return purportedly_upstream
 
-        upstream_product = sourcepackage.productseries.product
-        return upstream_product.invitesTranslationEdits(person, language)
+        return productseries.product.invitesTranslationEdits(person, language)
 
 
 class DistributionSet:

=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py	2011-03-23 05:02:39 +0000
+++ lib/lp/registry/model/distroseries.py	2011-03-23 07:38:40 +0000
@@ -69,7 +69,6 @@
     )
 from lp.app.enums import (
     service_uses_launchpad,
-    ServiceUsage,
     )
 from lp.app.errors import NotFoundError
 from lp.app.interfaces.launchpad import IServiceUsage
@@ -298,17 +297,7 @@
     @property
     def translations_usage(self):
         """See `IServiceUsage.`"""
-        # If translations_usage is set for the Distribution, respect it.
-        usage = self.distribution.translations_usage
-        if usage != ServiceUsage.UNKNOWN:
-            return usage
-
-        # If not, usage is based on the presence of current translation
-        # templates for the series.
-        if self.getCurrentTranslationTemplates().count() > 0:
-            return ServiceUsage.LAUNCHPAD
-        else:
-            return ServiceUsage.UNKNOWN
+        return self.distribution.translations_usage
 
     @property
     def codehosting_usage(self):

=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py	2011-03-22 08:08:40 +0000
+++ lib/lp/registry/model/product.py	2011-03-23 07:38:40 +0000
@@ -1052,7 +1052,7 @@
         """See `IProduct`."""
         obsolete_product_series = set(
             product_series for product_series in self.series
-            if len(product_series.getObsoleteTranslationTemplates()) > 0)
+            if product_series.has_obsolete_translation_templates)
         return sorted(obsolete_product_series, key=lambda s: s.datecreated)
 
     @property

=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py	2011-03-23 07:38:39 +0000
+++ lib/lp/registry/model/productseries.py	2011-03-23 07:38:40 +0000
@@ -45,7 +45,6 @@
 from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
 from lp.app.errors import NotFoundError
 from lp.app.enums import (
-    ServiceUsage,
     service_uses_launchpad)
 from lp.app.interfaces.launchpad import IServiceUsage
 from lp.blueprints.enums import (
@@ -172,17 +171,7 @@
     @property
     def translations_usage(self):
         """See `IServiceUsage.`"""
-        # If translations_usage is set for the Product, respect it.
-        usage = self.product.translations_usage
-        if usage != ServiceUsage.UNKNOWN:
-            return usage
-
-        # If not, usage is based on the presence of current translation
-        # templates for the series.
-        if self.potemplate_count > 0:
-            return ServiceUsage.LAUNCHPAD
-        else:
-            return ServiceUsage.UNKNOWN
+        return self.product.translations_usage
 
     @property
     def codehosting_usage(self):

=== modified file 'lib/lp/translations/browser/poexportrequest.py'
--- lib/lp/translations/browser/poexportrequest.py	2010-12-28 15:22:46 +0000
+++ lib/lp/translations/browser/poexportrequest.py	2011-03-23 07:38:40 +0000
@@ -80,7 +80,7 @@
         templates = self.context.getCurrentTranslationTemplates()
         if not bool(templates.any()):
             return None
-        formats = self.context.getTranslationTemplateFormats()
+        formats = list(self.context.getTranslationTemplateFormats())
         format = formats[0]
         if len(formats) > 1:
             self.request.response.addInfoNotification(

=== modified file 'lib/lp/translations/browser/pofile.py'
--- lib/lp/translations/browser/pofile.py	2011-03-23 07:38:39 +0000
+++ lib/lp/translations/browser/pofile.py	2011-03-23 07:38:40 +0000
@@ -992,19 +992,8 @@
         return potemplate.translation_side == TranslationSide.UPSTREAM
 
     def is_sharing(self):
-        if self.is_upstream_pofile:
-            productseries = self.context.potemplate.productseries
-            other_side_object = (
-                productseries.getUbuntuTranslationFocusPackage())
-        else:
-            sourcepackage = self.context.potemplate.sourcepackage
-            other_side_object = sourcepackage.productseries
-        if other_side_object is None:
-            return False
-        collection = other_side_object.getTemplatesCollection()
-        name = self.context.potemplate.name
-        return bool(
-            collection.restrictCurrent().restrictName(name).select().any())
+        potemplate = self.context.potemplate.getOtherSidePOTemplate()
+        return potemplate is not None
 
     @property
     def sharing_pofile(self):

=== modified file 'lib/lp/translations/browser/potemplate.py'
--- lib/lp/translations/browser/potemplate.py	2011-03-23 07:38:39 +0000
+++ lib/lp/translations/browser/potemplate.py	2011-03-23 07:38:40 +0000
@@ -347,19 +347,8 @@
         return self.context.translation_side == TranslationSide.UPSTREAM
 
     def is_sharing(self):
-        if self.is_upstream_pofile:
-            productseries = self.context.productseries
-            other_side_object = (
-                productseries.getUbuntuTranslationFocusPackage())
-        else:
-            sourcepackage = self.potemplate.sourcepackage
-            other_side_object = sourcepackage.productseries
-        if other_side_object is None:
-            return False
-        collection = other_side_object.getTemplatesCollection()
-        name = self.context.name
-        return bool(
-            collection.restrictCurrent().restrictName(name).select().any())
+        potemplate = self.context.getOtherSidePOTemplate()
+        return potemplate is not None
 
     @property
     def sharing_template(self):

=== modified file 'lib/lp/translations/browser/productseries.py'
--- lib/lp/translations/browser/productseries.py	2011-03-23 07:38:39 +0000
+++ lib/lp/translations/browser/productseries.py	2011-03-23 07:38:40 +0000
@@ -459,8 +459,7 @@
         sourcepackage = self.context.getUbuntuTranslationFocusPackage()
         if sourcepackage is None:
             return False
-        collection = sourcepackage.getTemplatesCollection().restrictCurrent()
-        return bool(collection.select().any())
+        return sourcepackage.has_current_translation_templates
 
     @property
     def sharing_sourcepackage(self):

=== modified file 'lib/lp/translations/browser/sourcepackage.py'
--- lib/lp/translations/browser/sourcepackage.py	2011-03-23 07:38:39 +0000
+++ lib/lp/translations/browser/sourcepackage.py	2011-03-23 07:38:40 +0000
@@ -58,8 +58,7 @@
         productseries = self.context.productseries
         if productseries is None:
             return False
-        collection = productseries.getTemplatesCollection().restrictCurrent()
-        return bool(collection.select().any())
+        return productseries.has_current_translation_templates
 
     @property
     def sharing_productseries(self):
@@ -123,8 +122,7 @@
         productseries = self.context.productseries
         if productseries is None:
             return False
-        collection = productseries.getTemplatesCollection().restrictCurrent()
-        return bool(collection.select().any())
+        return productseries.has_current_translation_templates
 
     def initialize(self):
         if not getFeatureFlag('translations.sharing_information.enabled'):

=== modified file 'lib/lp/translations/doc/distroseries-language.txt'
--- lib/lp/translations/doc/distroseries-language.txt	2010-10-03 15:30:06 +0000
+++ lib/lp/translations/doc/distroseries-language.txt	2011-03-23 07:38:40 +0000
@@ -89,16 +89,14 @@
 
 This is the one obsolete template.
 
-    >>> for potemplate in hoary.getObsoleteTranslationTemplates():
-    ...     print potemplate.name
-    disabled-template
+    >>> potemplate = hoary.getTranslationTemplateByName('disabled-template')
+    >>> print potemplate.iscurrent
+    False
 
 Also, we can see that the template has an Spanish translation that
 hoary_spanish.pofiles is hidding as expected.
 
-    >>> for potemplate in hoary.getTranslationTemplates():
-    ...     if potemplate.name == 'disabled-template':
-    ...         print potemplate.getPOFileByLang('es').title
+    >>> print potemplate.getPOFileByLang('es').title
     Spanish (es) translation of disabled-template in Ubuntu Hoary package
     "evolution"
 

=== modified file 'lib/lp/translations/interfaces/hastranslationtemplates.py'
--- lib/lp/translations/interfaces/hastranslationtemplates.py	2010-11-15 16:25:05 +0000
+++ lib/lp/translations/interfaces/hastranslationtemplates.py	2011-03-23 07:38:40 +0000
@@ -32,6 +32,10 @@
         title=_("Does this object have current translation templates?"),
         readonly=True)
 
+    has_obsolete_translation_templates = Bool(
+        title=_("Does this object have obsolete translation templates?"),
+        readonly=True)
+
     has_translation_files = Bool(
         title=_("Does this object have translation files?"),
         readonly=True)
@@ -74,14 +78,6 @@
         active translation template.
         """
 
-    def getObsoleteTranslationTemplates():
-        """Return an iterator over its not active translation templates.
-
-        A translation template is considered not active when any of
-        `IPOTemplate`.iscurrent or `IDistribution`.official_rosetta flags
-        are set to False.
-        """
-
     @export_read_operation()
     @operation_returns_collection_of(Interface)
     def getTranslationTemplates():
@@ -92,6 +88,9 @@
         :return: A sequence of `IPOTemplate`.
         """
 
+    def getTranslationTemplateByName(name):
+        """Return the template with the given name or None."""
+
     def getTranslationTemplateFormats():
         """A list of native formats for all current translation templates.
         """

=== modified file 'lib/lp/translations/model/hastranslationtemplates.py'
--- lib/lp/translations/model/hastranslationtemplates.py	2010-11-15 16:25:05 +0000
+++ lib/lp/translations/model/hastranslationtemplates.py	2011-03-23 07:38:40 +0000
@@ -14,8 +14,6 @@
     )
 from zope.interface import implements
 
-from canonical.launchpad import helpers
-from lp.app.enums import service_uses_launchpad
 from lp.translations.interfaces.hastranslationtemplates import (
     IHasTranslationTemplates,
     )
@@ -41,17 +39,7 @@
 
     def getCurrentTemplatesCollection(self, current_value=True):
         """See `IHasTranslationTemplates`."""
-        collection = self.getTemplatesCollection()
-
-        # XXX JeroenVermeulen 2010-07-15 bug=605924: Move the
-        # translations_usage distinction into browser code.
-        pillar = collection.target_pillar
-        if service_uses_launchpad(pillar.translations_usage):
-            return collection.restrictCurrent(current_value)
-        else:
-            # Product/Distribution does not have translation enabled.
-            # Treat all templates as obsolete.
-            return collection.refine(not current_value)
+        return self.getTemplatesCollection().restrictCurrent(current_value)
 
     def getCurrentTranslationTemplates(self,
                                        just_ids=False,
@@ -76,6 +64,13 @@
         return bool(
             self.getCurrentTranslationTemplates(just_ids=True).any())
 
+    @property
+    def has_obsolete_translation_templates(self):
+        """See `IHasTranslationTemplates`."""
+        return bool(
+            self.getCurrentTranslationTemplates(
+                just_ids=True, current_value=False).any())
+
     def getCurrentTranslationFiles(self, just_ids=False):
         """See `IHasTranslationTemplates`."""
         if just_ids:
@@ -92,23 +87,19 @@
         return bool(
             self.getCurrentTranslationFiles(just_ids=True).any())
 
-    def getObsoleteTranslationTemplates(self):
-        """See `IHasTranslationTemplates`."""
-        # XXX JeroenVermeulen 2010-07-15 bug=605924: This returns a list
-        # whereas the analogous method for current template returns a
-        # result set.  Clean up this mess.
-        return list(self.getCurrentTranslationTemplates(current_value=False))
-
     def getTranslationTemplates(self):
         """See `IHasTranslationTemplates`."""
         return self._orderTemplates(self.getTemplatesCollection().select())
 
+    def getTranslationTemplateByName(self, name):
+        """See `IHasTranslationTemplates`."""
+        return self.getTemplatesCollection().restrictName(name).select().one()
+
     def getTranslationTemplateFormats(self):
         """See `IHasTranslationTemplates`."""
         formats_query = self.getCurrentTranslationTemplates().order_by(
             'source_file_format').config(distinct=True)
-        return helpers.shortlist(
-            formats_query.values(POTemplate.source_file_format), 10)
+        return formats_query.values(POTemplate.source_file_format)
 
     def getTemplatesAndLanguageCounts(self):
         """See `IHasTranslationTemplates`."""

=== modified file 'lib/lp/translations/model/potemplate.py'
--- lib/lp/translations/model/potemplate.py	2011-03-23 07:38:39 +0000
+++ lib/lp/translations/model/potemplate.py	2011-03-23 07:38:40 +0000
@@ -550,9 +550,7 @@
                 self.productseries.getUbuntuTranslationFocusPackage())
         if other_side_object is None:
             return None
-        collection = (
-            other_side_object.getTemplatesCollection().restrictCurrent())
-        return collection.restrictName(self.name).select().one()
+        return other_side_object.getTranslationTemplateByName(self.name)
 
     def messageCount(self):
         """See `IRosettaStats`."""
@@ -1651,40 +1649,16 @@
     """A `Collection` of `POTemplate`."""
     starting_table = POTemplate
 
-    # The Product or Distribution that this collection is restricted to.
-    target_pillar = None
-
-    def __init__(self, *args, **kwargs):
-        super(TranslationTemplatesCollection, self).__init__(*args, **kwargs)
-        if self.base is not None:
-            self.target_pillar = self.base.target_pillar
-
     def restrictProductSeries(self, productseries):
-        product = productseries.product
-        new_collection = self.refine(
-            POTemplate.productseriesID == productseries.id)
-        new_collection._setTargetPillar(product)
-        return new_collection
+        return self.refine(POTemplate.productseriesID == productseries.id)
 
     def restrictDistroSeries(self, distroseries):
-        distribution = distroseries.distribution
-        new_collection = self.refine(
-            POTemplate.distroseriesID == distroseries.id)
-        new_collection._setTargetPillar(distribution)
-        return new_collection
+        return self.refine(POTemplate.distroseriesID == distroseries.id)
 
     def restrictSourcePackageName(self, sourcepackagename):
         return self.refine(
             POTemplate.sourcepackagenameID == sourcepackagename.id)
 
-    def _setTargetPillar(self, target_pillar):
-        assert (
-            self.target_pillar is None or
-            self.target_pillar == target_pillar), (
-                "Collection restricted to both %s and %s." % (
-                    self.target_pillar, target_pillar))
-        self.target_pillar = target_pillar
-
     def restrictCurrent(self, current_value=True):
         """Select based on `POTemplate.iscurrent`.
 

=== modified file 'lib/lp/translations/templates/distroseries-translations.pt'
--- lib/lp/translations/templates/distroseries-translations.pt	2010-10-26 21:26:27 +0000
+++ lib/lp/translations/templates/distroseries-translations.pt	2011-03-23 07:38:40 +0000
@@ -30,127 +30,127 @@
           <tal:message
             replace="structure context/@@+portlet-not-using-launchpad"/>
         </tal:not-using-launchpad>
-      <div id="translation-focus"
-           tal:condition="context/distribution/translation_focus">
-        <p tal:condition="not:view/is_translation_focus">
-          Launchpad currently recommends translating
-          <tal:target replace="
-             structure
-             context/distribution/translation_focus/fmt:link/+translations"
-                      >Hoary</tal:target>.
-        </p>
-        <p tal:condition="view/is_translation_focus">
-          <tal:target replace="context/displayname">Hoary</tal:target> is
-          the current translation focus for
-          <tal:distro replace="structure context/distribution/fmt:link">
-            Ubuntu
-          </tal:distro>.
-        </p>
-      </div>
-      </div>
-
-      <div tal:condition="view/show_page_content" class="yui-g">
-        <div class="yui-u first">
-          <div class="portlet">
-            <h3>Permissions</h3>
-            <p>
-              <tal:permissions replace="
-                structure
-                context/distribution/@@+portlet-translation-groups-and-permission"/>
-            </p>
-          </div>
-        </div>
-        <div class="yui-u">
-          <div class="portlet">
-            <h3>Administration</h3>
-            <p>Translation files that are waiting to be imported are shown in
-              the
-              <a tal:attributes="href context/fmt:url:translations/+imports"
-                 tal:content="string:${context/displayname} import queue">
-                import queue</a>.
-            </p>
-            <p>
-              To see all the translation templates in
-              <tal:series replace="context/displayname">Hoary</tal:series>,
-              go to the
-              <a tal:attributes="href context/menu:navigation/templates/url">
-                full list of templates</a>.
-            </p>
-            <p>
-              <tal:series replace="context/displayname">Hoary</tal:series>
-              translations are
-              <em tal:condition="not:context/hide_all_translations">
-                visible to everyone<!--
-              --></em><em tal:condition="context/hide_all_translations">
-                hidden from everyone but translation admins</em>.
-              Import queue is
-              <em tal:condition="not:context/defer_translation_imports">
-                active<!--
-              --></em><em tal:condition="context/defer_translation_imports">
-                currently halted for
-                <tal:series replace="context/displayname">
-                  Hoary
-              </tal:series></em>.
-              <a tal:attributes="href context/menu:navigation/admin/url"
-                 tal:condition="context/required:launchpad.TranslationsAdmin"
-                 class="edit sprite">
-                Change settings
-              </a>
-            </p>
-          </div>
-        </div>
-
-        <div class="yui-u first">
-          <div class="portlet">
-            <h3>Language packs</h3>
-
-            <div tal:replace="
-                structure
-                context/distribution/@@+language-pack-admin-info" />
-
-            <div>
-              <strong>Base pack:</strong>
-              <a class="sprite download"
-                 tal:condition="context/language_pack_base"
-                 tal:attributes="href context/language_pack_base/file/http_url">
-                <tal:export-date
-                   replace="context/language_pack_base/date_exported/fmt:datetime" />
-              </a>
-              <tal:not-export-date condition="not: context/language_pack_base">
-                none yet
-              </tal:not-export-date>
-            </div>
-            <div>
-              <strong>Update pack:</strong>
-              <a class="sprite download"
-                 tal:condition="context/language_pack_delta"
-                 tal:attributes="href context/language_pack_delta/file/http_url">
-                <tal:export-date
-                   replace="context/language_pack_delta/date_exported/fmt:datetime" />
-              </a>
-              <tal:not-export-date condition="not: context/language_pack_delta">
-                no update
-              </tal:not-export-date>
-            </div>
-            <p>
-              <a tal:attributes="
-                   href context/menu:navigation/language_packs/url">
-                See all language packs</a>
-            </p>
-          </div>
-        </div>
-      </div>
-      <tal:show-page-content condition="view/show_page_content">
-      <tal:stats condition="view/distroserieslanguages">
-        <div class="yui-b top-portlet">
-          <h2>Translation statistics</h2>
-          <div tal:replace="structure context/@@+langchart" />
-          <div class="translations-legend">
-            <div tal:replace="structure context/@@+rosetta-status-legend" />
-          </div>
-        </div>
-      </tal:stats>
-      </tal:show-page-content>
+        <div id="translation-focus"
+             tal:condition="context/distribution/translation_focus">
+          <p tal:condition="not:view/is_translation_focus">
+            Launchpad currently recommends translating
+            <tal:target replace="
+               structure
+               context/distribution/translation_focus/fmt:link/+translations"
+                        >Hoary</tal:target>.
+          </p>
+          <p tal:condition="view/is_translation_focus">
+            <tal:target replace="context/displayname">Hoary</tal:target> is
+            the current translation focus for
+            <tal:distro replace="structure context/distribution/fmt:link">
+              Ubuntu
+            </tal:distro>.
+          </p>
+        </div>
+      </div>
+
+      <div  tal:condition="view/show_page_content">
+        <div class="yui-g">
+          <div class="yui-u first">
+            <div class="portlet">
+              <h3>Permissions</h3>
+              <p>
+                <tal:permissions replace="
+                  structure
+                  context/distribution/@@+portlet-translation-groups-and-permission"/>
+              </p>
+            </div>
+          </div>
+          <div class="yui-u">
+            <div class="portlet">
+              <h3>Administration</h3>
+              <p>Translation files that are waiting to be imported are shown in
+                the
+                <a tal:attributes="href context/fmt:url:translations/+imports"
+                   tal:content="string:${context/displayname} import queue">
+                  import queue</a>.
+              </p>
+              <p>
+                To see all the translation templates in
+                <tal:series replace="context/displayname">Hoary</tal:series>,
+                go to the
+                <a tal:attributes="href context/menu:navigation/templates/url">
+                  full list of templates</a>.
+              </p>
+              <p>
+                <tal:series replace="context/displayname">Hoary</tal:series>
+                translations are
+                <em tal:condition="not:context/hide_all_translations">
+                  visible to everyone<!--
+                --></em><em tal:condition="context/hide_all_translations">
+                  hidden from everyone but translation admins</em>.
+                Import queue is
+                <em tal:condition="not:context/defer_translation_imports">
+                  active<!--
+                --></em><em tal:condition="context/defer_translation_imports">
+                  currently halted for
+                  <tal:series replace="context/displayname">
+                    Hoary
+                </tal:series></em>.
+                <a tal:attributes="href context/menu:navigation/admin/url"
+                   tal:condition="context/required:launchpad.TranslationsAdmin"
+                   class="edit sprite">
+                  Change settings
+                </a>
+              </p>
+            </div>
+          </div>
+
+          <div class="yui-u first">
+            <div class="portlet">
+              <h3>Language packs</h3>
+
+              <div tal:replace="
+                  structure
+                  context/distribution/@@+language-pack-admin-info" />
+
+              <div>
+                <strong>Base pack:</strong>
+                <a class="sprite download"
+                   tal:condition="context/language_pack_base"
+                   tal:attributes="href context/language_pack_base/file/http_url">
+                  <tal:export-date
+                     replace="context/language_pack_base/date_exported/fmt:datetime" />
+                </a>
+                <tal:not-export-date condition="not: context/language_pack_base">
+                  none yet
+                </tal:not-export-date>
+              </div>
+              <div>
+                <strong>Update pack:</strong>
+                <a class="sprite download"
+                   tal:condition="context/language_pack_delta"
+                   tal:attributes="href context/language_pack_delta/file/http_url">
+                  <tal:export-date
+                     replace="context/language_pack_delta/date_exported/fmt:datetime" />
+                </a>
+                <tal:not-export-date condition="not: context/language_pack_delta">
+                  no update
+                </tal:not-export-date>
+              </div>
+              <p>
+                <a tal:attributes="
+                     href context/menu:navigation/language_packs/url">
+                  See all language packs</a>
+              </p>
+            </div>
+          </div>
+        </div>
+        <tal:stats condition="view/distroserieslanguages">
+          <div class="yui-b top-portlet">
+            <h2>Translation statistics</h2>
+            <div tal:replace="structure context/@@+langchart" />
+            <div class="translations-legend">
+              <div tal:replace="structure context/@@+rosetta-status-legend" />
+            </div>
+          </div>
+        </tal:stats>
+      </div>
     </div>
   </body>
 </html>

=== modified file 'lib/lp/translations/templates/productseries-translations.pt'
--- lib/lp/translations/templates/productseries-translations.pt	2011-03-02 12:23:30 +0000
+++ lib/lp/translations/templates/productseries-translations.pt	2011-03-23 07:38:40 +0000
@@ -1,4 +1,3 @@
-
 <html
   xmlns="http://www.w3.org/1999/xhtml";
   xmlns:tal="http://xml.zope.org/namespaces/tal";
@@ -60,153 +59,155 @@
         </div>
       </tal:no-languages>
 
-      <div tal:condition="view/show_page_content" class="yui-g">
-        <div class="yui-u first">
-          <div class="portlet">
-            <h3>Permissions</h3>
-            <p>
-              <tal:permissions replace="
-                  structure
-                  context/product/@@+portlet-translation-groups-and-permission"/>
-            </p>
-            <p tal:condition="view/has_translation_documentation">
-              Before translating, please look at
-              <a tal:attributes="
-                  href
-                  context/product/translationgroup/translation_guide_url
-                  ">translation instructions</a> first.
-            </p>
-          </div>
-          <div class="portlet" id="sharing-information"
-            tal:condition="features/translations.sharing_information.enabled">
-            <h3>Sharing Information</h3>
-            <p tal:condition="not:view/is_sharing">
-              This project series is not sharing translations with
-              an Ubuntu source package.
-            </p>
-            <p tal:condition="view/is_sharing"
-               tal:define="package view/sharing_sourcepackage">
-              This project series is sharing translations with
-              <a class="sprite package-source"
-            tal:attributes="href package/fmt:url"
-            tal:content="package/displayname">apache in ubuntu hoary</a>.
-            </p>
-            <a tal:replace="structure view/sharing_details">
-              View sharing details
-            </a>
-          </div>
-        </div>
-
-        <div class="yui-u">
-          <div class="portlet">
-            <h3>Administration</h3>
-            <p>Translation files that are waiting to be imported are shown in
-              the
-              <a tal:attributes="href context/menu:navigation/imports/url">
-                import queue</a>.
-            </p>
-            <p>
-              To see all the translation templates in
-              <tal:series replace="context/displayname">trunk</tal:series>,
-              go to the
-              <a tal:attributes="href context/menu:navigation/templates/url">
-                full list of templates</a>.
-            </p>
-          </div>
-        </div>
-      </div>
-      <div class="yui-g">
-        <div class="yui-u first">
-          <div class="portlet automatic-synchronization">
-            <h3>Automatic synchronization</h3>
-            <tal:uses-bzr-sync condition="view/uses_bzr_sync">
-              <tal:imports condition="view/has_imports_enabled">
-                <tal:exports condition="view/has_exports_enabled">
-                  Translations are imported with every update from branch
-                  <a tal:replace="structure context/branch/fmt:link">
-                    branch
-                  </a>, and exported daily to branch
-	          <a tal:replace="
-                       structure context/translations_branch/fmt:link"
-                     >branch name</a>.
-                </tal:exports>
-              </tal:imports>
-              <tal:imports-only condition="view/has_imports_enabled">
-                <tal:no-exports condition="not:view/has_exports_enabled">
-                  Translations are imported with every update from branch
-                  <a tal:replace="structure context/branch/fmt:link">
-                    branch name
-                  </a>.
-                </tal:no-exports>
-              </tal:imports-only>
-              <tal:exports-only condition="view/has_exports_enabled">
-                <tal:no-imports condition="not:view/has_imports_enabled">
-                  Translations are exported daily to branch
-                  <a tal:replace="
-                       structure context/translations_branch/fmt:link">
-                    branch name
-                  </a>.
-                </tal:no-imports>
-              </tal:exports-only>
-              <div tal:condition="context/required:launchpad.Edit">
-                <a tal:attributes="href context/menu:navigation/settings/url"
-                   class="edit sprite">
-                  Change synchronization settings
-                </a>
-              </div>
-            </tal:uses-bzr-sync>
-            <tal:no-bzr-sync condition="not:view/uses_bzr_sync">
-              <p>This project is currently not using any synchronization
-                with bazaar branches.</p>
-              <tal:branch condition="context/branch">
-                <p tal:condition="context/required:launchpad.Edit">
-                  <a tal:attributes="
-                       href context/menu:navigation/requestbzrimport/url"
-                     class="add sprite">
-                    Request an import from bazaar
-                  </a> to do a one time import of all the templates and
-                  translations from
-                  <a tal:replace="structure context/branch/fmt:link">
-                    branch
-                  </a>.
-                </p>
-              </tal:branch>
-              <div tal:condition="context/required:launchpad.Edit">
-                <a tal:attributes="href context/menu:navigation/settings/url"
-                   class="edit sprite">
-                  Set up branch synchronization
-                </a>
-              </div>
-            </tal:no-bzr-sync>
-          </div>
-        </div>
-        <div class="yui-u"
-             tal:condition="context/required:launchpad.AnyPerson">
-          <div class="portlet">
-            <h3>Manual synchronization</h3>
-            <p>If you don't want to use bazaar synchronization, you can still
-              manually
-              <a tal:attributes="href context/menu:navigation/translationupload/url"
-                 tal:condition="context/required:launchpad.Edit"
-                 class="add sprite">upload</a>
-              or
-              <a tal:attributes="href context/menu:navigation/translationdownload/url"
-                 tal:condition="context/required:launchpad.AnyPerson"
-                 class="download sprite">download</a>
-              translation tarballs.
-            </p>
-          </div>
-        </div>
-      </div>
-
-      <tal:languages condition="view/productserieslanguages">
-        <h2>Translation status</h2>
-
-        <div tal:replace="structure context/@@+languages" />
-
-        <div style="height:1em;"></div>
-        <div tal:replace="structure context/@@+rosetta-status-legend" />
-      </tal:languages>
+      <div tal:condition="view/show_page_content">
+        <div class="yui-g">
+          <div class="yui-u first">
+            <div class="portlet">
+              <h3>Permissions</h3>
+              <p>
+                <tal:permissions replace="
+                    structure
+                    context/product/@@+portlet-translation-groups-and-permission"/>
+              </p>
+              <p tal:condition="view/has_translation_documentation">
+                Before translating, please look at
+                <a tal:attributes="
+                    href
+                    context/product/translationgroup/translation_guide_url
+                    ">translation instructions</a> first.
+              </p>
+            </div>
+            <div class="portlet" id="sharing-information"
+              tal:condition="features/translations.sharing_information.enabled">
+              <h3>Sharing Information</h3>
+              <p tal:condition="not:view/is_sharing">
+                This project series is not sharing translations with
+                an Ubuntu source package.
+              </p>
+              <p tal:condition="view/is_sharing"
+                 tal:define="package view/sharing_sourcepackage">
+                This project series is sharing translations with
+                <a class="sprite package-source"
+              tal:attributes="href package/fmt:url"
+              tal:content="package/displayname">apache in ubuntu hoary</a>.
+              </p>
+              <a tal:replace="structure view/sharing_details">
+                View sharing details
+              </a>
+            </div>
+          </div>
+
+          <div class="yui-u">
+            <div class="portlet">
+              <h3>Administration</h3>
+              <p>Translation files that are waiting to be imported are shown in
+                the
+                <a tal:attributes="href context/menu:navigation/imports/url">
+                  import queue</a>.
+              </p>
+              <p>
+                To see all the translation templates in
+                <tal:series replace="context/displayname">trunk</tal:series>,
+                go to the
+                <a tal:attributes="href context/menu:navigation/templates/url">
+                  full list of templates</a>.
+              </p>
+            </div>
+          </div>
+        </div>
+        <div class="yui-g">
+          <div class="yui-u first">
+            <div class="portlet automatic-synchronization">
+              <h3>Automatic synchronization</h3>
+              <tal:uses-bzr-sync condition="view/uses_bzr_sync">
+                <tal:imports condition="view/has_imports_enabled">
+                  <tal:exports condition="view/has_exports_enabled">
+                    Translations are imported with every update from branch
+                    <a tal:replace="structure context/branch/fmt:link">
+                      branch
+                    </a>, and exported daily to branch
+  	          <a tal:replace="
+                         structure context/translations_branch/fmt:link"
+                       >branch name</a>.
+                  </tal:exports>
+                </tal:imports>
+                <tal:imports-only condition="view/has_imports_enabled">
+                  <tal:no-exports condition="not:view/has_exports_enabled">
+                    Translations are imported with every update from branch
+                    <a tal:replace="structure context/branch/fmt:link">
+                      branch name
+                    </a>.
+                  </tal:no-exports>
+                </tal:imports-only>
+                <tal:exports-only condition="view/has_exports_enabled">
+                  <tal:no-imports condition="not:view/has_imports_enabled">
+                    Translations are exported daily to branch
+                    <a tal:replace="
+                         structure context/translations_branch/fmt:link">
+                      branch name
+                    </a>.
+                  </tal:no-imports>
+                </tal:exports-only>
+                <div tal:condition="context/required:launchpad.Edit">
+                  <a tal:attributes="href context/menu:navigation/settings/url"
+                     class="edit sprite">
+                    Change synchronization settings
+                  </a>
+                </div>
+              </tal:uses-bzr-sync>
+              <tal:no-bzr-sync condition="not:view/uses_bzr_sync">
+                <p>This project is currently not using any synchronization
+                  with bazaar branches.</p>
+                <tal:branch condition="context/branch">
+                  <p tal:condition="context/required:launchpad.Edit">
+                    <a tal:attributes="
+                         href context/menu:navigation/requestbzrimport/url"
+                       class="add sprite">
+                      Request an import from bazaar
+                    </a> to do a one time import of all the templates and
+                    translations from
+                    <a tal:replace="structure context/branch/fmt:link">
+                      branch
+                    </a>.
+                  </p>
+                </tal:branch>
+                <div tal:condition="context/required:launchpad.Edit">
+                  <a tal:attributes="href context/menu:navigation/settings/url"
+                     class="edit sprite">
+                    Set up branch synchronization
+                  </a>
+                </div>
+              </tal:no-bzr-sync>
+            </div>
+          </div>
+          <div class="yui-u"
+               tal:condition="context/required:launchpad.AnyPerson">
+            <div class="portlet">
+              <h3>Manual synchronization</h3>
+              <p>If you don't want to use bazaar synchronization, you can still
+                manually
+                <a tal:attributes="href context/menu:navigation/translationupload/url"
+                   tal:condition="context/required:launchpad.Edit"
+                   class="add sprite">upload</a>
+                or
+                <a tal:attributes="href context/menu:navigation/translationdownload/url"
+                   tal:condition="context/required:launchpad.AnyPerson"
+                   class="download sprite">download</a>
+                translation tarballs.
+              </p>
+            </div>
+          </div>
+        </div>
+
+        <tal:languages condition="view/productserieslanguages">
+          <h2>Translation status</h2>
+
+          <div tal:replace="structure context/@@+languages" />
+
+          <div style="height:1em;"></div>
+          <div tal:replace="structure context/@@+rosetta-status-legend" />
+        </tal:languages>
+      </div>
     </div>
   </body>
 </html>

=== modified file 'lib/lp/translations/tests/test_hastranslationtemplates.py'
--- lib/lp/translations/tests/test_hastranslationtemplates.py	2010-11-15 16:25:05 +0000
+++ lib/lp/translations/tests/test_hastranslationtemplates.py	2011-03-23 07:38:40 +0000
@@ -158,10 +158,22 @@
         second_template = self.createTranslationTemplate("second")
         self.assertTrue(self.container.has_current_translation_templates)
 
-        # A product or distribution that doesn't use Launchpad for
-        # translations has no current templates.
-        self.product_or_distro.translations_usage = ServiceUsage.EXTERNAL
-        self.assertFalse(self.container.has_current_translation_templates)
+    def test_has_obsolete_translation_templates(self):
+        # A series without templates has no obsolete templates.
+        self.assertFalse(self.container.has_obsolete_translation_templates)
+
+        # A series with a current template has no obsolete templates either.
+        first_template = self.createTranslationTemplate("first")
+        self.assertFalse(self.container.has_obsolete_translation_templates)
+
+        # A series with only non-current templates has obsolete templates.
+        first_template.iscurrent = False
+        self.assertTrue(self.container.has_obsolete_translation_templates)
+
+        # A series with current and non-current templates has obsolete
+        # templates.
+        self.createTranslationTemplate("second")
+        self.assertTrue(self.container.has_obsolete_translation_templates)
 
     def test_has_translation_files(self):
         # has_translations_files should only return true if the object has
@@ -170,12 +182,29 @@
         self.createTranslationFile("one")
         self.assertTrue(self.container.has_translation_files)
 
+    def test_getTranslationTemplateByName(self):
+        template_name = self.factory.getUniqueString()
+        # A series without templates does not find the template.
+        self.assertEqual(
+            None, self.container.getTranslationTemplateByName(template_name))
+
+        # A template with a different name is not found.
+        self.createTranslationTemplate(self.factory.getUniqueString())
+        self.assertEqual(
+            None, self.container.getTranslationTemplateByName(template_name))
+
+        # Only the template with the correct name is returned.
+        template = self.createTranslationTemplate(template_name)
+        self.assertEqual(
+            template,
+            self.container.getTranslationTemplateByName(template_name))
+
     def test_getTranslationTemplateFormats(self):
         # Check that translation_template_formats works properly.
 
         # With no templates, empty list is returned.
         all_formats = self.container.getTranslationTemplateFormats()
-        self.assertEquals([], all_formats)
+        self.assertEquals([], list(all_formats))
 
         # With one template, that template's format is returned.
         template1 = self.createTranslationTemplate("one")
@@ -183,7 +212,7 @@
         all_formats = self.container.getTranslationTemplateFormats()
         self.assertEquals(
             [TranslationFileFormat.PO],
-            all_formats)
+            list(all_formats))
 
         # With multiple templates of the same format, that
         # format is still returned only once.
@@ -192,7 +221,7 @@
         all_formats = self.container.getTranslationTemplateFormats()
         self.assertEquals(
             [TranslationFileFormat.PO],
-            all_formats)
+            list(all_formats))
 
         # With another template of a different format,
         # we get that format in a returned list.
@@ -203,7 +232,7 @@
         # Items are sorted by the format values, PO==1 < XPI==3.
         self.assertEquals(
             [TranslationFileFormat.PO, TranslationFileFormat.XPI],
-            all_formats)
+            list(all_formats))
 
 
 class TestProductSeriesHasTranslationTemplates(

=== modified file 'lib/lp/translations/utilities/translation_import.py'
--- lib/lp/translations/utilities/translation_import.py	2011-03-23 07:38:39 +0000
+++ lib/lp/translations/utilities/translation_import.py	2011-03-23 07:38:40 +0000
@@ -447,8 +447,7 @@
         productseries = sourcepackage.productseries
         if productseries is None:
             return True
-        collection = productseries.getTemplatesCollection().restrictCurrent()
-        return not bool(collection.select().any())
+        return not productseries.has_current_translation_templates
 
     @cachedproperty
     def translations_are_msgids(self):