← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jtv/launchpad/recife-pre-sharing-permissions into lp:~launchpad/launchpad/recife

 

Jeroen T. Vermeulen has proposed merging lp:~jtv/launchpad/recife-pre-sharing-permissions into lp:~launchpad/launchpad/recife.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers): code


= Cleanups prior to Recife sharing permissions checks =

This is a cleanup to be merged into the Recife feature branch.

I'm working on the part of the Recife model change where we check if a particular translation submission for Ubuntu should be shared with upstream, or vice versa.  In my preparations I came across some things that needed cleaning up:

 * The basic translation view checked far too many separate things in one big method.

 * View failures generated some truly horrible error messages.

 * Existing permissions checks needed breaking down into smaller chunks to be comprehensible.

 * Some inefficient "do we know how plural forms work for this POFile" logic was also redundant.

 * Reading of "lock_timestamp" was tested in a doctest instead of a view unit test.

 * Lots and lots of trailing whitespace.

I sped up the inefficient logic by doing the cheapest (and often sufficient) test first, centralized it into a single method, and replaced the view properties that duplicated it with a single attribute.

To test, best run all Translations tests really:
{{{
./bin/test -vvc lp.translations
}}}

There's a bit of lint left, most related to comment blocks that aren't directly attached to their respective next lines.  I think that style is valid where the comment applies to a section of a class, as these do, so I didn't touch these cases.  The other source of lint is Moin-style headings in doctests, which I didn't touch in order to stay under the review limit.


Jeroen
-- 
https://code.launchpad.net/~jtv/launchpad/recife-pre-sharing-permissions/+merge/38407
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jtv/launchpad/recife-pre-sharing-permissions into lp:~launchpad/launchpad/recife.
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2010-10-05 17:06:23 +0000
+++ lib/lp/testing/factory.py	2010-10-14 11:26:43 +0000
@@ -1794,16 +1794,22 @@
         MessageChunk(message=message, sequence=1, content=content)
         return message
 
-    def makeLanguage(self, language_code=None, name=None, pluralforms=None):
+    def makeLanguage(self, language_code=None, name=None, pluralforms=None,
+                     plural_expression=None):
         """Makes a language given the language_code and name."""
         if language_code is None:
             language_code = self.getUniqueString('lang')
         if name is None:
             name = "Language %s" % language_code
+        if plural_expression is None and pluralforms is not None:
+            # If the number of plural forms is known, the language
+            # should also have a plural expression and vice versa.
+            plural_expression = 'n %% %d' % pluralforms
 
         language_set = getUtility(ILanguageSet)
         return language_set.createLanguage(
-            language_code, name, pluralforms=pluralforms)
+            language_code, name, pluralforms=pluralforms,
+            pluralexpression=plural_expression)
 
     def makeLibraryFileAlias(self, filename=None, content=None,
                              content_type='text/plain', restricted=False,

=== modified file 'lib/lp/testing/tests/test_factory.py'
--- lib/lp/testing/tests/test_factory.py	2010-09-30 17:57:01 +0000
+++ lib/lp/testing/tests/test_factory.py	2010-10-14 11:26:43 +0000
@@ -431,6 +431,14 @@
         for number_of_forms in [None, 1, 3]:
             language = self.factory.makeLanguage(pluralforms=number_of_forms)
             self.assertEqual(number_of_forms, language.pluralforms)
+            self.assertEqual(
+                number_of_forms is None, language.pluralexpression is None)
+
+    def test_makeLanguage_with_plural_expression(self):
+        expression = '(n+1) % 5'
+        language = self.factory.makeLanguage(
+            pluralforms=5, plural_expression=expression)
+        self.assertEqual(expression, language.pluralexpression)
 
     # makeSourcePackagePublishingHistory
     def test_makeSourcePackagePublishingHistory_returns_ISPPH(self):

=== modified file 'lib/lp/translations/browser/pofile.py'
--- lib/lp/translations/browser/pofile.py	2010-09-06 10:40:54 +0000
+++ lib/lp/translations/browser/pofile.py	2010-10-14 11:26:43 +0000
@@ -364,15 +364,6 @@
         return statement
 
     @property
-    def has_plural_form_information(self):
-        """Return whether we know the plural forms for this language."""
-        if self.context.potemplate.hasPluralMessage():
-            return self.context.language.pluralforms is not None
-        # If there are no plural forms, we assume that we have the
-        # plural form information for this language.
-        return True
-
-    @property
     def number_of_plural_forms(self):
         """The number of plural forms for the language or 1 if not known."""
         if self.context.language.pluralforms is not None:

=== modified file 'lib/lp/translations/browser/tests/pofile-base-views.txt'
--- lib/lp/translations/browser/tests/pofile-base-views.txt	2009-09-09 18:03:44 +0000
+++ lib/lp/translations/browser/tests/pofile-base-views.txt	2010-10-14 11:26:43 +0000
@@ -37,8 +37,6 @@
 
 The view has information about the languages plural forms:
 
-    >>> print view.has_plural_form_information
-    True
     >>> print view.number_of_plural_forms
     2
     >>> print view.plural_expression

=== modified file 'lib/lp/translations/browser/tests/test_translationmessage_view.py'
--- lib/lp/translations/browser/tests/test_translationmessage_view.py	2010-08-23 08:35:29 +0000
+++ lib/lp/translations/browser/tests/test_translationmessage_view.py	2010-10-14 11:26:43 +0000
@@ -1,22 +1,30 @@
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
+from __future__ import with_statement
+
 __metaclass__ = type
 
 from datetime import (
     datetime,
     timedelta,
     )
-from unittest import TestLoader
 
 import pytz
 
 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
 from canonical.testing import ZopelessDatabaseLayer
-from lp.testing import TestCaseWithFactory
+from lp.app.errors import UnexpectedFormData
+from lp.testing import (
+    anonymous_logged_in,
+    person_logged_in,
+    TestCaseWithFactory,
+    )
 from lp.translations.browser.translationmessage import (
+    CurrentTranslationMessagePageView,
     CurrentTranslationMessageView,
     )
+from lp.translations.interfaces.translationsperson import ITranslationsPerson
 
 
 class TestCurrentTranslationMessage_can_dismiss(TestCaseWithFactory):
@@ -168,5 +176,58 @@
         self._assertConfirmEmptyPluralPackaged(True, False, False, False)
 
 
-def test_suite():
-    return TestLoader().loadTestsFromName(__name__)
+class TestCurrentTranslationMessagePageView(TestCaseWithFactory):
+    """Test `CurrentTranslationMessagePageView` and its base class."""
+
+    layer = ZopelessDatabaseLayer
+
+    def _makeView(self, blank_timestamp=False):
+        message = self.factory.makeTranslationMessage()
+        request = LaunchpadTestRequest()
+        view = CurrentTranslationMessagePageView(message, request)
+        if blank_timestamp:
+            view.lock_timestamp = None
+        else:
+            view.lock_timestamp = datetime.now(pytz.utc)
+        return view
+
+    def test_extractLockTimestamp(self):
+        view = self._makeView()
+        view.request.form['lock_timestamp'] = u'2010-01-01 00:00:00 UTC'
+        self.assertEqual(
+            datetime(2010, 01, 01, tzinfo=pytz.utc),
+            view._extractLockTimestamp())
+
+    def test_extractLockTimestamp_returns_None_by_default(self):
+        view = self._makeView()
+        self.assertIs(None, view._extractLockTimestamp())
+
+    def test_extractLockTimestamp_returns_None_for_bogus_timestamp(self):
+        view = self._makeView()
+        view.request.form['lock_timestamp'] = u'Hi mom!'
+        self.assertIs(None, view._extractLockTimestamp())
+
+    def test_checkSubmitConditions_passes(self):
+        with person_logged_in(self.factory.makePerson()):
+            view = self._makeView()
+            view._checkSubmitConditions()
+
+    def test_checkSubmitConditions_requires_lock_timestamp(self):
+        with person_logged_in(self.factory.makePerson()):
+            view = self._makeView(blank_timestamp=True)
+            self.assertRaises(UnexpectedFormData, view._checkSubmitConditions)
+
+    def test_checkSubmitConditions_rejects_anonymous_request(self):
+        with anonymous_logged_in():
+            view = self._makeView()
+            self.assertRaises(UnexpectedFormData, view._checkSubmitConditions)
+
+    def test_checkSubmitConditions_rejects_license_decliners(self):
+        # Users who have declined the relicensing agreement can't post
+        # translations.
+        decliner = self.factory.makePerson()
+        ITranslationsPerson(decliner).translations_relicensing_agreement = (
+            False)
+        with person_logged_in(decliner):
+            view = self._makeView()
+            self.assertRaises(UnexpectedFormData, view._checkSubmitConditions)

=== modified file 'lib/lp/translations/browser/tests/translationmessage-views.txt'
--- lib/lp/translations/browser/tests/translationmessage-views.txt	2010-09-06 10:40:54 +0000
+++ lib/lp/translations/browser/tests/translationmessage-views.txt	2010-10-14 11:26:43 +0000
@@ -11,8 +11,8 @@
     >>> from lp.services.worlddata.interfaces.language import ILanguageSet
     >>> from canonical.launchpad.webapp import canonical_url
 
-All the tests will be submitted as comming from Kurem, an editor for the POFile
-that we are going to edit.
+All the tests will be submitted as comming from Kurem, an editor for the
+POFile that we are going to edit.
 
     >>> login('kurem@xxxxxxxxx')
 
@@ -230,30 +230,6 @@
 
 A new translation is submitted through the view.
 
-    >>> server_url = '/'.join(
-    ...     [canonical_url(translationmessage), '+translate'])
-    >>> form = {
-    ...     'alt': None,
-    ...     'msgset_1': None,
-    ...     'msgset_1_es_translation_0_new_checkbox': True,
-    ...     'msgset_1_es_translation_0_new': 'Foo',
-    ...     'submit_translations': 'Save & Continue'}
-    >>> translationmessage_page_view = create_view(
-    ...     translationmessage, "+translate", form=form,
-    ...     layer=TranslationsLayer, server_url=server_url)
-    >>> translationmessage_page_view.request.method = 'POST'
-
-And when we initialise the view class, we detect that we missed the timestamp
-that allow us to know whether we are working with latest submitted value or
-someone updated the database while we were working on those translations.
-
-    >>> translationmessage_page_view.initialize()
-    Traceback (most recent call last):
-    ...
-    UnexpectedFormData: We didn't find the timestamp...
-
-We do a new submit, but this time including a lock_timestamp field.
-
     >>> form = {
     ...     'lock_timestamp': '2006-11-28T13:00:00+00:00',
     ...     'alt': None,
@@ -269,8 +245,6 @@
     >>> translationmessage_page_view.initialize()
     >>> transaction.commit()
 
-This time we didn't get any problem with the submission.
-
 Now, let's see how the system prevents a submission that has a timestamp older
 than when last current translation was submitted.
 

=== modified file 'lib/lp/translations/browser/translationmessage.py'
--- lib/lp/translations/browser/translationmessage.py	2010-09-06 10:40:54 +0000
+++ lib/lp/translations/browser/translationmessage.py	2010-10-14 11:26:43 +0000
@@ -36,6 +36,7 @@
 from zope.interface import implements
 from zope.schema.vocabulary import getVocabularyRegistry
 
+from canonical.launchpad.readonly import is_read_only
 from canonical.launchpad.webapp import (
     ApplicationMenu,
     canonical_url,
@@ -64,6 +65,7 @@
     RosettaTranslationOrigin,
     TranslationConflict,
     )
+from lp.translations.interfaces.translations import TranslationConstants
 from lp.translations.interfaces.translationsperson import ITranslationsPerson
 from lp.translations.utilities.validate import GettextValidationError
 
@@ -230,9 +232,6 @@
     """
 
     pofile = None
-    # There will never be 100 plural forms.  Usually, we'll be iterating
-    # over just two or three.
-    MAX_PLURAL_FORMS = 100
 
     @property
     def label(self):
@@ -246,6 +245,9 @@
     def initialize(self):
         assert self.pofile, "Child class must define self.pofile"
 
+        self.has_plural_form_information = (
+            self.pofile.hasPluralFormInformation())
+
         # These two dictionaries hold translation data parsed from the
         # form submission. They exist mainly because of the need to
         # redisplay posted translations when they contain errors; if not
@@ -286,6 +288,16 @@
 
         self._initializeAltLanguage()
 
+        method = self.request.method
+        if method == 'POST':
+            self.lock_timestamp = self._extractLockTimestamp()
+            self._checkSubmitConditions()
+        else:
+            # It's not a POST, so we should generate lock_timestamp.
+            UTC = pytz.timezone('UTC')
+            self.lock_timestamp = datetime.datetime.now(UTC)
+
+
         # The batch navigator needs to be initialized early, before
         # _submitTranslations is called; the reason for this is that
         # _submitTranslations, in the case of no errors, redirects to
@@ -297,50 +309,65 @@
         self.start = self.batchnav.start
         self.size = self.batchnav.currentBatch().size
 
-        if self.request.method == 'POST':
-            if self.user is None:
-                raise UnexpectedFormData(
-                    "Anonymous users or users who are not accepting our "
-                    "licensing terms cannot do POST submissions.")
-            translations_person = ITranslationsPerson(self.user)
-            if (translations_person.translations_relicensing_agreement
-                    is not None and
-                not translations_person.translations_relicensing_agreement):
-                raise UnexpectedFormData(
-                    "Users who do not agree to licensing terms "
-                    "cannot do POST submissions.")
-            try:
-                # Try to get the timestamp when the submitted form was
-                # created. We use it to detect whether someone else updated
-                # the translation we are working on in the elapsed time
-                # between the form loading and its later submission.
-                self.lock_timestamp = zope_datetime.parseDatetimetz(
-                    self.request.form.get('lock_timestamp', u''))
-            except zope_datetime.DateTimeError:
-                # invalid format. Either we don't have the timestamp in the
-                # submitted form or it has the wrong format.
-                raise UnexpectedFormData(
-                    "We didn't find the timestamp that tells us when was"
-                    " generated the submitted form.")
-
-            # Check if this is really the form we are listening for..
-            if self.request.form.get("submit_translations"):
-                # Check if this is really the form we are listening for..
-                if self._submitTranslations():
-                    # .. and if no errors occurred, adios. Otherwise, we
-                    # need to set up the subviews for error display and
-                    # correction.
-                    return
-        else:
-            # It's not a POST, so we should generate lock_timestamp.
-            UTC = pytz.timezone('UTC')
-            self.lock_timestamp = datetime.datetime.now(UTC)
+        if method == 'POST' and self.request.form.get('submit_translations'):
+            if self._submitTranslations():
+                # If no errors occurred, adios. Otherwise, we need to set up
+                # the subviews for error display and correction.
+                return
 
         # Slave view initialization depends on _submitTranslations being
         # called, because the form data needs to be passed in to it --
         # again, because of error handling.
         self._initializeTranslationMessageViews()
 
+    def _extractLockTimestamp(self):
+        """Extract the lock timestamp from the request.
+
+        The lock_timestamp is used to detect conflicting concurrent
+        translation updates: if the translation that is being changed
+        has been set after the current form was generated, the user
+        chose a translation based on outdated information.  In that
+        case there is a conflict.
+        """
+        try:
+            return zope_datetime.parseDatetimetz(
+                self.request.form.get('lock_timestamp', u''))
+        except zope_datetime.DateTimeError:
+            # invalid format. Either we don't have the timestamp in the
+            # submitted form or it has the wrong format.
+            return None
+
+    def _checkSubmitConditions(self):
+        """Verify that this submission is possible and valid.
+
+        :raises: `UnexpectedFormData` if conditions are not met.  In
+            principle the user should not have been given the option to
+            submit the current request.
+        """
+        if is_read_only():
+            raise UnexpectedFormData(
+                "Launchpad is currently in read-only mode for maintenance.  "
+                "Please try again later.")
+
+        if self.user is None:
+            raise UnexpectedFormData("You are not logged in.")
+
+        # Users who have declined the licensing agreement can't post
+        # translations.  We don't stop users who haven't made a decision
+        # yet at this point; they may be project owners making
+        # corrections.
+        translations_person = ITranslationsPerson(self.user)
+        relicensing = translations_person.translations_relicensing_agreement
+        if relicensing is not None and not relicensing:
+            raise UnexpectedFormData(
+                "You can't post translations since you have not agreed to "
+                "our translations licensing terms.")
+
+        if self.lock_timestamp is None:
+            raise UnexpectedFormData(
+                "Your form submission did not contain the lock_timestamp "
+                "that tells Launchpad when the submitted form was generated.")
+
     #
     # API Hooks
     #
@@ -587,15 +614,6 @@
         self.second_lang_code = second_lang_code
 
     @property
-    def has_plural_form_information(self):
-        """Return whether we know the plural forms for this language."""
-        if self.pofile.potemplate.hasPluralMessage():
-            return self.pofile.language.pluralforms is not None
-        # If there are no plural forms, we assume that we have the
-        # plural form information for this language.
-        return True
-
-    @property
     def user_is_official_translator(self):
         """Determine whether the current user is an official translator."""
         return self.pofile.canEditTranslations(self.user)
@@ -658,7 +676,7 @@
         # Extract the translations from the form, and store them in
         # self.form_posted_translations. We try plural forms in turn,
         # starting at 0.
-        for pluralform in xrange(self.MAX_PLURAL_FORMS):
+        for pluralform in xrange(TranslationConstants.MAX_PLURAL_FORMS):
             msgset_ID_LANGCODE_translation_PLURALFORM_new = '%s%d_new' % (
                 msgset_ID_LANGCODE_translation_, pluralform)
             if msgset_ID_LANGCODE_translation_PLURALFORM_new not in form:
@@ -737,7 +755,7 @@
                     potmsgset].append(pluralform)
         else:
             raise AssertionError('More than %d plural forms were submitted!'
-                                 % self.MAX_PLURAL_FORMS)
+                                 % TranslationConstants.MAX_PLURAL_FORMS)
 
     def _observeTranslationUpdate(self, potmsgset):
         """Observe that a translation was updated for the potmsgset.
@@ -1056,7 +1074,7 @@
 
             diverged_and_have_shared = (
                 self.context.potemplate is not None and
-                self.shared_translationmessage is not None) 
+                self.shared_translationmessage is not None)
             if diverged_and_have_shared:
                 pofile = self.shared_translationmessage.ensureBrowserPOFile()
                 if pofile is None:
@@ -1155,10 +1173,10 @@
 
     def _setOnePOFile(self, messages):
         """Return a list of messages that all have a browser_pofile set.
-        
+
         If a pofile cannot be found for a message, it is not included in
         the resulting list.
-        """ 
+        """
         result = []
         for message in messages:
             if message.browser_pofile is None:
@@ -1170,7 +1188,7 @@
                     message.setPOFile(pofile)
             result.append(message)
         return result
-    
+
     def _buildAllSuggestions(self):
         """Builds all suggestions and puts them into suggestions_block.
 

=== modified file 'lib/lp/translations/interfaces/pofile.py'
--- lib/lp/translations/interfaces/pofile.py	2010-09-06 10:40:54 +0000
+++ lib/lp/translations/interfaces/pofile.py	2010-10-14 11:26:43 +0000
@@ -168,6 +168,9 @@
         for.
         """
 
+    def hasPluralFormInformation():
+        """Do we know the plural-forms information for this `POFile`?"""
+
     def getHeader():
         """Return an `ITranslationHeaderData` representing its header."""
 

=== modified file 'lib/lp/translations/model/pofile.py'
--- lib/lp/translations/model/pofile.py	2010-09-30 08:51:21 +0000
+++ lib/lp/translations/model/pofile.py	2010-10-14 11:26:43 +0000
@@ -177,6 +177,24 @@
         return False
 
 
+def is_admin(user):
+    """Is `user` an admin or Translations admin?"""
+    celebs = getUtility(ILaunchpadCelebrities)
+    return user.inTeam(celebs.admin) or user.inTeam(celebs.rosetta_experts)
+
+
+def is_product_owner(user, potemplate):
+    """Is `user` the owner of a `Product` that `potemplate` belongs to?
+
+    A product's owners have edit rights on the product's translations.
+    """
+    productseries = potemplate.productseries
+    if productseries is None:
+        return False
+
+    return user.inTeam(productseries.product.owner)
+
+
 def _can_edit_translations(pofile, person):
     """Say if a person is able to edit existing translations.
 
@@ -190,29 +208,25 @@
     translation team for the given `IPOFile`.translationpermission and the
     language associated with this `IPOFile`.
     """
+    if person is None:
+        # Anonymous users can't edit anything.
+        return False
+
     if is_read_only():
         # Nothing can be edited in read-only mode.
         return False
 
-    # If the person is None, then they cannot edit
-    if person is None:
-        return False
-
     # XXX Carlos Perello Marin 2006-02-07 bug=4814:
     # We should not check the permissions here but use the standard
     # security system.
 
     # Rosetta experts and admins can always edit translations.
-    admins = getUtility(ILaunchpadCelebrities).admin
-    rosetta_experts = getUtility(ILaunchpadCelebrities).rosetta_experts
-    if (person.inTeam(admins) or person.inTeam(rosetta_experts)):
+    if is_admin(person):
         return True
 
     # The owner of the product is also able to edit translations.
-    if pofile.potemplate.productseries is not None:
-        product = pofile.potemplate.productseries.product
-        if person.inTeam(product.owner):
-            return True
+    if is_product_owner(person, pofile.potemplate):
+        return True
 
     # If a person has decided not to license their translations under BSD
     # license they can't edit translations.
@@ -284,6 +298,16 @@
         """See `IPOFile`."""
         return self.language.guessed_pluralforms
 
+    def hasPluralFormInformation(self):
+        """See `IPOFile`."""
+        if self.language.pluralforms is None:
+            # We have no plural information for this language.  It
+            # doesn't actually matter unless the template contains
+            # messages with plural forms.
+            return not self.potemplate.hasPluralMessage()
+        else:
+            return True
+
     def canEditTranslations(self, person):
         """See `IPOFile`."""
         return _can_edit_translations(self, person)

=== modified file 'lib/lp/translations/tests/test_pofile.py'
--- lib/lp/translations/tests/test_pofile.py	2010-09-30 17:57:01 +0000
+++ lib/lp/translations/tests/test_pofile.py	2010-10-14 11:26:43 +0000
@@ -1888,6 +1888,31 @@
         self.pofile.markChanged(translator=translator)
         self.assertEqual(translator, self.pofile.lasttranslator)
 
+    def test_hasPluralFormInformation_bluffs_if_irrelevant(self):
+        # If the template has no messages that use plural forms, the
+        # POFile has all the relevant plural-form information regardless
+        # of whether we know the plural forms for the language.
+        language = self.factory.makeLanguage()
+        pofile, potmsgset = self.factory.makePOFileAndPOTMsgSet(
+            language.code, with_plural=False)
+        self.assertTrue(pofile.hasPluralFormInformation())
+
+    def test_hasPluralFormInformation_admits_defeat(self):
+        # If there are messages with plurals, hasPluralFormInformation
+        # needs the plural-form information for the language.
+        language = self.factory.makeLanguage()
+        pofile, potmsgset = self.factory.makePOFileAndPOTMsgSet(
+            language.code, with_plural=True)
+        self.assertFalse(pofile.hasPluralFormInformation())
+
+    def test_hasPluralFormInformation_uses_language_info(self):
+        # hasPluralFormInformation returns True if plural forms
+        # information is available for the language.
+        language = self.factory.makeLanguage(pluralforms=5)
+        pofile, potmsgset = self.factory.makePOFileAndPOTMsgSet(
+            language.code, with_plural=True)
+        self.assertTrue(pofile.hasPluralFormInformation())
+
 
 class TestPOFileTranslationMessages(TestCaseWithFactory):
     """Test PO file getTranslationMessages method."""
@@ -1905,66 +1930,66 @@
         # A shared message is included in this POFile's messages.
         message = self.factory.makeTranslationMessage(
             potmsgset=self.potmsgset, pofile=self.pofile, force_shared=True)
-        
+
         self.assertEqual(
             [message], list(self.pofile.getTranslationMessages()))
-        
+
     def test_getTranslationMessages_current_diverged(self):
         # A diverged message is included in this POFile's messages.
         message = self.factory.makeTranslationMessage(
             potmsgset=self.potmsgset, pofile=self.pofile, force_diverged=True)
-        
+
         self.assertEqual(
             [message], list(self.pofile.getTranslationMessages()))
-        
+
     def test_getTranslationMessages_suggestion(self):
         # A suggestion is included in this POFile's messages.
         message = self.factory.makeTranslationMessage(
             potmsgset=self.potmsgset, pofile=self.pofile)
-        
+
         self.assertEqual(
             [message], list(self.pofile.getTranslationMessages()))
-        
+
     def test_getTranslationMessages_obsolete(self):
         # A message on an obsolete POTMsgSEt is included in this
         # POFile's messages.
         potmsgset = self.factory.makePOTMsgSet(self.potemplate, sequence=0)
         message = self.factory.makeTranslationMessage(
             potmsgset=potmsgset, pofile=self.pofile, force_shared=True)
-        
+
         self.assertEqual(
             [message], list(self.pofile.getTranslationMessages()))
-        
+
     def test_getTranslationMessages_other_pofile(self):
         # A message from another POFiles is not included.
         other_pofile = self.factory.makePOFile('de')
         self.factory.makeTranslationMessage(
             potmsgset=self.potmsgset, pofile=other_pofile)
-        
+
         self.assertEqual([], list(self.pofile.getTranslationMessages()))
-        
+
     def test_getTranslationMessages_condition_matches(self):
         # A message matching the given condition is included.
         # Diverged messages are linked to a specific POTemplate.
         message = self.factory.makeTranslationMessage(
             potmsgset=self.potmsgset, pofile=self.pofile, force_diverged=True)
-        
+
         self.assertContentEqual(
             [message],
             self.pofile.getTranslationMessages(
                 "TranslationMessage.potemplate IS NOT NULL"))
-       
+
     def test_getTranslationMessages_condition_matches_not(self):
         # A message not matching the given condition is excluded.
         # Shared messages are not linked to a POTemplate.
         self.factory.makeTranslationMessage(
             potmsgset=self.potmsgset, pofile=self.pofile, force_shared=True)
-        
+
         self.assertContentEqual(
             [],
             self.pofile.getTranslationMessages(
                 "TranslationMessage.potemplate IS NOT NULL"))
-       
+
     def test_getTranslationMessages_condition_matches_in_other_pofile(self):
         # A message matching given condition but located in another POFile
         # is not included.
@@ -1972,12 +1997,12 @@
         self.factory.makeTranslationMessage(
             potmsgset=self.potmsgset, pofile=other_pofile,
             force_diverged=True)
-        
+
         self.assertContentEqual(
             [],
             self.pofile.getTranslationMessages(
                 "TranslationMessage.potemplate IS NOT NULL"))
-       
+
     def test_getTranslationMessages_diverged_elsewhere(self):
         # Diverged messages from sharing POTemplates are not included.
         # Create a sharing potemplate in another product series and share
@@ -1992,9 +2017,9 @@
         self.factory.makeTranslationMessage(
             potmsgset=self.potmsgset, pofile=other_pofile,
             force_diverged=True)
-        
+
         self.assertEqual([], list(self.pofile.getTranslationMessages()))
-       
+
 
 class TestPOFileToTranslationFileDataAdapter(TestCaseWithFactory):
     """Test POFile being adapted to IPOFileToTranslationFileData."""

=== modified file 'lib/lp/translations/utilities/tests/test_file_importer.py'
--- lib/lp/translations/utilities/tests/test_file_importer.py	2010-10-06 11:05:13 +0000
+++ lib/lp/translations/utilities/tests/test_file_importer.py	2010-10-14 11:26:43 +0000
@@ -613,7 +613,7 @@
         if side == self.UPSTREAM:
             potemplate = self.upstream_template
         else:
-            # Create a template in a source package. 
+            # Create a template in a source package.
             ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
             distroseries = self.factory.makeDistroSeries(distribution=ubuntu)
             ubuntu.translation_focus = distroseries
@@ -639,7 +639,7 @@
             uploader=uploader, content=self.POFILE)
         entry.potemplate = potemplate
         entry.pofile = pofile
-        # The uploaded file is only created in the librarian by a commit. 
+        # The uploaded file is only created in the librarian by a commit.
         transaction.commit()
         return entry
 

=== modified file 'lib/lp/translations/utilities/translation_import.py'
--- lib/lp/translations/utilities/translation_import.py	2010-10-01 14:37:53 +0000
+++ lib/lp/translations/utilities/translation_import.py	2010-10-14 11:26:43 +0000
@@ -493,7 +493,7 @@
         """Are these English strings instead of translations?
 
         If this template uses symbolic message ids, the English POFile
-        will contain the English original texts that correspond to the 
+        will contain the English original texts that correspond to the
         symbols."""
         return (
             self.importer.uses_source_string_msgids and


Follow ups