← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~henninge/launchpad/bug-347117-duplicate-template-name into lp:launchpad

 

Henning Eggers has proposed merging lp:~henninge/launchpad/bug-347117-duplicate-template-name into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #347117 in Launchpad itself: ""Duplicate key" when moving a template."
  https://bugs.launchpad.net/launchpad/+bug/347117

For more details, see:
https://code.launchpad.net/~henninge/launchpad/bug-347117-duplicate-template-name/+merge/63710

= Summary =

This is about improving the admin form for a potemplate to not allow a
template to be moved to a different source package or productseries that
already has a template of that name or with that translation domain.

== Proposed fix ==

The view code for the admin form already validates the name and
translation domain by checking for duplicates. This needs to be extended
to also check when the souce package or productseries changes.

== Pre-implementation notes ==

I think I talked to somebody about this but I forgot who it was.
Maybe Deryck?

== Implementation details ==

The implementation of the fix is very straight forward. A lot is about
selecting the right error message.

This validation behavior was previously tested in a page test. I moved
those tests into a new view unittest and added tests for the new
validation code.

The old page test neede its indentation fixed from 2 to 4 spaces, which
makes the diff quite large.

== Tests ==

bin/text -vvcm lp.translations.browser.tests.test_potemplate_views

== Demo and Q/A ==

- Find two sourcepackages which each have at least one template.
- Change the name of the template in one sourcepackage to be the same
  as the template in the other package.
- Go to the other template and try to change the source package to the
  first package - you should see a field error on the sourcepackagename
  field.
- Repeat for translation domain.
- Rpeat both with two productseries.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/translations/browser/tests/test_potemplate_views.py
  lib/lp/translations/browser/potemplate.py
-- 
https://code.launchpad.net/~henninge/launchpad/bug-347117-duplicate-template-name/+merge/63710
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~henninge/launchpad/bug-347117-duplicate-template-name into lp:launchpad.
=== modified file 'lib/lp/translations/browser/potemplate.py'
--- lib/lp/translations/browser/potemplate.py	2011-03-30 11:17:35 +0000
+++ lib/lp/translations/browser/potemplate.py	2011-06-07 14:23:31 +0000
@@ -596,27 +596,39 @@
     label = 'Administer translation template'
     page_title = "Administer"
 
-    def validateName(self, name, similar_templates):
-        """Form submission changes template name.  Validate it."""
-        if name == self.context.name:
-            # Not changed.
-            return
-
+    def validateName(self, name, similar_templates,
+                     sourcepackage_changed, productseries_changed):
+        """Check that the name does not clash with an existing template."""
         if similar_templates.getPOTemplateByName(name) is not None:
-            self.setFieldError('name', "Name is already in use.")
-            return
-
-    def validateDomain(self, domain, similar_templates):
-        if domain == self.context.translation_domain:
-            # Not changed.
-            return
-
+            if sourcepackage_changed:
+                self.setFieldError(
+                    'sourcepackagename',
+                    "Source package already has a template with "
+                    "that same name.")
+            elif productseries_changed:
+                self.setFieldError(
+                    'productseries',
+                    "Series already has a template with that same name.")
+            elif name != self.context.name:
+                self.setFieldError('name', "Name is already in use.")
+
+    def validateDomain(self, domain, similar_templates,
+                       sourcepackage_changed, productseries_changed):
         other_template = similar_templates.getPOTemplateByTranslationDomain(
             domain)
         if other_template is not None:
-            self.setFieldError(
-                'translation_domain', "Domain is already in use.")
-            return
+            if sourcepackage_changed:
+                self.setFieldError(
+                    'sourcepackagename',
+                    "Source package already has a template with "
+                    "that same domain.")
+            elif productseries_changed:
+                self.setFieldError(
+                    'productseries',
+                    "Series already has a template with that same domain.")
+            elif domain != self.context.translation_domain:
+                self.setFieldError(
+                    'translation_domain', "Domain is already in use.")
 
     def validate(self, data):
         super(POTemplateAdminView, self).validate(data)
@@ -645,9 +657,17 @@
         similar_templates = getUtility(IPOTemplateSet).getSubset(
             distroseries=distroseries, sourcepackagename=sourcepackagename,
             productseries=productseries)
+        sourcepackage_changed = (
+            distroseries != self.context.distroseries or
+            sourcepackagename != self.context.sourcepackagename)
+        productseries_changed = productseries != self.context.productseries
 
-        self.validateName(data.get('name'), similar_templates)
-        self.validateDomain(data.get('translation_domain'), similar_templates)
+        self.validateName(
+            data.get('name'), similar_templates,
+            sourcepackage_changed, productseries_changed)
+        self.validateDomain(
+            data.get('translation_domain'), similar_templates,
+            sourcepackage_changed, productseries_changed)
 
 
 class POTemplateExportView(BaseExportView):

=== added file 'lib/lp/translations/browser/tests/test_potemplate_views.py'
--- lib/lp/translations/browser/tests/test_potemplate_views.py	1970-01-01 00:00:00 +0000
+++ lib/lp/translations/browser/tests/test_potemplate_views.py	2011-06-07 14:23:31 +0000
@@ -0,0 +1,152 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Module doc."""
+
+__metaclass__ = type
+
+
+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.testing import TestCaseWithFactory
+from lp.translations.browser.potemplate import POTemplateAdminView
+
+
+class TestPOTemplateAdminViewValidation(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def _makeData(self, potemplate, **kwargs):
+        """Create form data for the given template with some changed values.
+
+        The attributes are only those considered by the validate method.
+        """
+        attributes = [
+            'distroseries', 'sourcepackagename', 'productseries',
+            'name', 'translation_domain']
+        data = dict(
+            [(name, getattr(potemplate, name)) for name in attributes])
+        data.update(**kwargs)
+        return data
+
+    def test_detects_name_clash_on_name_change(self):
+        # A template name may not already be used.
+        existing_name = self.factory.getUniqueString()
+        existing_potemplate = self.factory.makePOTemplate(name=existing_name)
+        series = existing_potemplate.productseries
+        potemplate = self.factory.makePOTemplate(productseries=series)
+
+        view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+        data = self._makeData(potemplate, name=existing_name)
+        view.validate(data)
+        self.assertEqual([u'Name is already in use.'], view.errors)
+
+    def test_detects_domain_clash_on_domain_change(self):
+        # A translation domain may not already be used.
+        existing_domain = self.factory.getUniqueString()
+        existing_potemplate = self.factory.makePOTemplate(
+            translation_domain=existing_domain)
+        series = existing_potemplate.productseries
+        potemplate = self.factory.makePOTemplate(productseries=series)
+
+        view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+        data = self._makeData(potemplate, translation_domain=existing_domain)
+        view.validate(data)
+        self.assertEqual([u'Domain is already in use.'], view.errors)
+
+    def test_detects_name_clash_on_productseries_change(self):
+        # Detect changing to a productseries that already has a template of
+        # the same name.
+        template_name = self.factory.getUniqueString()
+        existing_potemplate = self.factory.makePOTemplate(name=template_name)
+        new_series = existing_potemplate.productseries
+        potemplate = self.factory.makePOTemplate(name=template_name)
+
+        view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+        data = self._makeData(potemplate, productseries=new_series)
+        view.validate(data)
+        self.assertEqual(
+            [u'Series already has a template with that same name.'],
+            view.errors)
+
+    def test_detects_domain_clash_on_productseries_change(self):
+        # Detect changing to a productseries that already has a template with
+        # the same translation domain.
+        translation_domain = self.factory.getUniqueString()
+        existing_potemplate = self.factory.makePOTemplate(
+            translation_domain=translation_domain)
+        new_series = existing_potemplate.productseries
+        potemplate = self.factory.makePOTemplate(
+            translation_domain=translation_domain)
+
+        view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+        data = self._makeData(potemplate, productseries=new_series)
+        view.validate(data)
+        self.assertEqual(
+            [u'Series already has a template with that same domain.'],
+            view.errors)
+
+    def test_detects_name_clash_on_sourcepackage_change(self):
+        # Detect changing to a source package that already has a template of
+        # the same name.
+        sourcepackage = self.factory.makeSourcePackage()
+        existing_potemplate = self.factory.makePOTemplate(
+            sourcepackage=sourcepackage)
+        potemplate = self.factory.makePOTemplate(
+            distroseries=sourcepackage.distroseries,
+            name=existing_potemplate.name)
+
+        view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+        data = self._makeData(
+            potemplate, sourcepackagename=sourcepackage.sourcepackagename)
+        view.validate(data)
+        self.assertEqual(
+            [u'Source package already has a template with that same name.'],
+            view.errors)
+
+    def test_detects_domain_clash_on_sourcepackage_change(self):
+        # Detect changing to a productseries that already has a template with
+        # the same translation domain.
+        sourcepackage = self.factory.makeSourcePackage()
+        existing_potemplate = self.factory.makePOTemplate(
+            sourcepackage=sourcepackage)
+        potemplate = self.factory.makePOTemplate(
+            distroseries=sourcepackage.distroseries,
+            translation_domain=existing_potemplate.translation_domain)
+
+        view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+        data = self._makeData(
+            potemplate, sourcepackagename=sourcepackage.sourcepackagename)
+        view.validate(data)
+        self.assertEqual(
+            [u'Source package already has a template with that same domain.'],
+            view.errors)
+
+    def test_detects_no_sourcepackage_or_productseries(self):
+        # Detect if no source package or productseries was selected.
+        potemplate = self.factory.makePOTemplate()
+
+        view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+        data = self._makeData(
+            potemplate,
+            distroseries=None, sourcepackagename=None, productseries=None)
+        view.validate(data)
+        self.assertEqual(
+            [u'Choose either a distribution release series or a project '
+             u'release series.'], view.errors)
+
+    def test_detects_sourcepackage_and_productseries(self):
+        # Detect if no source package or productseries was selected.
+        potemplate = self.factory.makePOTemplate()
+        sourcepackage = self.factory.makeSourcePackage()
+
+        view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+        data = self._makeData(
+            potemplate,
+            distroseries=sourcepackage.distroseries,
+            sourcepackagename=sourcepackage.sourcepackagename,
+            productseries=potemplate.productseries)
+        view.validate(data)
+        self.assertEqual(
+            [u'Choose a distribution release series or a project '
+             u'release series, but not both.'], view.errors)

=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-admin.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-admin.txt	2010-02-01 19:19:32 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-admin.txt	2011-06-07 14:23:31 +0000
@@ -10,177 +10,178 @@
 
 An unprivileged user cannot reach the POTemplate administration page.
 
-  >>> user_browser.open(
-  ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
-  ...     'evolution-2.2/+admin')
-  Traceback (most recent call last):
-  ...
-  Unauthorized:...
+    >>> user_browser.open(
+    ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
+    ...     'evolution-2.2/+admin')
+    Traceback (most recent call last):
+    ...
+    Unauthorized:...
 
 Jordi, a Rosetta expert, can.
 
-  >>> browser = setupBrowser(auth='Basic jordi@xxxxxxxxxx:test')
-  >>> browser.open(
-  ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
-  ...     'evolution-2.2/+admin')
-  >>> print browser.url
-  http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/+admin
+    >>> browser = setupBrowser(auth='Basic jordi@xxxxxxxxxx:test')
+    >>> browser.open(
+    ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
+    ...     'evolution-2.2/+admin')
+    >>> print browser.url
+    http://translations...dev/evolution/trunk/+pots/evolution-2.2/+admin
 
 Now, we should be sure that the admin form gets all required fields to allow
 a Rosetta Expert to administer it.
 
-  >>> browser.getControl(name='field.name').value
-  'evolution-2.2'
-  >>> browser.getControl('Translation domain').value
-  'evolution-2.2'
-  >>> browser.getControl(name='field.description').value
-  'Template for evolution in hoary'
-  >>> print browser.getControl(name='field.header').value
-  Project-Id-Version: PACKAGE VERSION
-  Report-Msgid-Bugs-To:
-  POT-Creation-Date: 2005-08-25 14:56+0200
-  PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
-  Last-Translator: FULL NAME <EMAIL@ADDRESS>
-  Language-Team: LANGUAGE <LL@xxxxxx>
-  MIME-Version: 1.0
-  Content-Type: text/plain; charset=CHARSET
-  Content-Transfer-Encoding: 8bit
-  Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;
-  <BLANKLINE>
-  >>> browser.getControl(name='field.iscurrent').value
-  True
-  >>> browser.getControl(name='field.owner').value
-  'rosetta-admins'
-  >>> browser.getControl(name='field.productseries').value
-  'evolution/trunk'
-  >>> browser.getControl(name='field.distroseries').value
-  ['']
-  >>> browser.getControl(name='field.sourcepackagename').value
-  ''
-  >>> browser.getControl(name='field.sourcepackageversion').value
-  ''
-  >>> browser.getControl(name='field.binarypackagename').value
-  ''
-  >>> browser.getControl(name='field.languagepack').value
-  False
-  >>> browser.getControl(name='field.path').value
-  'po/evolution-2.2.pot'
+    >>> browser.getControl(name='field.name').value
+    'evolution-2.2'
+    >>> browser.getControl('Translation domain').value
+    'evolution-2.2'
+    >>> browser.getControl(name='field.description').value
+    'Template for evolution in hoary'
+    >>> print browser.getControl(name='field.header').value
+    Project-Id-Version: PACKAGE VERSION
+    Report-Msgid-Bugs-To:
+    POT-Creation-Date: 2005-08-25 14:56+0200
+    PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
+    Last-Translator: FULL NAME <EMAIL@ADDRESS>
+    Language-Team: LANGUAGE <LL@xxxxxx>
+    MIME-Version: 1.0
+    Content-Type: text/plain; charset=CHARSET
+    Content-Transfer-Encoding: 8bit
+    Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;
+    <BLANKLINE>
+    >>> browser.getControl(name='field.iscurrent').value
+    True
+    >>> browser.getControl(name='field.owner').value
+    'rosetta-admins'
+    >>> browser.getControl(name='field.productseries').value
+    'evolution/trunk'
+    >>> browser.getControl(name='field.distroseries').value
+    ['']
+    >>> browser.getControl(name='field.sourcepackagename').value
+    ''
+    >>> browser.getControl(name='field.sourcepackageversion').value
+    ''
+    >>> browser.getControl(name='field.binarypackagename').value
+    ''
+    >>> browser.getControl(name='field.languagepack').value
+    False
+    >>> browser.getControl(name='field.path').value
+    'po/evolution-2.2.pot'
 
 An administrator can also access the POTemplate administration page.
 
-  >>> admin_browser.open(
-  ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
-  ...     'evolution-2.2/+admin')
-  >>> print admin_browser.url
-  http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/+admin
+    >>> admin_browser.open(
+    ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
+    ...     'evolution-2.2/+admin')
+    >>> print admin_browser.url
+    http://translations...dev/evolution/trunk/+pots/evolution-2.2/+admin
 
 Now, we should be sure that the admin form gets all required fields to allow
 an admin to administer it.
 
-  >>> admin_browser.getControl(name='field.name').value
-  'evolution-2.2'
-  >>> admin_browser.getControl('Translation domain').value
-  'evolution-2.2'
-  >>> admin_browser.getControl(name='field.description').value
-  'Template for evolution in hoary'
-  >>> print admin_browser.getControl(name='field.header').value
-  Project-Id-Version: PACKAGE VERSION
-  Report-Msgid-Bugs-To:
-  POT-Creation-Date: 2005-08-25 14:56+0200
-  PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
-  Last-Translator: FULL NAME <EMAIL@ADDRESS>
-  Language-Team: LANGUAGE <LL@xxxxxx>
-  MIME-Version: 1.0
-  Content-Type: text/plain; charset=CHARSET
-  Content-Transfer-Encoding: 8bit
-  Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;
-  <BLANKLINE>
-  >>> admin_browser.getControl(name='field.iscurrent').value
-  True
-  >>> admin_browser.getControl(name='field.owner').value
-  'rosetta-admins'
-  >>> admin_browser.getControl(name='field.productseries').value
-  'evolution/trunk'
-  >>> admin_browser.getControl(name='field.distroseries').value
-  ['']
-  >>> admin_browser.getControl(name='field.sourcepackagename').value
-  ''
-  >>> admin_browser.getControl(name='field.sourcepackageversion').value
-  ''
-  >>> admin_browser.getControl(name='field.binarypackagename').value
-  ''
-  >>> admin_browser.getControl(name='field.languagepack').value
-  False
-  >>> admin_browser.getControl(name='field.path').value
-  'po/evolution-2.2.pot'
-  >>> from zope import datetime as zope_datetime
-  >>> old_date_last_updated = zope_datetime.parseDatetimetz(
-  ...     admin_browser.getControl('Date for last update').value)
-  >>> old_date_last_updated.isoformat()
-  '2005-08-25T15:27:55.264235+00:00'
+    >>> admin_browser.getControl(name='field.name').value
+    'evolution-2.2'
+    >>> admin_browser.getControl('Translation domain').value
+    'evolution-2.2'
+    >>> admin_browser.getControl(name='field.description').value
+    'Template for evolution in hoary'
+    >>> print admin_browser.getControl(name='field.header').value
+    Project-Id-Version: PACKAGE VERSION
+    Report-Msgid-Bugs-To:
+    POT-Creation-Date: 2005-08-25 14:56+0200
+    PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
+    Last-Translator: FULL NAME <EMAIL@ADDRESS>
+    Language-Team: LANGUAGE <LL@xxxxxx>
+    MIME-Version: 1.0
+    Content-Type: text/plain; charset=CHARSET
+    Content-Transfer-Encoding: 8bit
+    Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;
+    <BLANKLINE>
+    >>> admin_browser.getControl(name='field.iscurrent').value
+    True
+    >>> admin_browser.getControl(name='field.owner').value
+    'rosetta-admins'
+    >>> admin_browser.getControl(name='field.productseries').value
+    'evolution/trunk'
+    >>> admin_browser.getControl(name='field.distroseries').value
+    ['']
+    >>> admin_browser.getControl(name='field.sourcepackagename').value
+    ''
+    >>> admin_browser.getControl(name='field.sourcepackageversion').value
+    ''
+    >>> admin_browser.getControl(name='field.binarypackagename').value
+    ''
+    >>> admin_browser.getControl(name='field.languagepack').value
+    False
+    >>> admin_browser.getControl(name='field.path').value
+    'po/evolution-2.2.pot'
+    >>> from zope import datetime as zope_datetime
+    >>> old_date_last_updated = zope_datetime.parseDatetimetz(
+    ...     admin_browser.getControl('Date for last update').value)
+    >>> old_date_last_updated.isoformat()
+    '2005-08-25T15:27:55.264235+00:00'
 
 And that we are able to POST it.
 
-  >>> admin_browser.getControl('Translation domain').value = 'foo'
-  >>> admin_browser.getControl('Change').click()
-  >>> print admin_browser.url
-  http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2
+    >>> admin_browser.getControl('Translation domain').value = 'foo'
+    >>> admin_browser.getControl('Change').click()
+    >>> print admin_browser.url
+    http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2
 
 Going back to the form we can see that the changes are saved and also,
 the date when the template was last updated is now newer than previous
 value.
 
-  >>> admin_browser.getLink('Administer').click()
-  >>> admin_browser.getControl('Translation domain').value
-  'foo'
-  >>> zope_datetime.parseDatetimetz(admin_browser.getControl(
-  ...     'Date for last update').value) > old_date_last_updated
-  True
+    >>> admin_browser.getLink('Administer').click()
+    >>> admin_browser.getControl('Translation domain').value
+    'foo'
+    >>> zope_datetime.parseDatetimetz(admin_browser.getControl(
+    ...     'Date for last update').value) > old_date_last_updated
+    True
 
 We can also change the switch to note whether the template must be
 exported as part of language packs.
 
-  >>> admin_browser.getControl(name='field.languagepack').value = True
-  >>> admin_browser.getControl('Change').click()
+    >>> admin_browser.getControl(name='field.languagepack').value = True
+    >>> admin_browser.getControl('Change').click()
 
 Finally, let's rename 'evolution-2.2' to 'evolution-renamed'.
 
-  >>> admin_browser.open(
-  ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
-  ...     'evolution-2.2/+admin')
+    >>> admin_browser.open(
+    ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
+    ...     'evolution-2.2/+admin')
 
 Lets use this opportunity to check if languagepack was changed successfully
 above.
 
-  >>> admin_browser.getControl(name='field.languagepack').value
-  True
+    >>> admin_browser.getControl(name='field.languagepack').value
+    True
 
 And now let's get back to renaming.
 
-  >>> admin_browser.getControl(name='field.name').value = 'evolution-renamed'
-  >>> admin_browser.getControl('Change').click()
-  >>> print admin_browser.url
-  http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
+    >>> admin_browser.getControl(name='field.name').value = (
+    ...     'evolution-renamed')
+    >>> admin_browser.getControl('Change').click()
+    >>> print admin_browser.url
+    http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
 
 Administrators can disable and then make changes to a disabled template.
 
-  >>> admin_browser.open(
-  ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
-  ...     'evolution-renamed/+admin')
-  >>> admin_browser.getControl(name='field.iscurrent').value = False
-  >>> admin_browser.getControl('Change').click()
-  >>> print admin_browser.url
-  http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
+    >>> admin_browser.open(
+    ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
+    ...     'evolution-renamed/+admin')
+    >>> admin_browser.getControl(name='field.iscurrent').value = False
+    >>> admin_browser.getControl('Change').click()
+    >>> print admin_browser.url
+    http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
 
 Now we will reenable the template.
 
-  >>> admin_browser.open(
-  ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
-  ...     'evolution-renamed/+admin')
-  >>> admin_browser.getControl(name='field.iscurrent').value = True
-  >>> admin_browser.getControl('Change').click()
-  >>> print admin_browser.url
-  http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
+    >>> admin_browser.open(
+    ...     'http://translations.launchpad.dev/evolution/trunk/+pots/'
+    ...     'evolution-renamed/+admin')
+    >>> admin_browser.getControl(name='field.iscurrent').value = True
+    >>> admin_browser.getControl('Change').click()
+    >>> print admin_browser.url
+    http://translations.launchpad.dev/evolution/trunk/+pots/evolution-renamed
 
 
 Distribution templates
@@ -201,7 +202,8 @@
     >>> templatesubset = templateset.getSubset(
     ...     distroseries=hoary, sourcepackagename=package)
     >>> template_owner = factory.makePerson()
-    >>> template = templatesubset.new('foo', 'foo', 'foo.pot', template_owner)
+    >>> template = templatesubset.new(
+    ...     'foo', 'foo', 'foo.pot', template_owner)
 
     >>> distro_owner = factory.makePerson('do@xxxxxxxxxxx', password='test')
     >>> ubuntu.owner = distro_owner
@@ -252,69 +254,3 @@
     >>> group_owner_browser.open(template_admin_url)
     >>> group_owner_browser.getControl(name='field.iscurrent').value = True
     >>> group_owner_browser.getControl('Change').click()
-
-
-Input Check
------------
-
-The template admin form requires either a productseries or a
-distroseries.
-
-    >>> login(ANONYMOUS)
-    >>> product = factory.makeProduct(name='aproduct')
-    >>> template = factory.makePOTemplate(
-    ...     productseries=product.getSeries('trunk'))
-    >>> template_url = canonical_url(template)
-    >>> logout()
-
-    >>> admin_browser.open(template_url + '/+admin')
-
-    >>> admin_browser.getControl(name='field.distroseries').value = [None]
-    >>> admin_browser.getControl(name='field.productseries').value = ''
-    >>> admin_browser.getControl('Change').click()
-    >>> print_errors(admin_browser.contents)
-    There is 1 error.
-    Choose either a distribution release series or a project release series.
-
-It can't be both.
-
-    >>> admin_browser.open(template_url + '/+admin')
-
-    >>> admin_browser.getControl(name='field.distroseries').value = [
-    ...     'ubuntu/hoary']
-    >>> admin_browser.getControl(name='field.productseries').value = (
-    ...     'aproduct/trunk')
-    >>> admin_browser.getControl('Change').click()
-    >>> print_errors(admin_browser.contents)
-    There is 1 error.
-    Choose a distribution release series or a project release series,
-    but not both.
-
-Also, the form won't allow you to set a name that duplicates that of
-another template.
-
-    >>> login(ANONYMOUS)
-    >>> other_template = factory.makePOTemplate(
-    ...     productseries=template.productseries)
-    >>> other_name = other_template.name
-    >>> other_domain = other_template.translation_domain
-    >>> logout()
-
-    >>> admin_browser.open(template_url + '/+admin')
-    >>> admin_browser.getControl(name='field.name').value = other_name
-    >>> admin_browser.getControl('Change').click()
-    >>> print_errors(admin_browser.contents)
-    There is 1 error.
-    Template name: Name is already in use.
-    ...
-
-Similar for the translation domain.
-
-    >>> admin_browser.open(template_url + '/+admin')
-    >>> admin_browser.getControl(name='field.translation_domain').value = (
-    ...     other_domain)
-    >>> admin_browser.getControl('Change').click()
-    >>> print_errors(admin_browser.contents)
-    There is 1 error.
-    Translation domain: Domain is already in use.
-    ...


Follow ups