← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/fix-pocket-queue-admin-series into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/fix-pocket-queue-admin-series into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #648611 in Launchpad itself: "ubuntu-sru either have too much or too little permission as queue admins"
  https://bugs.launchpad.net/launchpad/+bug/648611

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/fix-pocket-queue-admin-series/+merge/118929

== Summary ==

This branch fixes a couple of problems with per-series pocket queue admin permissions that I noticed while QAing my previous branch for this bug.  My QA notes are in bug 648611.

== Proposed fix ==

The buggy query just needs extra parentheses.

I also noticed that I forgot to handle traversal of permissions with series= on Archive, so I fixed that up in the obvious way too.

== Implementation details ==

Along the way, I fixed a trivial case in Archive permission traversal where it was possible to get UnboundLocalError if you passed an unknown component or pocket.

To avoid increasing LoC count, I cleaned up lots of duplicate/unused imports and unnecessary wrapping in lib/lp/soyuz/stories/.  I'm afraid this has made the branch a little more verbose than it might be.  The interesting files are:

  lib/lp/soyuz/browser/archive.py
  lib/lp/soyuz/doc/archivepermission.txt
  lib/lp/soyuz/model/archivepermission.py
  lib/lp/soyuz/stories/webservice/xx-archive.txt

== Tests ==

bin/test -vvct soyuz/doc/archivepermission.txt -t soyuz/stories/webservice/xx-archive.txt

== Demo and Q/A ==

Repeat the QA from https://code.launchpad.net/~cjwatson/launchpad/pocket-queue-admin/+merge/117630.

== Lint ==

Lots of insane pre-existing doctest cruft that becomes associated with this branch due to the LoC reduction I was doing.  I cleaned a bit of it up, but didn't really want to make this branch even harder to review by going through it all.

./lib/lp/soyuz/stories/distribution/xx-distribution-packages.txt
     226: want exceeds 78 characters.
     229: want exceeds 78 characters.
./lib/lp/soyuz/stories/ppa/xx-private-ppa-presentation.txt
       1: narrative uses a moin header.
       6: narrative uses a moin header.
      16: narrative uses a moin header.
./lib/lp/soyuz/stories/ppa/xx-private-ppa-subscription-stories.txt
       1: narrative uses a moin header.
      10: narrative uses a moin header.
      24: narrative uses a moin header.
      30: narrative uses a moin header.
      88: narrative uses a moin header.
     121: narrative uses a moin header.
     127: narrative uses a moin header.
     160: narrative uses a moin header.
     192: narrative uses a moin header.
     199: narrative uses a moin header.
     242: narrative uses a moin header.
     270: narrative uses a moin header.
     331: narrative uses a moin header.
     337: narrative uses a moin header.
     345: narrative uses a moin header.
./lib/lp/soyuz/stories/ppa/xx-private-ppa-subscriptions.txt
       1: narrative uses a moin header.
       6: narrative uses a moin header.
      57: want exceeds 78 characters.
     107: narrative uses a moin header.
     158: narrative uses a moin header.
     221: narrative uses a moin header.
./lib/lp/soyuz/stories/ppa/xx-ubuntu-ppas.txt
       1: narrative uses a moin header.
      19: narrative uses a moin header.
      72: narrative uses a moin header.
     118: narrative uses a moin header.
     240: narrative uses a moin header.
     254: narrative uses a moin header.
     439: narrative uses a moin header.
     467: narrative uses a moin header.
     520: narrative uses a moin header.
     554: narrative uses a moin header.
     594: want exceeds 78 characters.
     609: narrative uses a moin header.
     634: narrative uses a moin header.
     648: narrative uses a moin header.
     687: narrative uses a moin header.
./lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt
       1: narrative uses a moin header.
      27: source has bad indentation.
      31: source has bad indentation.
      36: source has bad indentation.
      43: source has bad indentation.
      46: want exceeds 78 characters.
      47: want exceeds 78 characters.
      48: want exceeds 78 characters.
      52: source has bad indentation.
      57: source has bad indentation.
      81: source has bad indentation.
      84: source has bad indentation.
      85: want exceeds 78 characters.
      90: source has bad indentation.
     101: source has bad indentation.
     109: source has bad indentation.
     120: source has bad indentation.
     124: source exceeds 78 characters.
     124: source has bad indentation.
     128: source has bad indentation.
     132: source has bad indentation.
     139: source has bad indentation.
     142: source has bad indentation.
     151: source exceeds 78 characters.
     151: source has bad indentation.
     158: source has bad indentation.
     163: source has bad indentation.
     185: source has bad indentation.
     189: source has bad indentation.
     196: source has bad indentation.
     230: source has bad indentation.
     236: source has bad indentation.
     242: source has bad indentation.
     248: source has bad indentation.
     251: source has bad indentation.
     252: want exceeds 78 characters.
     257: source has bad indentation.
     258: source exceeds 78 characters.
     263: source has bad indentation.
     270: source has bad indentation.
     278: source has bad indentation.
     285: source has bad indentation.
     288: source has bad indentation.
     292: narrative uses a moin header.
     309: want exceeds 78 characters.
     320: want exceeds 78 characters.
     322: want exceeds 78 characters.
     324: want exceeds 78 characters.
     358: want exceeds 78 characters.
     376: want exceeds 78 characters.
     398: want exceeds 78 characters.
     400: want exceeds 78 characters.
     402: want exceeds 78 characters.
     405: narrative uses a moin header.
     422: want exceeds 78 characters.
     433: want exceeds 78 characters.
     435: want exceeds 78 characters.
./lib/lp/soyuz/stories/soyuz/xx-private-builds.txt
       1: narrative uses a moin header.
      11: narrative uses a moin header.
     125: narrative uses a moin header.
     191: narrative uses a moin header.
     243: narrative uses a moin header.
     322: want exceeds 78 characters.
     328: want exceeds 78 characters.
./lib/lp/soyuz/stories/soyuz/xx-queue-pages-motu.txt
      43: want exceeds 78 characters.
      44: want exceeds 78 characters.
      45: want exceeds 78 characters.
      46: want exceeds 78 characters.
      47: want exceeds 78 characters.
      48: want exceeds 78 characters.
      49: want exceeds 78 characters.
./lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt
      70: want exceeds 78 characters.
      71: want exceeds 78 characters.
      72: want exceeds 78 characters.
      73: want exceeds 78 characters.
      74: want exceeds 78 characters.
      75: want exceeds 78 characters.
      76: want exceeds 78 characters.
     100: want exceeds 78 characters.
     101: want exceeds 78 characters.
     102: want exceeds 78 characters.
     103: want exceeds 78 characters.
     104: want exceeds 78 characters.
     105: want exceeds 78 characters.
     113: want exceeds 78 characters.
     114: want exceeds 78 characters.
     119: want exceeds 78 characters.
     120: want exceeds 78 characters.
     121: want exceeds 78 characters.
     128: want exceeds 78 characters.
     129: want exceeds 78 characters.
     160: want exceeds 78 characters.
     161: want exceeds 78 characters.
     162: want exceeds 78 characters.
     163: want exceeds 78 characters.
     164: want exceeds 78 characters.
     165: want exceeds 78 characters.
     166: want exceeds 78 characters.
     239: want exceeds 78 characters.
     326: want exceeds 78 characters.
     327: want exceeds 78 characters.
     328: want exceeds 78 characters.
     329: want exceeds 78 characters.
     330: want exceeds 78 characters.
     331: want exceeds 78 characters.
     332: want exceeds 78 characters.
     333: want exceeds 78 characters.
     339: want exceeds 78 characters.
     340: want exceeds 78 characters.
     341: want exceeds 78 characters.
     342: want exceeds 78 characters.
     343: want exceeds 78 characters.
     344: want exceeds 78 characters.
     345: want exceeds 78 characters.
     393: want exceeds 78 characters.
     394: want exceeds 78 characters.
     402: want exceeds 78 characters.
     403: want exceeds 78 characters.
     404: want exceeds 78 characters.
     405: want exceeds 78 characters.
     406: want exceeds 78 characters.
     407: want exceeds 78 characters.
     480: want exceeds 78 characters.
     481: want exceeds 78 characters.
     482: want exceeds 78 characters.
     483: want exceeds 78 characters.
     492: want exceeds 78 characters.
     493: want exceeds 78 characters.
     511: want exceeds 78 characters.
     530: want exceeds 78 characters.
     531: want exceeds 78 characters.
     532: want exceeds 78 characters.
./lib/lp/soyuz/stories/webservice/xx-archive.txt
      43: want exceeds 78 characters.
      47: want exceeds 78 characters.
     173: want exceeds 78 characters.
     190: want exceeds 78 characters.
     207: want exceeds 78 characters.
     224: want exceeds 78 characters.
     369: want exceeds 78 characters.
     430: want exceeds 78 characters.
     561: want exceeds 78 characters.
     623: want exceeds 78 characters.
     702: want exceeds 78 characters.
     727: want exceeds 78 characters.
     729: want exceeds 78 characters.
-- 
https://code.launchpad.net/~cjwatson/launchpad/fix-pocket-queue-admin-series/+merge/118929
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/fix-pocket-queue-admin-series into lp:launchpad.
=== modified file 'lib/lp/soyuz/browser/archive.py'
--- lib/lp/soyuz/browser/archive.py	2012-07-20 10:14:01 +0000
+++ lib/lp/soyuz/browser/archive.py	2012-08-09 11:36:26 +0000
@@ -380,6 +380,8 @@
         if item_type is None or item is None:
             return None
 
+        the_item = None
+        kwargs = {}
         if item_type == 'component':
             # See if "item" is a component name.
             try:
@@ -406,14 +408,21 @@
             # See if "item" is a pocket name.
             try:
                 the_item = PackagePublishingPocket.items[item]
+                # Was a 'series' URL param passed?
+                series = get_url_param('series')
+                if series is not None:
+                    # Get the requested distro series.
+                    try:
+                        series = self.context.distribution[series]
+                        kwargs["distroseries"] = series
+                    except NotFoundError:
+                        pass
             except KeyError:
                 pass
-        else:
-            the_item = None
 
         if the_item is not None:
             result_set = getUtility(IArchivePermissionSet).checkAuthenticated(
-                user, self.context, permission_type, the_item)
+                user, self.context, permission_type, the_item, **kwargs)
             try:
                 return result_set[0]
             except IndexError:

=== modified file 'lib/lp/soyuz/doc/archivepermission.txt'
--- lib/lp/soyuz/doc/archivepermission.txt	2012-06-11 23:57:35 +0000
+++ lib/lp/soyuz/doc/archivepermission.txt	2012-08-09 11:36:26 +0000
@@ -69,7 +69,7 @@
 The checkAuthenticated() call is also able to check someone's
 permission on a SourcePackageName, which gives a smaller radius of
 permission than allowing access to the whole component.  Just pass
-a SourcePacakgeName as the "item" parameter:
+a SourcePackageName as the "item" parameter:
 
     >>> from lp.registry.interfaces.sourcepackagename import (
     ...     ISourcePackageNameSet)
@@ -98,10 +98,9 @@
     >>> [permission.person.name for permission in all_main_permissions]
     [u'ubuntu-team']
 
-    >>> mark_permissions = permission_set.checkAuthenticated(
+    >>> permission_set.checkAuthenticated(
     ...     mark, ubuntu.main_archive, ArchivePermissionType.UPLOAD,
-    ...     main_component)
-    >>> mark_permissions.count()
+    ...     main_component).count()
     1
 
 checkAuthenticated() does not know about any other item types, and
@@ -121,9 +120,8 @@
 permissionsForPerson() returns all the permission records for the supplied
 person:
 
-    >>> permissions = permission_set.permissionsForPerson(
-    ...     ubuntu.main_archive,  ubuntu_team)
-    >>> permissions.count()
+    >>> permission_set.permissionsForPerson(
+    ...     ubuntu.main_archive, ubuntu_team).count()
     7
 
 uploadersForComponent() returns ArchivePermission records where a person
@@ -178,16 +176,14 @@
 uploadersForPackage() returns the ArchivePermission records where a person
 or team has permission to upload to the supplied source package name:
 
-    >>> uploaders = permission_set.uploadersForPackage(
-    ...     ubuntu.main_archive, alsa_utils)
-    >>> uploaders.count()
+    >>> permission_set.uploadersForPackage(
+    ...     ubuntu.main_archive, alsa_utils).count()
     0
 
 You can also pass a string package name instead of an ISourcePackageName:
 
-    >>> uploaders = permission_set.uploadersForPackage(
-    ...     ubuntu.main_archive, "alsa-utils")
-    >>> uploaders.count()
+    >>> permission_set.uploadersForPackage(
+    ...     ubuntu.main_archive, "alsa-utils").count()
     0
 
 Passing a non-existent package name will cause a
@@ -348,3 +344,68 @@
     ubuntu-team
     name12
 
+newPocketQueueAdmin() creates a permission for a person to administer a
+specific pocket in the distroseries queues, which may or may not be
+per-series:
+
+    >>> from lp.registry.interfaces.pocket import PackagePublishingPocket
+
+    >>> def showPocketQueueAdmins(archive, pocket, distroseries=None):
+    ...     archive_admins = permission_set.queueAdminsForPocket(
+    ...         archive, pocket, distroseries=distroseries)
+    ...     for archive_admin in sorted(
+    ...         archive_admins, key=operator.attrgetter("id")):
+    ...         print archive_admin.person.name
+
+    >>> new_permission = permission_set.newPocketQueueAdmin(
+    ...     ubuntu.main_archive, carlos, PackagePublishingPocket.SECURITY)
+    >>> new_permission = permission_set.newPocketQueueAdmin(
+    ...     ubuntu.main_archive, mark, PackagePublishingPocket.PROPOSED,
+    ...     distroseries=ubuntu.series[0])
+    >>> showPocketQueueAdmins(
+    ...     ubuntu.main_archive, PackagePublishingPocket.SECURITY)
+    carlos
+    >>> showPocketQueueAdmins(
+    ...     ubuntu.main_archive, PackagePublishingPocket.PROPOSED)
+    mark
+    >>> showPocketQueueAdmins(
+    ...     ubuntu.main_archive, PackagePublishingPocket.PROPOSED,
+    ...     distroseries=ubuntu.series[0])
+    mark
+    >>> showPocketQueueAdmins(
+    ...     ubuntu.main_archive, PackagePublishingPocket.PROPOSED,
+    ...     distroseries=ubuntu.series[1])
+
+checkAuthenticated returns sensible results for these permissions:
+
+    >>> def countPocketAdminPermissions(person, pocket, distroseries=None):
+    ...     return permission_set.checkAuthenticated(
+    ...         person, ubuntu.main_archive,
+    ...         ArchivePermissionType.QUEUE_ADMIN, pocket,
+    ...         distroseries=distroseries).count()
+
+    >>> countPocketAdminPermissions(carlos, PackagePublishingPocket.SECURITY)
+    1
+    >>> countPocketAdminPermissions(mark, PackagePublishingPocket.PROPOSED)
+    1
+    >>> countPocketAdminPermissions(
+    ...     mark, PackagePublishingPocket.PROPOSED, ubuntu.series[0])
+    1
+    >>> countPocketAdminPermissions(
+    ...     mark, PackagePublishingPocket.PROPOSED, ubuntu.series[1])
+    0
+    >>> countPocketAdminPermissions(
+    ...     mark, PackagePublishingPocket.SECURITY, ubuntu.series[0])
+    0
+
+deletePocketQueueAdmin removes them:
+
+    >>> permission_set.deletePocketQueueAdmin(
+    ...     ubuntu.main_archive, carlos, PackagePublishingPocket.SECURITY)
+    >>> permission_set.deletePocketQueueAdmin(
+    ...     ubuntu.main_archive, mark, PackagePublishingPocket.PROPOSED,
+    ...     distroseries=ubuntu.series[0])
+    >>> showPocketQueueAdmins(
+    ...     ubuntu.main_archive, PackagePublishingPocket.SECURITY)
+    >>> showPocketQueueAdmins(
+    ...     ubuntu.main_archive, PackagePublishingPocket.PROPOSED)

=== modified file 'lib/lp/soyuz/model/archivepermission.py'
--- lib/lp/soyuz/model/archivepermission.py	2012-08-01 04:18:50 +0000
+++ lib/lp/soyuz/model/archivepermission.py	2012-08-09 11:36:26 +0000
@@ -192,8 +192,8 @@
             clauses.append("ArchivePermission.pocket = %s" % sqlvalues(item))
             if distroseries is not None:
                 clauses.append(
-                    "ArchivePermission.distroseries IS NULL OR "
-                    "ArchivePermission.distroseries = %s" %
+                    "(ArchivePermission.distroseries IS NULL OR "
+                     "ArchivePermission.distroseries = %s)" %
                     sqlvalues(distroseries))
                 prejoins.append("distroseries")
         else:

=== modified file 'lib/lp/soyuz/stories/distribution/xx-distribution-packages.txt'
--- lib/lp/soyuz/stories/distribution/xx-distribution-packages.txt	2012-06-14 10:34:55 +0000
+++ lib/lp/soyuz/stories/distribution/xx-distribution-packages.txt	2012-08-09 11:36:26 +0000
@@ -380,8 +380,7 @@
 A link to further PPA searches is also included.
 
     >>> link = browser.getLink(
-    ...     url='http://launchpad.dev/ubuntutest/+ppas?'
-    ...         'name_filter=netapplet')
+    ...     url='http://launchpad.dev/ubuntutest/+ppas?name_filter=netapplet')
     >>> link.text
     "...other untrusted versions of..."
 
@@ -389,10 +388,10 @@
 Source package change logs
 --------------------------
 
-/$DISTRO/+source/$PACKAGE/+changelog pages contain a version history that lists
-each published version of a package with its changelog entry for that version.
-To navigate to this page, click on the "View full change log" link from
-the index page.
+/$DISTRO/+source/$PACKAGE/+changelog pages contain a version history that
+lists each published version of a package with its changelog entry for that
+version.  To navigate to this page, click on the "View full change log" link
+from the index page.
 
     >>> browser.open("http://launchpad.dev/ubuntu/+source/foobar/";)
     >>> browser.getLink("View full change log").click()
@@ -497,8 +496,7 @@
 If the package being viewed has no publishing history, a blank table is
 displayed:
 
-    >>> user_browser.open(
-    ...     "http://launchpad.dev/ubuntu/+source/a52dec/";)
+    >>> user_browser.open("http://launchpad.dev/ubuntu/+source/a52dec/";)
     >>> print extract_text(find_tag_by_id(
     ...     user_browser.contents, 'packages_list'))
 
@@ -538,10 +536,7 @@
 
     >>> from zope.component import getUtility
     >>> from lp.services.database.sqlbase import flush_database_updates
-    >>> from lp.testing import login, logout
     >>> from lp.registry.interfaces.distribution import IDistributionSet
-    >>> from lp.soyuz.tests.test_publishing import (
-    ...      SoyuzTestPublisher)
 
     >>> login("foo.bar@xxxxxxxxxxxxx")
 

=== modified file 'lib/lp/soyuz/stories/ppa/xx-copy-packages.txt'
--- lib/lp/soyuz/stories/ppa/xx-copy-packages.txt	2012-08-01 11:02:13 +0000
+++ lib/lp/soyuz/stories/ppa/xx-copy-packages.txt	2012-08-09 11:36:26 +0000
@@ -108,12 +108,9 @@
 
     >>> from zope.component import getUtility
     >>> from lp.registry.interfaces.person import IPersonSet
-    >>> from lp.testing import login, logout
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
-    >>> from lp.services.librarian.interfaces import (
-    ...     ILibraryFileAliasSet,
-    ...     )
+    >>> from lp.services.librarian.interfaces import ILibraryFileAliasSet
     >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> from lp.soyuz.model.processor import ProcessorFamily
 
@@ -145,8 +142,7 @@
 He is a little confused by the number of packages presented by
 default and wants to refine the options.
 
-    >>> jblack_browser.getControl(
-    ...     name='field.name_filter').value = 'pmount'
+    >>> jblack_browser.getControl(name='field.name_filter').value = 'pmount'
     >>> jblack_browser.getControl("Filter").click()
 
 There we go, James can be certain about which package to select, only
@@ -206,28 +202,23 @@
 Currently, James only has access to his just created PPA, which is the
 default form value for 'Destination PPA'.
 
-    >>> print jblack_browser.getControl(
-    ...     'Destination PPA').displayOptions
+    >>> print jblack_browser.getControl('Destination PPA').displayOptions
     ['PPA for James Blackwell']
 
-    >>> print jblack_browser.getControl(
-    ...     'Destination PPA').value
+    >>> print jblack_browser.getControl('Destination PPA').value
     ['jblack/ppa']
 
 James notice that Celso's 'pmount' was uploaded and built in Warty,
 but he is using Hoary. No problem, because he can select a destination
 series while copying.
 
-    >>> print jblack_browser.getControl(
-    ...     'Destination series').displayOptions
+    >>> print jblack_browser.getControl('Destination series').displayOptions
     ['The same series', 'Breezy Badger Autotest', 'Grumpy', 'Hoary', 'Warty']
 
-    >>> print jblack_browser.getControl(
-    ...     'Destination series').value
+    >>> print jblack_browser.getControl('Destination series').value
     ['']
 
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['hoary']
+    >>> jblack_browser.getControl('Destination series').value = ['hoary']
 
 James may want to copy binaries over, or to do a full rebuild from
 source, which is the default option.
@@ -359,14 +350,12 @@
 
 Leave the Destination PPA alone, because it defaults to 'This PPA'.
 
-    >>> print jblack_browser.getControl(
-    ...     'Destination PPA').displayValue
+    >>> print jblack_browser.getControl('Destination PPA').displayValue
     ['This PPA']
 
 The destination series always default to 'The same series'.
 
-    >>> jblack_browser.getControl(
-    ...     'Destination series').displayValue
+    >>> jblack_browser.getControl('Destination series').displayValue
     ['The same series']
 
 He uses the default option of rebuilding copied source along the way.
@@ -396,9 +385,8 @@
 binaries go together, James executes the copy including the binaries.
 
     >>> jblack_browser.getControl(
-    ...    name='field.selected_sources').value = [pmount_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['grumpy']
+    ...     name='field.selected_sources').value = [pmount_pub_id]
+    >>> jblack_browser.getControl('Destination series').value = ['grumpy']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -418,8 +406,7 @@
 wait until the next publishing cycle to be published in the archive.
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
-    >>> from lp.soyuz.tests.test_publishing import (
-    ...     SoyuzTestPublisher)
+    >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
     >>> test_publisher = SoyuzTestPublisher()
     >>> jblack = person_set.getByName('jblack')
     >>> pmount_build = jblack.archive.getBuildRecords()[0]
@@ -436,9 +423,8 @@
 binaries could not be published in the PPA.
 
     >>> jblack_browser.getControl(
-    ...    name='field.selected_sources').value = [pmount_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['grumpy']
+    ...     name='field.selected_sources').value = [pmount_pub_id]
+    >>> jblack_browser.getControl('Destination series').value = ['grumpy']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['REBUILD_SOURCES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -458,8 +444,7 @@
 
     >>> jblack_browser.getControl(
     ...    name='field.selected_sources').value = [pmount_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['grumpy']
+    >>> jblack_browser.getControl('Destination series').value = ['grumpy']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -494,8 +479,7 @@
     >>> logout()
     >>> jblack_browser.getControl(
     ...    name='field.selected_sources').value = [pmount_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['grumpy']
+    >>> jblack_browser.getControl('Destination series').value = ['grumpy']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -539,9 +523,8 @@
 selected destination PPA will be rendered.
 
     >>> jblack_browser.getControl(
-    ...    name='field.selected_sources').value = [pmount_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['grumpy']
+    ...     name='field.selected_sources').value = [pmount_pub_id]
+    >>> jblack_browser.getControl('Destination series').value = ['grumpy']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -561,9 +544,9 @@
     >>> jblack_browser.getLink('Cancel').click()
     >>> jblack_browser.getLink('Delete packages').click()
     >>> jblack_browser.getControl(
-    ...    name='field.selected_sources').value = [pmount_pub_id]
+    ...     name='field.selected_sources').value = [pmount_pub_id]
     >>> jblack_browser.getControl(
-    ...    "Deletion comment").value = "Deleted packages can be copied."
+    ...     "Deletion comment").value = "Deleted packages can be copied."
     >>> jblack_browser.getControl("Request Deletion").click()
 
 James return to his PPA packages page and checks that the package is
@@ -613,9 +596,8 @@
 published in the archive.
 
     >>> jblack_browser.getControl(
-    ...    name='field.selected_sources').value = [pmount_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['warty']
+    ...     name='field.selected_sources').value = [pmount_pub_id]
+    >>> jblack_browser.getControl('Destination series').value = ['warty']
     >>> jblack_browser.getControl("Copy Packages").click()
     >>> messages = get_feedback_messages(jblack_browser.contents)
     >>> for msg in messages:
@@ -632,9 +614,8 @@
 allowed if they are the same.
 
     >>> jblack_browser.getControl(
-    ...    name='field.selected_sources').value = [pmount_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['warty']
+    ...     name='field.selected_sources').value = [pmount_pub_id]
+    >>> jblack_browser.getControl('Destination series').value = ['warty']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -668,8 +649,7 @@
     >>> jblack_browser.open('http://launchpad.dev/people')
     >>> jblack_browser.getLink('Register a team').click()
 
-    >>> jblack_browser.getControl(
-    ...     name="field.name").value = 'jblack-friends'
+    >>> jblack_browser.getControl(name="field.name").value = 'jblack-friends'
 
     >>> jblack_browser.getControl(
     ...     'Display Name').value = 'James Blackwell Friends'
@@ -707,8 +687,7 @@
 Now that James have access to more than one PPA, the copy-packages form
 allows him to select one of them.
 
-    >>> print jblack_browser.getControl(
-    ...     'Destination PPA').displayOptions
+    >>> print jblack_browser.getControl('Destination PPA').displayOptions
     ['PPA for James Blackwell', 'PPA for James Blackwell Friends']
 
 James wants to populate the PPA for James Blackwell Friends, he
@@ -719,8 +698,7 @@
 
 James decides that 'hoary' is where the action will be for his friends.
 
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['hoary']
+    >>> jblack_browser.getControl('Destination series').value = ['hoary']
 
 Also, in order to make James Friends' PPA ready to use, this time
 James will also copy Celso's binaries for the selected sources.
@@ -795,8 +773,7 @@
     ...    name='field.selected_sources').value = [
     ...    pmount_pub_id, iceweasel_pub_id]
 
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['hoary']
+    >>> jblack_browser.getControl('Destination series').value = ['hoary']
 
     >>> jblack_browser.getControl("Copy Packages").click()
     >>> messages = get_feedback_messages(jblack_browser.contents)
@@ -815,8 +792,7 @@
     >>> jblack_browser.open('http://launchpad.dev/people')
     >>> jblack_browser.getLink('Register a team').click()
 
-    >>> jblack_browser.getControl(
-    ...     name="field.name").value = 'jblack-sandbox'
+    >>> jblack_browser.getControl(name="field.name").value = 'jblack-sandbox'
 
     >>> jblack_browser.getControl(
     ...     'Display Name').value = 'James Blackwell Sandbox'
@@ -824,8 +800,7 @@
     >>> jblack_browser.getControl("Create").click()
 
     >>> jblack_browser.getLink('Create a new PPA').click()
-    >>> jblack_browser.getControl(
-    ...     name="field.displayname").value = (
+    >>> jblack_browser.getControl(name="field.displayname").value = (
     ...     'PPA for James Blackwell Sandbox')
     >>> jblack_browser.getControl(name="field.accepted").value = True
     >>> jblack_browser.getControl(
@@ -860,8 +835,7 @@
     >>> jblack_browser.getControl(
     ...     'Destination PPA').value = ['jblack-sandbox/ppa']
 
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['']
+    >>> jblack_browser.getControl('Destination series').value = ['']
 
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['REBUILD_SOURCES']
@@ -909,12 +883,10 @@
     >>> jblack_browser.getControl(name='field.selected_sources').value = (
     ...     [deleted_pub_id])
 
-    >>> print jblack_browser.getControl(
-    ...     'Destination PPA').displayValue
+    >>> print jblack_browser.getControl('Destination PPA').displayValue
     ['This PPA']
 
-    >>> print jblack_browser.getControl(
-    ...     'Destination series').displayValue
+    >>> print jblack_browser.getControl('Destination series').displayValue
     ['The same series']
 
     >>> jblack_browser.getControl(
@@ -946,7 +918,6 @@
 in No Privileges' PPA.
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
-    >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
     >>> hoary = ubuntu.getSeries('hoary')
 
@@ -1004,8 +975,7 @@
     >>> foo_pub_id = getPPAPubIDsFor('no-priv', 'foo')[0]
     >>> jblack_browser.getControl(
     ...     name='field.selected_sources').value = [foo_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination PPA').value = ['jblack/ppa']
+    >>> jblack_browser.getControl('Destination PPA').value = ['jblack/ppa']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -1036,8 +1006,7 @@
     >>> foo_pub_id = getPPAPubIDsFor('cprov', 'foo')[0]
     >>> jblack_browser.getControl(
     ...     name='field.selected_sources').value = [foo_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination PPA').value = ['jblack/ppa']
+    >>> jblack_browser.getControl('Destination PPA').value = ['jblack/ppa']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -1054,10 +1023,8 @@
 
     >>> jblack_browser.getControl(
     ...     name='field.selected_sources').value = [foo_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination PPA').value = ['jblack/ppa']
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['warty']
+    >>> jblack_browser.getControl('Destination PPA').value = ['jblack/ppa']
+    >>> jblack_browser.getControl('Destination series').value = ['warty']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -1097,10 +1064,8 @@
     >>> foo_pub_id = getPPAPubIDsFor('mark', 'foo')[0]
     >>> jblack_browser.getControl(
     ...     name='field.selected_sources').value = [foo_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination PPA').value = ['jblack/ppa']
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['grumpy']
+    >>> jblack_browser.getControl('Destination PPA').value = ['jblack/ppa']
+    >>> jblack_browser.getControl('Destination series').value = ['grumpy']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()
@@ -1134,10 +1099,8 @@
     >>> foo_pub_id = getPPAPubIDsFor('jblack-friends', 'foo')[0]
     >>> jblack_browser.getControl(
     ...     name='field.selected_sources').value = [foo_pub_id]
-    >>> jblack_browser.getControl(
-    ...     'Destination PPA').value = ['jblack/ppa']
-    >>> jblack_browser.getControl(
-    ...     'Destination series').value = ['grumpy']
+    >>> jblack_browser.getControl('Destination PPA').value = ['jblack/ppa']
+    >>> jblack_browser.getControl('Destination series').value = ['grumpy']
     >>> jblack_browser.getControl(
     ...     name='field.include_binaries').value = ['COPY_BINARIES']
     >>> jblack_browser.getControl("Copy Packages").click()

=== modified file 'lib/lp/soyuz/stories/ppa/xx-delete-packages.txt'
--- lib/lp/soyuz/stories/ppa/xx-delete-packages.txt	2012-02-10 10:14:20 +0000
+++ lib/lp/soyuz/stories/ppa/xx-delete-packages.txt	2012-08-09 11:36:26 +0000
@@ -104,10 +104,8 @@
     >>> admin_browser.getControl('Deletion comment').value
     ''
 
-    >>> admin_browser.getControl(
-    ...     name='field.name_filter').value = ''
-    >>> admin_browser.getControl(
-    ...    name='field.selected_sources').value = ['31']
+    >>> admin_browser.getControl(name='field.name_filter').value = ''
+    >>> admin_browser.getControl(name='field.selected_sources').value = ['31']
     >>> admin_browser.getControl("Request Deletion").click()
     >>> messages = get_feedback_messages(admin_browser.contents)
     >>> for msg in messages:
@@ -147,8 +145,7 @@
 entirely readable content.
 
     >>> admin_browser.getControl("Filter").click()
-    >>> admin_browser.getControl(
-    ...    name='field.selected_sources').value = ['27']
+    >>> admin_browser.getControl(name='field.selected_sources').value = ['27']
     >>> admin_browser.getControl(
     ...     "Deletion comment").value = "DO <where is my XSS ?> IT"
     >>> admin_browser.getControl("Request Deletion").click()
@@ -170,8 +167,7 @@
 
 An already deleted source:
 
-    >>> admin_browser.getControl(
-    ...    name='field.selected_sources').value = ['27']
+    >>> admin_browser.getControl(name='field.selected_sources').value = ['27']
     Traceback (most recent call last):
     ...
     ItemNotFoundError: insufficient items with name '27'
@@ -236,8 +232,7 @@
 
     >>> admin_browser.getControl(
     ...    name='field.selected_sources').value = ['28', '29']
-    >>> admin_browser.getControl(
-    ...    "Deletion comment").value = "DO IT AGAIN !"
+    >>> admin_browser.getControl("Deletion comment").value = "DO IT AGAIN !"
     >>> admin_browser.getControl("Request Deletion").click()
 
     >>> messages = get_feedback_messages(admin_browser.contents)
@@ -262,8 +257,7 @@
 
     >>> re_post_browser.getControl(
     ...    name='field.selected_sources').value = ['28', '29']
-    >>> re_post_browser.getControl(
-    ...    "Deletion comment").value = "DO IT AGAIN !"
+    >>> re_post_browser.getControl("Deletion comment").value = "DO IT AGAIN !"
     >>> re_post_browser.getControl("Request Deletion").click()
 
     >>> print extract_text(find_main_content(re_post_browser.contents))
@@ -326,13 +320,11 @@
     >>> from zope.component import getUtility
 
     >>> from lp.services.database.constants import UTC_NOW
-    >>> from lp.testing import login, logout
     >>> from lp.services.librarian.interfaces import ILibraryFileAliasSet
     >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> from lp.registry.interfaces.person import IPersonSet
     >>> from lp.soyuz.enums import PackagePublishingStatus
-    >>> from lp.soyuz.tests.test_publishing import (
-    ...     SoyuzTestPublisher)
+    >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
 
@@ -454,8 +446,7 @@
     ...     "divisible by three will lead to a frameshift mutation.")
     >>> user_browser.getControl(
     ...    name='field.selected_sources').value = [str(foo_pub_src.id)]
-    >>> user_browser.getControl(
-    ...    "Deletion comment").value = deletion_comment
+    >>> user_browser.getControl("Deletion comment").value = deletion_comment
     >>> user_browser.getControl("Request Deletion").click()
 
     >>> messages = get_feedback_messages(user_browser.contents)

=== modified file 'lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt'
--- lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt	2012-01-15 11:06:57 +0000
+++ lib/lp/soyuz/stories/ppa/xx-ppa-packages.txt	2012-08-09 11:36:26 +0000
@@ -271,8 +271,6 @@
 states. Note, for consistency we have to create the binary publishing records
 for iceweasel before marking it as superseded.
 
-    >>> from zope.component import getUtility
-    >>> from lp.registry.interfaces.person import IPersonSet
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> cprov = getUtility(IPersonSet).getByName('cprov')
     >>> iceweasel_pub = cprov.archive.getPublishedSources(
@@ -305,7 +303,8 @@
 
 Use can explicitly select 'published' filter and will get the same result.
 
-    >>> anon_browser.getControl(name='field.status_filter').value = ['published']
+    >>> anon_browser.getControl(
+    ...     name='field.status_filter').value = ['published']
     >>> anon_browser.getControl('Filter', index=0).click()
     >>> print_archive_package_rows(anon_browser.contents)
     Source          Published   Status     Series           Section  Build
@@ -315,7 +314,8 @@
 When needed the users can select the 'superseded' filter and the
 result will only contain packages SUPERSEDED or DELETED.
 
-    >>> anon_browser.getControl(name='field.status_filter').value = ['superseded']
+    >>> anon_browser.getControl(
+    ...     name='field.status_filter').value = ['superseded']
     >>> anon_browser.getControl('Filter', index=0).click()
     >>> print_archive_package_rows(anon_browser.contents)
     Source            Published    Status        Series   Section  Build

=== modified file 'lib/lp/soyuz/stories/ppa/xx-private-ppa-presentation.txt'
--- lib/lp/soyuz/stories/ppa/xx-private-ppa-presentation.txt	2012-01-15 11:06:57 +0000
+++ lib/lp/soyuz/stories/ppa/xx-private-ppa-presentation.txt	2012-08-09 11:36:26 +0000
@@ -8,7 +8,7 @@
 Public PPAs appear like any other launchpad page.
 
     >>> browser.open("http://launchpad.dev/~cprov/+archive";)
-    >>> from BeautifulSoup import BeautifulSoup, SoupStrainer
+    >>> from BeautifulSoup import BeautifulSoup
     >>> body_el = BeautifulSoup(browser.contents).first('body')
     >>> 'private' in body_el['class']
     False
@@ -33,8 +33,6 @@
     >>> cprov_browser = setupBrowser(
     ...     auth='Basic celso.providelo@xxxxxxxxxxxxx:test')
     >>> cprov_browser.open("http://launchpad.dev/~cprov/+archive/p3a";)
-    >>> from BeautifulSoup import BeautifulSoup, SoupStrainer
     >>> body_el = BeautifulSoup(cprov_browser.contents).first('body')
     >>> 'private' in body_el['class']
     True
-

=== modified file 'lib/lp/soyuz/stories/ppa/xx-private-ppa-subscription-stories.txt'
--- lib/lp/soyuz/stories/ppa/xx-private-ppa-subscription-stories.txt	2012-01-27 14:25:50 +0000
+++ lib/lp/soyuz/stories/ppa/xx-private-ppa-subscription-stories.txt	2012-08-09 11:36:26 +0000
@@ -142,7 +142,8 @@
     ...     "a different description")
     >>> cprov_browser.getControl(name="field.actions.update").click()
 
-then the browser is redirected back to the subscriptions page, the updated subscription for the launchpad team is displayed as well as a notification
+then the browser is redirected back to the subscriptions page, the updated
+subscription for the launchpad team is displayed as well as a notification
 about the update.
 
     >>> print cprov_browser.url
@@ -272,8 +273,6 @@
 a user Mark who is a member of the Launchpad team,
 
     >>> login('celso.providelo@xxxxxxxxxxxxx')
-    >>> from zope.component import getUtility
-    >>> from lp.registry.interfaces.person import IPersonSet
     >>> cprov = getUtility(IPersonSet).getByName('cprov')
     >>> launchpad = getUtility(IPersonSet).getByName('launchpad')
     >>> ignore = private_ppa.newSubscription(launchpad, cprov)
@@ -350,5 +349,3 @@
 When Andrew visits his subscriptions
 Then the page clearly identifies the subscription as no longer valid
 And there is no entry in the sources.list for the expired subscription.
-
-

=== modified file 'lib/lp/soyuz/stories/ppa/xx-private-ppa-subscriptions.txt'
--- lib/lp/soyuz/stories/ppa/xx-private-ppa-subscriptions.txt	2012-03-19 16:33:41 +0000
+++ lib/lp/soyuz/stories/ppa/xx-private-ppa-subscriptions.txt	2012-08-09 11:36:26 +0000
@@ -124,9 +124,9 @@
 First, create a subscription for Joe Smith's team to mark's archive
 so that Joe has multiple subscriptions:
 
-    >>> mark_browser = setupBrowser(
-    ...     auth="Basic mark@xxxxxxxxxxx:test")
-    >>> mark_browser.open("http://launchpad.dev/~mark/+archive/p3a/+subscriptions";)
+    >>> mark_browser = setupBrowser(auth="Basic mark@xxxxxxxxxxx:test")
+    >>> mark_browser.open(
+    ...     "http://launchpad.dev/~mark/+archive/p3a/+subscriptions";)
     >>> mark_browser.getControl(name='field.subscriber').value = 'joesmith'
     >>> mark_browser.getControl(
     ...     name='field.description').value = "Joe is also my friend"
@@ -178,9 +178,7 @@
 This page will include information about the signing key, if the archive
 has a signing key:
 
-    >>> from zope.component import getUtility
     >>> from lp.registry.interfaces.gpg import IGPGKeySet
-    >>> from lp.registry.interfaces.person import IPersonSet
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> mark = getUtility(IPersonSet).getByName('mark')
     >>> a_key = getUtility(IGPGKeySet).get(1)
@@ -227,4 +225,3 @@
     Traceback (most recent call last):
     ...
     NotFound...
-

=== modified file 'lib/lp/soyuz/stories/ppa/xx-ubuntu-ppas.txt'
--- lib/lp/soyuz/stories/ppa/xx-ubuntu-ppas.txt	2011-12-30 06:14:56 +0000
+++ lib/lp/soyuz/stories/ppa/xx-ubuntu-ppas.txt	2012-08-09 11:36:26 +0000
@@ -366,7 +366,6 @@
     >>> test_publisher.prepareBreezyAutotest()
     >>> new_version = test_publisher.getPubSource(
     ...     distroseries=warty, version="1.1", sourcename='iceweasel')
-    >>> import transaction
     >>> transaction.commit()
     >>> logout()
 
@@ -498,7 +497,6 @@
 Users will only be able to see it for PPAs that have, at least, one
 published source.
 
-    >>> from lp.soyuz.tests.ppa import publishToPPA
     >>> login(ANONYMOUS)
     >>> publishToPPA("no-priv", "warty", "commercialpackage", "1.0-1")
     >>> logout()
@@ -568,7 +566,6 @@
 for more information).
 
     >>> from lp.registry.interfaces.gpg import IGPGKeySet
-    >>> from lp.registry.interfaces.person import IPersonSet
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> no_priv = getUtility(IPersonSet).getByName('no-priv')
@@ -626,7 +623,8 @@
 
 And the sources.list entries point to the right distribution release:
 
-    >>> results = find_tag_by_id(anon_browser.contents, 'sources-list-entries')
+    >>> results = find_tag_by_id(
+    ...     anon_browser.contents, 'sources-list-entries')
     >>> text = extract_text(results)
     >>> print text
     deb http://ppa.launchpad.dev/mark/ppa/ubuntu breezy-autotest main
@@ -653,7 +651,6 @@
 the 'Most active' section we will create some of them on-the-fly so we
 can check how it looks.
 
-    >>> from lp.soyuz.tests.ppa import publishToTeamPPA
     >>> login(ANONYMOUS)
     >>> publishToPPA("cprov", "warty", "commercialpackage", "1.0-1")
     >>> publishToPPA("cprov", "hoary", "commercialpackage", "1.0-1")

=== modified file 'lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt'
--- lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt	2011-05-27 19:53:20 +0000
+++ lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt	2012-08-09 11:36:26 +0000
@@ -24,8 +24,7 @@
 
 Starting from distribution page:
 
-  >>> browser.open(
-  ...     'http://launchpad.dev/ubuntu')
+  >>> browser.open('http://launchpad.dev/ubuntu')
 
 Search for mozilla in the packages:
 
@@ -42,7 +41,8 @@
 about Firefox's publishing history.
 
   >>> browser.getLink("View full publishing history").click()
-  >>> print extract_text(find_tag_by_id(browser.contents, 'publishing-summary'))
+  >>> print extract_text(
+  ...     find_tag_by_id(browser.contents, 'publishing-summary'))
   Date                                              Status     Target   Pocket   Component   Section   Version
   2006-02-13 12:19:00 UTC                           Published  Warty    release  main        web       0.9
   Published on 2006-02-13 2004-09-27 11:57:13 UTC   Pending    Warty   release   main        editors   0.9
@@ -198,12 +198,10 @@
 
 Any user can see the copyright for the most recent source package release.
 
-    >>> # Add copyright info for the object under test.
     >>> import transaction
     >>> from zope.component import getUtility
     >>> from zope.security.proxy import removeSecurityProxy
     >>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
-    >>> from lp.testing import login, logout
 
     >>> login('admin@xxxxxxxxxxxxx')
     >>> ubuntu = getUtility(ILaunchpadCelebrities).ubuntu

=== modified file 'lib/lp/soyuz/stories/soyuz/xx-person-packages.txt'
--- lib/lp/soyuz/stories/soyuz/xx-person-packages.txt	2012-06-11 00:47:38 +0000
+++ lib/lp/soyuz/stories/soyuz/xx-person-packages.txt	2012-08-09 11:36:26 +0000
@@ -166,14 +166,11 @@
 so that they appear in his +packages page.
 
     >>> from zope.component import getUtility
-    >>> from lp.testing import login, logout
     >>> from lp.services.database.sqlbase import flush_database_updates
     >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> from lp.registry.interfaces.person import IPersonSet
-    >>> from lp.soyuz.tests.test_publishing import (
-    ...      SoyuzTestPublisher)
-    >>> from lp.soyuz.enums import (
-    ...     PackagePublishingStatus)
+    >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
+    >>> from lp.soyuz.enums import PackagePublishingStatus
 
     >>> login("foo.bar@xxxxxxxxxxxxx")
     >>> cprov = getUtility(IPersonSet).getByName('cprov')
@@ -296,7 +293,6 @@
 Now we'll publish it in the primary archive.
 
     >>> login("foo.bar@xxxxxxxxxxxxx")
-    >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> from lp.soyuz.enums import ArchivePurpose
     >>> from lp.soyuz.interfaces.archive import IArchiveSet
     >>> ubuntu = getUtility(IDistributionSet)['ubuntu']

=== modified file 'lib/lp/soyuz/stories/soyuz/xx-private-builds.txt'
--- lib/lp/soyuz/stories/soyuz/xx-private-builds.txt	2012-03-07 05:16:26 +0000
+++ lib/lp/soyuz/stories/soyuz/xx-private-builds.txt	2012-08-09 11:36:26 +0000
@@ -21,25 +21,20 @@
     >>> from lp.buildmaster.interfaces.builder import IBuilderSet
     >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> from lp.registry.interfaces.person import IPersonSet
-    >>> from lp.soyuz.tests.test_publishing import (
-    ...      SoyuzTestPublisher)
-    >>> from lp.soyuz.enums import (
-    ...     PackagePublishingStatus)
-    >>> from lp.testing import login, logout
+    >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
+    >>> from lp.soyuz.enums import PackagePublishingStatus
     >>> from lp.services.database.sqlbase import flush_database_updates
 
     >>> login("foo.bar@xxxxxxxxxxxxx")
     >>> cprov = getUtility(IPersonSet).getByName("cprov")
     >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
     >>> cprov_private_ppa = factory.makeArchive(
-    ...     private=True, owner=cprov, name="p3a",
-    ...     distribution=ubuntu)
+    ...     private=True, owner=cprov, name="p3a", distribution=ubuntu)
     >>> test_publisher = SoyuzTestPublisher()
     >>> test_publisher.prepareBreezyAutotest()
     >>> private_source_pub = test_publisher.getPubSource(
     ...     status=PackagePublishingStatus.PUBLISHED,
-    ...     sourcename='privacy-test',
-    ...     archive=cprov_private_ppa)
+    ...     sourcename='privacy-test', archive=cprov_private_ppa)
     >>> [private_build] = private_source_pub.createMissingBuilds()
     >>> frog = getUtility(IBuilderSet)['frog']
     >>> frog.builderok = True
@@ -53,8 +48,7 @@
     >>> name12 = getUtility(IPersonSet).getByName("name12")
     >>> buildd_admins = getUtility(
     ...     IPersonSet).getByName("launchpad-buildd-admins")
-    >>> from lp.registry.interfaces.person import (
-    ...     TeamMembershipStatus)
+    >>> from lp.registry.interfaces.person import TeamMembershipStatus
     >>> ignored = buildd_admins.addMember(
     ...     name12, reviewer=name12, status=TeamMembershipStatus.APPROVED)
 
@@ -283,12 +277,11 @@
     >>> from lp.services.webapp.dbpolicy import MasterDatabasePolicy
     >>> from lp.services.webapp.interfaces import IStoreSelector
     >>> getUtility(IStoreSelector).push(MasterDatabasePolicy())
-    >>> from lp.registry.interfaces.distribution import (
-    ...     IDistributionSet)
     >>> ubuntutest = getUtility(IDistributionSet)['ubuntutest']
     >>> breezy_autotest = ubuntutest['breezy-autotest']
     >>> new_pub = private_source_pub.copyTo(
-    ...     breezy_autotest, private_source_pub.pocket, ubuntutest.main_archive)
+    ...     breezy_autotest, private_source_pub.pocket,
+    ...     ubuntutest.main_archive)
     >>> binary_pkg_release = test_publisher.uploadBinaryForBuild(
     ...     private_build, 'privacy-test-bin')
     >>> binary_pkg_pub_history = test_publisher.publishBinaryInArchive(

=== modified file 'lib/lp/soyuz/stories/soyuz/xx-queue-pages-motu.txt'
--- lib/lp/soyuz/stories/soyuz/xx-queue-pages-motu.txt	2012-06-28 12:43:10 +0000
+++ lib/lp/soyuz/stories/soyuz/xx-queue-pages-motu.txt	2012-08-09 11:36:26 +0000
@@ -70,7 +70,6 @@
 permission to manipulate them.
 
     >>> from zope.component import getUtility
-    >>> from lp.testing import login, logout
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> from lp.registry.interfaces.sourcepackagename import (
     ...     ISourcePackageNameSet)
@@ -82,15 +81,13 @@
     >>> universe = getUtility(IComponentSet)['universe']
     >>> alsa_utils = getUtility(
     ...     ISourcePackageNameSet).queryByName("alsa-utils")
-    >>> pmount = getUtility(
-    ...     IBinaryPackageNameSet).queryByName("pmount")
+    >>> pmount = getUtility(IBinaryPackageNameSet).queryByName("pmount")
     >>> mozilla = getUtility(
     ...     IBinaryPackageNameSet).queryByName("mozilla-firefox")
     >>> for source in SourcePackageRelease.selectBy(
     ...     sourcepackagename=alsa_utils):
     ...     source.component = universe
-    >>> for binary in BinaryPackageRelease.selectBy(
-    ...     binarypackagename=pmount):
+    >>> for binary in BinaryPackageRelease.selectBy(binarypackagename=pmount):
     ...     binary.component = universe
     >>> for binary in BinaryPackageRelease.selectBy(
     ...     binarypackagename=mozilla):
@@ -158,5 +155,3 @@
     ...     print message
     FAILED: netapplet (You have no rights to accept component(s) 'main')
     OK: mozilla-firefox(multiverse/(unchanged)/(unchanged))
-
-

=== modified file 'lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt'
--- lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt	2012-06-28 12:43:10 +0000
+++ lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt	2012-08-09 11:36:26 +0000
@@ -17,7 +17,6 @@
 permissioning data has no web UI to administer it yet, so we'll create
 a Zope interaction to show it:
 
-    >>> from lp.testing import login, logout
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> from zope.component import getUtility
     >>> from lp.registry.interfaces.distribution import IDistributionSet
@@ -140,10 +139,8 @@
     >>> pss = getUtility(IPackagesetSet)
     >>> desktop = pss.new(
     ...     u'desktop', u'Ubuntu Desktop', name12, breezy_autotest)
-    >>> server = pss.new(
-    ...     u'server', u'Ubuntu Server', name12, breezy_autotest)
-    >>> core = pss.new(
-    ...     u'core', u'Ubuntu Core', name12, breezy_autotest)
+    >>> server = pss.new(u'server', u'Ubuntu Server', name12, breezy_autotest)
+    >>> core = pss.new(u'core', u'Ubuntu Core', name12, breezy_autotest)
     >>> desktop.add([core])
     >>> desktop.addSources(['alsa-utils'])
     >>> server.addSources(['alsa-utils'])
@@ -218,8 +215,7 @@
 The 'filelist' is expanded as one or more table rows, right below the
 clicked item:
 
-    >>> filelist_body = first_tag_by_class(
-    ...     anon_browser.contents, 'queue-4')
+    >>> filelist_body = first_tag_by_class(anon_browser.contents, 'queue-4')
     >>> filelist = filelist_body.findAll('tr')
 
 It contains a list of files related to the queue item clicked, followed
@@ -277,9 +273,6 @@
 And store a chroot for ubuntu breezy-autotest/i386 architectures, so
 the builds can be created.
 
-    >>> from zope.component import getUtility
-    >>> from lp.testing import login, logout
-    >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
@@ -503,8 +496,7 @@
 The user can drill down into the file list to see the overridden binary
 values:
 
-    >>> filelist = find_tags_by_class(
-    ...     anon_browser.contents, 'queue-2')
+    >>> filelist = find_tags_by_class(anon_browser.contents, 'queue-2')
     >>> for row in filelist:
     ...     print extract_text(row)
     pmount_1.0-1_all.deb (18 bytes) NEW 0.1-1 restricted admin extra
@@ -597,4 +589,3 @@
 
     >>> from lp.testing.layers import LibrarianLayer
     >>> LibrarianLayer.librarian_fixture.clear()
-

=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
--- lib/lp/soyuz/stories/webservice/xx-archive.txt	2012-08-08 16:34:39 +0000
+++ lib/lp/soyuz/stories/webservice/xx-archive.txt	2012-08-09 11:36:26 +0000
@@ -284,8 +284,6 @@
     >>> from lp.testing.pages import webservice_for_person
     >>> from lp.services.webapp.interfaces import OAuthPermission
     >>> from lp.registry.interfaces.distribution import IDistributionSet
-    >>> from lp.testing.factory import LaunchpadObjectFactory
-    >>> factory = LaunchpadObjectFactory()
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> cjwatson = getUtility(IPersonSet).getByName('kamion')
@@ -650,7 +648,6 @@
 
     >>> show_pocket_permissions('Proposed')
 
-    >>> grumpy = user_webservice.get("/ubuntu/grumpy").jsonBody()
     >>> response = user_webservice.named_get(
     ...     ubuntu['main_archive_link'], 'checkUpload',
     ...     distroseries=grumpy['self_link'],
@@ -711,20 +708,28 @@
 
     >>> ubuntu_owner_ws = ubuntu_owner_webservice.get(
     ...     "/~ubuntu-owner").jsonBody()
-    >>> response = ubuntu_owner_webservice.named_post(
-    ...     ubuntu_devel['main_archive_link'], 'newPocketQueueAdmin', {},
-    ...     api_version='devel', person=ubuntu_owner_ws['self_link'],
-    ...     pocket='Security', distroseries=grumpy['self_link'])
-    >>> print response
+    >>> hoary = user_webservice.get("/ubuntu/hoary").jsonBody()
+    >>> new_permissions = []
+    >>> for series in hoary, grumpy:
+    ...     response = ubuntu_owner_webservice.named_post(
+    ...         ubuntu_devel['main_archive_link'], 'newPocketQueueAdmin', {},
+    ...         api_version='devel', person=ubuntu_owner_ws['self_link'],
+    ...         pocket='Security', distroseries=series['self_link'])
+    ...     print response
+    ...     new_permissions.append(ubuntu_owner_webservice.get(
+    ...         response.getHeader('Location')).jsonBody())
+    HTTP/1.1 201 Created
+    ...
     HTTP/1.1 201 Created
     ...
 
-    >>> new_permission = ubuntu_owner_webservice.get(
-    ...     response.getHeader('Location')).jsonBody()
-    >>> print new_permission['self_link']
+    >>> print new_permissions[0]['self_link']
+    http://.../ubuntu/+archive/primary/+queue-admin/ubuntu-owner?type=pocket&item=SECURITY&series=hoary
+    >>> print new_permissions[1]['self_link']
     http://.../ubuntu/+archive/primary/+queue-admin/ubuntu-owner?type=pocket&item=SECURITY&series=grumpy
 
     >>> show_pockets_for_admin(ubuntu_owner_ws)
+    Queue Administration Rights ...~ubuntu-owner None None Security .../hoary
     Queue Administration Rights ...~ubuntu-owner None None Security .../grumpy
 
 deletePocketQueueAdmin removes these permissions.
@@ -735,10 +740,13 @@
     ...     pocket='Security')
     HTTP/1.1 200 Ok
     ...
-    >>> print ubuntu_owner_webservice.named_post(
-    ...     ubuntu_devel['main_archive_link'], 'deletePocketQueueAdmin', {},
-    ...     api_version='devel', person=ubuntu_owner_ws['self_link'],
-    ...     pocket='Security', distroseries=grumpy['self_link'])
+    >>> for series in hoary, grumpy:
+    ...     print ubuntu_owner_webservice.named_post(
+    ...         ubuntu_devel['main_archive_link'], 'deletePocketQueueAdmin',
+    ...         {}, api_version='devel', person=ubuntu_owner_ws['self_link'],
+    ...         pocket='Security', distroseries=series['self_link'])
+    HTTP/1.1 200 Ok
+    ...
     HTTP/1.1 200 Ok
     ...
 

=== modified file 'lib/lp/soyuz/stories/webservice/xx-builds.txt'
--- lib/lp/soyuz/stories/webservice/xx-builds.txt	2012-04-10 10:31:55 +0000
+++ lib/lp/soyuz/stories/webservice/xx-builds.txt	2012-08-09 11:36:26 +0000
@@ -101,7 +101,6 @@
 
     >>> login('foo.bar@xxxxxxxxxxxxx')
 
-    >>> from zope.component import getUtility
     >>> from lp.soyuz.interfaces.binarypackagebuild import (
     ...     IBinaryPackageBuildSet)
 
@@ -145,7 +144,6 @@
 
     >>> from lp.testing.pages import webservice_for_person
     >>> from lp.services.webapp.interfaces import OAuthPermission
-    >>> from lp.registry.interfaces.person import IPersonSet
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> admin_person = getUtility(IPersonSet).getByName('mark')
     >>> cprov = getUtility(IPersonSet).getByName('cprov')


Follow ups