Merge lp:~abentley/launchpad/request-build into lp:launchpad/db-devel

Proposed by Aaron Bentley
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~abentley/launchpad/request-build
Merge into: lp:launchpad/db-devel
Prerequisite: lp:~abentley/launchpad/recipe-index
Diff against target: 719 lines (+358/-105)
12 files modified
configs/development/build-from-branch.zcml (+24/-0)
lib/canonical/launchpad/browser/__init__.py (+0/-4)
lib/canonical/launchpad/icing/style-3-0.css.in (+11/-0)
lib/lp/code/browser/sourcepackagerecipe.py (+86/-1)
lib/lp/code/browser/tests/test_sourcepackagerecipe.py (+98/-21)
lib/lp/code/templates/sourcepackagerecipe-index.pt (+73/-51)
lib/lp/code/templates/sourcepackagerecipe-request-builds.pt (+27/-0)
lib/lp/soyuz/browser/archive.py (+14/-16)
lib/lp/soyuz/browser/configure.zcml (+1/-1)
lib/lp/soyuz/browser/tests/build-views.txt (+1/-1)
lib/lp/testing/__init__.py (+11/-3)
lib/lp/testing/factory.py (+12/-7)
To merge this branch: bzr merge lp:~abentley/launchpad/request-build
Reviewer Review Type Date Requested Status
Michael Nelson (community) ui Approve
Paul Hummer (community) code ui* Approve
Review via email: mp+22570@code.launchpad.net

Description of the change

= Summary =
Provide a UI for requesting builds, improve the display of recent/past builds

== Proposed fix ==
http://people.canonical.com/~abentley/request-builds-2.png
http://people.canonical.com/~abentley/recipe-index-table-3.png

== Pre-implementation notes ==
Pre and mid-implementation discussion with rockstar.

== Implementation details ==
Tables seem to default to basically unstyled, so I created a "code" CSS class that our tables can use.

A lot of new infrastructure was needed. This includes vocabularies for the user-targettable archives and applicable distroseries. I used functions returning SimpleVocabularies rather than defining new Vocabulary classes because I couldn't see any advantage to classes, and less code is better.

As a lint fix, I added several entries to testing.__all__, because they were apparently meant to be importable from testing.

I extracted make_archive_vocabulary out of createDestinationArchiveField, which required some re-evaluation of the functionality. It made no sense to me or Julian that the context archive could be included if the user was unable to target it, so the logic now always excludes the context archive, and the field is always required if the context is not an appropriate archive.

I also had to fix a lot of circular imports now that we're importing make_archive_vocabulary from Soyuz.

I also tweaked some factory methods and getUserBrowser, to support parameterizations I wanted to do.

== Tests ==
bin/test test_sourcepackagerecipe -t browser

== Demo and Q/A ==
Cannot be Q/A'ed until more pieces are in place.

= Launchpad lint =
(As far as I can tell, this is all bogus, including the super warnings)

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  configs/development/build-from-branch.zcml
  lib/canonical/launchpad/browser/__init__.py
  lib/canonical/launchpad/icing/style-3-0.css.in
  lib/lp/code/browser/sourcepackagerecipe.py
  lib/lp/code/browser/tests/test_sourcepackagerecipe.py
  lib/lp/code/templates/sourcepackagerecipe-index.pt
  lib/lp/code/templates/sourcepackagerecipe-request-builds.pt
  lib/lp/soyuz/browser/archive.py
  lib/lp/soyuz/browser/configure.zcml
  lib/lp/soyuz/browser/tests/build-views.txt
  lib/lp/testing/__init__.py
  lib/lp/testing/factory.py

== Pylint notices ==

lib/canonical/launchpad/browser/__init__.py
    15: [F0401] Unable to import 'lp.soyuz.browser.binarypackagerelease'
    16: [F0401] Unable to import 'lp.code.browser.branchmergeproposal'
    17: [F0401] Unable to import 'lp.code.browser.branchref'
    18: [F0401] Unable to import 'lp.code.browser.branchsubscription'
    19: [F0401] Unable to import 'lp.code.browser.branchvisibilitypolicy'
    20: [F0401] Unable to import 'lp.code.browser.codeimport'
    21: [F0401] Unable to import 'lp.code.browser.codeimportmachine'
    22: [F0401] Unable to import 'lp.registry.browser.codeofconduct'
    23: [F0401] Unable to import 'lp.code.browser.codereviewcomment'
    24: [F0401] Unable to import 'lp.registry.browser.distributionmirror'
    25: [F0401] Unable to import 'lp.soyuz.browser.distributionsourcepackagerelease'
    26: [F0401] Unable to import 'lp.soyuz.browser.distroarchseries'
    27: [F0401] Unable to import 'lp.soyuz.browser.distroarchseriesbinarypackage'
    28: [F0401] Unable to import 'lp.soyuz.browser.distroarchseriesbinarypackagerelease'
    29: [F0401] Unable to import 'lp.soyuz.browser.distroseriesbinarypackage'
    30: [F0401] Unable to import 'lp.soyuz.browser.distroseriessourcepackagerelease'
    31: [F0401] Unable to import 'lp.answers.browser.faq'
    32: [F0401] Unable to import 'lp.answers.browser.faqcollection'
    33: [F0401] Unable to import 'lp.answers.browser.faqtarget'
    34: [F0401] Unable to import 'lp.registry.browser.featuredproject'
    35: [F0401] Unable to import 'canonical.launchpad.browser.feeds'
    36: [F0401] Unable to import 'lp.hardwaredb.browser.hwdb'
    37: [F0401] Unable to import 'lp.registry.browser.karma'
    38: [F0401] Unable to import 'canonical.launchpad.browser.launchpad'
    39: [F0401] Unable to import 'canonical.launchpad.browser.launchpadstatistic'
    40: [F0401] Unable to import 'canonical.launchpad.browser.librarian'
    41: [F0401] Unable to import 'canonical.launchpad.browser.logintoken'
    42: [F0401] Unable to import 'lp.registry.browser.mailinglists'
    43: [F0401] Unable to import 'lp.registry.browser.mentoringoffer'
    44: [F0401] Unable to import 'canonical.launchpad.browser.oauth'
    45: [F0401] Unable to import 'lp.registry.browser.objectreassignment'
    46: [F0401] Unable to import 'canonical.launchpad.browser.packagerelationship'
    47: [F0401] Unable to import 'lp.registry.browser.peoplemerge'
    48: [F0401] Unable to import 'lp.registry.browser.poll'
    49: [F0401] Unable to import 'lp.soyuz.browser.publishedpackage'
    50: [F0401] Unable to import 'lp.soyuz.browser.publishing'
    51: [F0401] Unable to import 'lp.answers.browser.question'
    52: [F0401] Unable to import 'lp.answers.browser.questiontarget'
    53: [F0401] Unable to import 'lp.soyuz.browser.queue'
    54: [F0401] Unable to import 'lp.soyuz.browser.sourcepackagerelease'
    55: [F0401] Unable to import 'lp.blueprints.browser.specificationbranch'
    56: [F0401] Unable to import 'lp.blueprints.browser.specificationdependency'
    57: [F0401] Unable to import 'lp.blueprints.browser.specificationfeedback'
    58: [F0401] Unable to import 'lp.blueprints.browser.specificationgoal'
    59: [F0401] Unable to import 'lp.blueprints.browser.specificationsubscription'
    60: [F0401] Unable to import 'lp.blueprints.browser.specificationtarget'
    61: [F0401] Unable to import 'lp.blueprints.browser.sprint'
    62: [F0401] Unable to import 'lp.blueprints.browser.sprintattendance'
    63: [F0401] Unable to import 'lp.blueprints.browser.sprintspecification'
    64: [F0401] Unable to import 'lp.registry.browser.team'
    65: [F0401] Unable to import 'lp.registry.browser.teammembership'
    66: [F0401] Unable to import 'canonical.launchpad.browser.temporaryblobstorage'
    67: [F0401] Unable to import 'canonical.launchpad.browser.widgets'

lib/lp/code/browser/sourcepackagerecipe.py
    11: [F0401] Unable to import 'zope.component'
    13: [F0401] Unable to import 'zope.schema'
    14: [F0401] Unable to import 'zope.schema.vocabulary'
    15: [F0401] Unable to import 'canonical.widgets.itemswidgets'
    17: [F0401] Unable to import 'canonical.launchpad.interfaces'
    18: [F0401] Unable to import 'canonical.launchpad.webapp'
    21: [F0401] Unable to import 'canonical.launchpad.webapp.authorization'
    22: [F0401] Unable to import 'lp.buildmaster.interfaces.buildbase'
    23: [F0401] Unable to import 'lp.code.interfaces.sourcepackagerecipe'
    24: [F0401] Unable to import 'lp.soyuz.browser.archive'
    25: [F0401] Unable to import 'lp.soyuz.interfaces.archive'
    27: [F0401] Unable to import 'lp.registry.interfaces.distroseries'
    28: [F0401] Unable to import 'lp.registry.interfaces.pocket'

lib/lp/code/browser/tests/test_sourcepackagerecipe.py
    29: [E1002, TestSourcePackageRecipeView.setUp] Use super on an old style class

lib/lp/soyuz/browser/archive.py
    31: [F0401] Unable to import 'pytz'
    34: [F0401] Unable to import 'zope.app.form.browser'
    35: [F0401] Unable to import 'zope.component'
    36: [F0401] Unable to import 'zope.formlib'
    38: [F0401] Unable to import 'zope.security.proxy'
    39: [F0401] Unable to import 'zope.schema'
    40: [F0401] Unable to import 'zope.schema.interfaces'
    41: [F0401] Unable to import 'zope.schema.vocabulary'
    42: [F0401] Unable to import 'storm.zope.interfaces'
    46: [F0401] Unable to import 'canonical.cachedproperty'
    47: [F0401] Unable to import 'canonical.launchpad'
    48: [F0401] Unable to import 'canonical.launchpad.helpers'
    49: [F0401] Unable to import 'canonical.lazr.utils'
    50: [F0401] Unable to import 'lp.buildmaster.interfaces.buildbase'
    51: [F0401] Unable to import 'lp.services.browser_helpers'
    52: [F0401] Unable to import 'lp.services.worlddata.interfaces.country'
    53: [F0401] Unable to import 'lp.soyuz.browser.build'
    54: [F0401] Unable to import 'lp.soyuz.browser.sourceslist'
    56: [F0401] Unable to import 'canonical.launchpad.browser.librarian'
    57: [F0401] Unable to import 'lp.soyuz.adapters.archivedependencies'
    59: [F0401] Unable to import 'lp.soyuz.adapters.archivesourcepublication'
    61: [F0401] Unable to import 'lp.soyuz.interfaces.archive'
    64: [F0401] Unable to import 'lp.soyuz.interfaces.archivepermission'
    66: [F0401] Unable to import 'lp.soyuz.interfaces.archivesubscriber'
    67: [F0401] Unable to import 'lp.soyuz.interfaces.binarypackagename'
    68: [F0401] Unable to import 'lp.soyuz.interfaces.build'
    69: [F0401] Unable to import 'lp.soyuz.interfaces.buildrecords'
    70: [F0401] Unable to import 'lp.soyuz.interfaces.component'
    71: [F0401] Unable to import 'lp.registry.interfaces.series'
    72: [F0401] Unable to import 'canonical.launchpad.interfaces.launchpad'
    74: [F0401] Unable to import 'lp.soyuz.interfaces.packagecopyrequest'
    76: [F0401] Unable to import 'lp.soyuz.interfaces.packageset'
    77: [F0401] Unable to import 'lp.registry.interfaces.person'
    78: [F0401] Unable to import 'lp.registry.interfaces.pocket'
    79: [F0401] Unable to import 'lp.soyuz.interfaces.publishing'
    82: [F0401] Unable to import 'lp.registry.interfaces.sourcepackagename'
    84: [F0401] Unable to import 'canonical.launchpad.webapp'
    88: [F0401] Unable to import 'lp.soyuz.scripts.packagecopier'
    89: [F0401] Unable to import 'canonical.launchpad.webapp.authorization'
    90: [F0401] Unable to import 'canonical.launchpad.webapp.badge'
    91: [F0401] Unable to import 'canonical.launchpad.webapp.batching'
    92: [F0401] Unable to import 'canonical.launchpad.webapp.interfaces'
    93: [F0401] Unable to import 'canonical.launchpad.webapp.menu'
    94: [F0401] Unable to import 'canonical.launchpad.webapp.tales'
    95: [F0401] Unable to import 'canonical.widgets'
    97: [F0401] Unable to import 'canonical.widgets.itemswidgets'
    99: [F0401] Unable to import 'canonical.widgets.lazrjs'
    101: [F0401] Unable to import 'canonical.widgets.textwidgets'
    791: [E1002, ArchiveView.initialize] Use super on an old style class
    989: [E1002, ArchiveSourceSelectionFormView.setUpWidgets] Use super on an old style class

lib/lp/testing/__init__.py
    57: [F0401] Unable to import 'pytz'
    58: [F0401] Unable to import 'storm.expr'
    59: [F0401] Unable to import 'storm.store'
    60: [F0401] Unable to import 'storm.tracer'
    63: [F0401] Unable to import 'transaction'
    67: [F0401] Unable to import 'windmill.authoring'
    69: [F0401] Unable to import 'zope.component'
    70: [F0401] Unable to import 'zope.event'
    72: [F0401] Unable to import 'zope.security.proxy'
    74: [F0401] Unable to import 'zope.testing.testrunner.runner'
    76: [F0401] Unable to import 'canonical.launchpad.webapp'
    77: [F0401] Unable to import 'canonical.config'
    78: [F0401] Unable to import 'canonical.launchpad.webapp.interfaces'
    79: [F0401] Unable to import 'canonical.launchpad.windmill.testing'
    80: [F0401] Unable to import 'lp.codehosting.vfs'
    83: [F0401] Unable to import 'lp.testing._login'
    87: [F0401] Unable to import 'lp.testing._tales'
    88: [F0401] Unable to import 'lp.testing._webservice'
    201: [F0401, run_with_storm_debug] Unable to import 'storm.tracer'
    375: [F0401, TestCase.setUp] Unable to import 'lp.testing.factory'
    404: [F0401, TestCaseWithFactory.setUp] Unable to import 'lp.testing.factory'
    416: [F0401, TestCaseWithFactory.getUserBrowser] Unable to import 'canonical.launchpad.testing.pages'
    542: [F0401, TestCaseWithFactory.useBzrBranches] Unable to import 'lp.codehosting.scanner.tests.test_bzrsync'
    606: [F0401, YUIUnitTestCase.setUp] Unable to import 'canonical.launchpad.testing.pages'

lib/lp/testing/factory.py
    27: [F0401] Unable to import 'pytz'
    28: [F0401] Unable to import 'storm.store'
    29: [F0401] Unable to import 'transaction'
    33: [F0401] Unable to import 'zope.component'
    34: [F0401] Unable to import 'zope.security.proxy'
    36: [F0401] Unable to import 'canonical.autodecorate'
    37: [F0401] Unable to import 'canonical.config'
    38: [F0401] Unable to import 'canonical.database.constants'
    41: [F0401] Unable to import 'canonical.launchpad.database.account'
    42: [F0401] Unable to import 'canonical.launchpad.database.emailaddress'
    43: [F0401] Unable to import 'canonical.launchpad.database.message'
    44: [F0401] Unable to import 'canonical.launchpad.interfaces'
    45: [F0401] Unable to import 'canonical.launchpad.interfaces.account'
    47: [F0401] Unable to import 'canonical.launchpad.interfaces.emailaddress'
    49: [F0401] Unable to import 'canonical.launchpad.interfaces.gpghandler'
    50: [F0401] Unable to import 'lp.hardwaredb.interfaces.hwdb'
    53: [F0401] Unable to import 'canonical.launchpad.interfaces.launchpad'
    54: [F0401] Unable to import 'canonical.launchpad.interfaces.librarian'
    55: [F0401] Unable to import 'canonical.launchpad.interfaces.temporaryblobstorage'
    58: [F0401] Unable to import 'canonical.launchpad.webapp.dbpolicy'
    59: [F0401] Unable to import 'canonical.launchpad.webapp.interfaces'
    62: [F0401] Unable to import 'lp.blueprints.interfaces.specification'
    64: [F0401] Unable to import 'lp.blueprints.interfaces.sprint'
    66: [F0401] Unable to import 'lp.bugs.interfaces.bug'
    67: [F0401] Unable to import 'lp.bugs.interfaces.bugtask'
    68: [F0401] Unable to import 'lp.bugs.interfaces.bugtracker'
    69: [F0401] Unable to import 'lp.bugs.interfaces.bugwatch'
    70: [F0401] Unable to import 'lp.buildmaster.interfaces.builder'
    71: [F0401] Unable to import 'lp.buildmaster.interfaces.buildfarmjob'
    72: [F0401] Unable to import 'lp.buildmaster.model.buildqueue'
    74: [F0401] Unable to import 'lp.code.enums'
    79: [F0401] Unable to import 'lp.code.errors'
    80: [F0401] Unable to import 'lp.code.interfaces.branchmergequeue'
    81: [F0401] Unable to import 'lp.code.interfaces.branchnamespace'
    82: [F0401] Unable to import 'lp.code.interfaces.branchtarget'
    83: [F0401] Unable to import 'lp.code.interfaces.codeimport'
    84: [F0401] Unable to import 'lp.code.interfaces.codeimportevent'
    85: [F0401] Unable to import 'lp.code.interfaces.codeimportmachine'
    86: [F0401] Unable to import 'lp.code.interfaces.codeimportresult'
    87: [F0401] Unable to import 'lp.code.interfaces.revision'
    88: [F0401] Unable to import 'lp.code.interfaces.sourcepackagerecipe'
    89: [F0401] Unable to import 'lp.code.interfaces.sourcepackagerecipebuild'
    92: [F0401] Unable to import 'lp.code.model.diff'
    93: [F0401] Unable to import 'lp.codehosting.codeimport.worker'
    95: [F0401] Unable to import 'lp.registry.interfaces.distribution'
    96: [F0401] Unable to import 'lp.registry.model.distributionsourcepackage'
    98: [F0401] Unable to import 'lp.registry.interfaces.distroseries'
    99: [F0401] Unable to import 'lp.registry.interfaces.gpg'
    100: [F0401] Unable to import 'lp.registry.interfaces.mailinglist'
    102: [F0401] Unable to import 'lp.registry.interfaces.mailinglistsubscription'
    104: [F0401] Unable to import 'lp.registry.interfaces.person'
    106: [F0401] Unable to import 'lp.registry.interfaces.poll'
    107: [F0401] Unable to import 'lp.registry.interfaces.product'
    108: [F0401] Unable to import 'lp.registry.interfaces.productseries'
    109: [F0401] Unable to import 'lp.registry.interfaces.projectgroup'
    110: [F0401] Unable to import 'lp.registry.interfaces.series'
    111: [F0401] Unable to import 'lp.registry.interfaces.sourcepackage'
    113: [F0401] Unable to import 'lp.registry.interfaces.sourcepackagename'
    115: [F0401] Unable to import 'lp.registry.interfaces.ssh'
    116: [F0401] Unable to import 'lp.registry.interfaces.distributionmirror'
    118: [F0401] Unable to import 'lp.registry.interfaces.pocket'
    119: [F0401] Unable to import 'lp.registry.model.milestone'
    120: [F0401] Unable to import 'lp.registry.model.suitesourcepackage'
    122: [F0401] Unable to import 'lp.services.mail.signedmessage'
    123: [F0401] Unable to import 'lp.services.worlddata.interfaces.country'
    124: [F0401] Unable to import 'lp.services.worlddata.interfaces.language'
    126: [F0401] Unable to import 'lp.soyuz.interfaces.archive'
    128: [F0401] Unable to import 'lp.soyuz.adapters.packagelocation'
    129: [F0401] Unable to import 'lp.soyuz.interfaces.component'
    130: [F0401] Unable to import 'lp.soyuz.interfaces.packageset'
    131: [F0401] Unable to import 'lp.soyuz.interfaces.processor'
    132: [F0401] Unable to import 'lp.soyuz.interfaces.publishing'
    133: [F0401] Unable to import 'lp.soyuz.interfaces.section'
    134: [F0401] Unable to import 'lp.soyuz.model.processor'
    135: [F0401] Unable to import 'lp.soyuz.model.publishing'
    137: [F0401] Unable to import 'lp.testing'
    139: [F0401] Unable to import 'lp.translations.interfaces.potemplate'
    140: [F0401] Unable to import 'lp.translations.interfaces.translationgroup'
    142: [F0401] Unable to import 'lp.translations.interfaces.translationsperson'
    143: [F0401] Unable to import 'lp.translations.interfaces.translator'
    144: [F0401] Unable to import 'lp.translations.interfaces.translationtemplatesbuildjob'
    1756: [F0401, LaunchpadObjectFactory.makeRecipe] Unable to import 'bzrlib.plugins.builder.recipe'

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :
Revision history for this message
Paul Hummer (rockstar) wrote :

Here are the things we discussed on the phone today that should be improvements:

 - The request-build page should only show "active" distroseries.
 - The recipe index page should have the build history in a table, so that the data is easier to scan (line up the various parts of the history)

Revision history for this message
Aaron Bentley (abentley) wrote :
Revision history for this message
Paul Hummer (rockstar) wrote :

Based on the screenshot at:

http://people.canonical.com/~abentley/recipe-index-table-3.png

This looks good. Moving the table down to be a full width and making description half width seems to be the best plan. Thanks for adding the padding below the table for "Request a Build." I think this UI is complete.

Revision history for this message
Paul Hummer (rockstar) wrote :

70 +table.code {
71 + margin-bottom: 1em;
72 +}
73 +table.code th {
74 + text-align: left;
75 + font-weight: bold;
76 +}
77 +table.code th, table.code td {
78 + padding-right: 3em;
79 +}
80 +

I'd rather see this implemented as a specific id, since it's still pretty specific to the column count (for padding) and where the table is used.

157 + class schema(Interface):
158 + """Schema for requesting a build."""
159 + distros = List(
160 + Choice(vocabulary='BuildableDistroSeries'),
161 + title=u'Distribution series')
162 + archive = Choice(vocabulary='TargetPPAs', title=u'Archive')
163 +

I like this. I think it makes for less clutter. Have we done this before somewhere? I'm worried it might be inconsistent, even though it's better.

Other than that, this looks good. Thanks for working on it.

review: Approve (code ui*)
Revision history for this message
Michael Nelson (michael.nelson) wrote :

Hi Aaron and Paul,

Looking great.

Regarding the table styling, did you try adding the 'listing' class? That's what is used in other places (see table.listing in both style.css and style-3-0.css.in) for styling tables that list items. It would be great to re-use it for consistency rather than creating another table listing style if possible.

I think I forgot to mention during the review for the recipe index page, but I had a question about the breadcrumbs... shouldn't they be:

Person-name5 >> Branches >> generic_string23 recipe and
Person-name5 >> Branches >> generic_string23 recipe >> Request builds
?

And lastly, I was surprised to see non-ubuntu distroseries there for selection? Currently we only support PPA's where the distribution is ubuntu? (see lp.soyuz.browser.archive.ArchiveActivateView.save_action and https://bugs.edge.launchpad.net/soyuz/+bug/188564)

review: Needs Information (ui)
Revision history for this message
Aaron Bentley (abentley) wrote :

On 04/09/2010 06:30 AM, Michael Nelson wrote:
> Review: Needs Information ui
> Hi Aaron and Paul,
>
> Looking great.
>
> Regarding the table styling, did you try adding the 'listing' class?

No. Thanks for pointing it out.

However, it does not work very well:
http://people.canonical.com/~abentley/recipe-index-table-4.png

The table is unnecessarily wide, and the headings are not aligned with
the cell contents.

> I think I forgot to mention during the review for the recipe index page, but I had a question about the breadcrumbs... shouldn't they be:
>
> Person-name5>> Branches>> generic_string23 recipe and
> Person-name5>> Branches>> generic_string23 recipe>> Request builds
> ?
>
> And lastly, I was surprised to see non-ubuntu distroseries there for selection?

We're aware that we may need to tweak this before going live, but it has
been difficult to get answers about exactly what selection criteria
should be used, so we went with simply listing active distroseries.

Aaron

Revision history for this message
Aaron Bentley (abentley) wrote :

On 04/09/2010 12:37 AM, Paul Hummer wrote:
> Review: Approve code ui*
> 70 +table.code {
> 71 + margin-bottom: 1em;
> 72 +}
> 73 +table.code th {
> 74 + text-align: left;
> 75 + font-weight: bold;
> 76 +}
> 77 +table.code th, table.code td {
> 78 + padding-right: 3em;
> 79 +}
> 80 +
>
> I'd rather see this implemented as a specific id, since it's still pretty specific to the column count (for padding) and where the table is used.

How is it specific to the column count, or where the table is used?

I really want tables to not be broken, and to me that means:
- left-aligned headings
- bold (or otherwise emphasized) headings
- reasonable padding between columns
- blank space between the bottom of the table and the next element

If it wasn't disruptive, I would make this the default style for all tables.

> 157 + class schema(Interface):
> 158 + """Schema for requesting a build."""
> 159 + distros = List(
> 160 + Choice(vocabulary='BuildableDistroSeries'),
> 161 + title=u'Distribution series')
> 162 + archive = Choice(vocabulary='TargetPPAs', title=u'Archive')
> 163 +
>
> I like this. I think it makes for less clutter.

The fact that it's an inner class named schema, instead of being defined
externally and then assigned as a member?

> Have we done this before somewhere? I'm worried it might be inconsistent, even though it's better.

A quick grep suggests this is the first time we've done this. I hope
not the last.

Aaron

Revision history for this message
Michael Nelson (michael.nelson) wrote :

On Fri, Apr 9, 2010 at 4:15 PM, Aaron Bentley <email address hidden> wrote:
> On 04/09/2010 06:30 AM, Michael Nelson wrote:
>> Review: Needs Information ui
>> Hi Aaron and Paul,
>>
>> Looking great.
>>
>> Regarding the table styling, did you try adding the 'listing' class?
>
> No.  Thanks for pointing it out.
>
> However, it does not work very well:
> http://people.canonical.com/~abentley/recipe-index-table-4.png
>
> The table is unnecessarily wide,

There is .narrow-listing too (which you would need to add in addition
to .listing), if that helps (as yes, the .listing class defaults to
100%)

> and the headings are not aligned with
> the cell contents.

I *think* this is because all the listing styles assume that your
table uses the thead/tbody elements, which you're not afaics. See:

lib/canonical/launchpad/icing/style.css:278 ff.

>
>> I think I forgot to mention during the review for the recipe index page, but I had a question about the breadcrumbs... shouldn't they be:
>>
>> Person-name5>>  Branches>>  generic_string23 recipe and
>> Person-name5>>  Branches>>  generic_string23 recipe>>  Request builds
>> ?

Any comment about this?

>>
>> And lastly, I was surprised to see non-ubuntu distroseries there for selection?
>
> We're aware that we may need to tweak this before going live, but it has
> been difficult to get answers about exactly what selection criteria
> should be used, so we went with simply listing active distroseries.

I see. My assumption is that it's simply active ubuntu distroseries.

Hope that helps!

>
> Aaron
> --
> https://code.launchpad.net/~abentley/launchpad/request-build/+merge/22570
> You are reviewing the proposed merge of lp:~abentley/launchpad/request-build into lp:launchpad.
>

Revision history for this message
Michael Nelson (michael.nelson) wrote :

Forgot to mark this as approved, assuming you think about the previous comment :)

review: Approve (ui)
Revision history for this message
Aaron Bentley (abentley) wrote :

On 04/09/2010 11:18 AM, Michael Nelson wrote:
> On Fri, Apr 9, 2010 at 4:15 PM, Aaron Bentley<email address hidden> wrote:
>> The table is unnecessarily wide,
>
> There is .narrow-listing too (which you would need to add in addition
> to .listing), if that helps (as yes, the .listing class defaults to
> 100%)

That is much better.

> I *think* this is because all the listing styles assume that your
> table uses the thead/tbody elements, which you're not afaics. See:
>
> lib/canonical/launchpad/icing/style.css:278 ff.

I don't understand why the style would assume that. It would be much
nicer to only expect thead/tbody when that provides functionality.

Anyhow, it's much improved:
http://people.canonical.com/~abentley/recipe-index-table-5.png

I'd rather have left-aligned headings, but anything's better than
right-aligned.

>>> I think I forgot to mention during the review for the recipe index page, but I had a question about the breadcrumbs... shouldn't they be:
>>>
>>> Person-name5>> Branches>> generic_string23 recipe and
>>> Person-name5>> Branches>> generic_string23 recipe>> Request builds
>>> ?
>
> Any comment about this?

I'd rather be eating toast than thinking about breadcrumbs?

Aaron

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configs/development/build-from-branch.zcml'
2--- configs/development/build-from-branch.zcml 2010-03-16 19:22:29 +0000
3+++ configs/development/build-from-branch.zcml 2010-04-13 13:23:28 +0000
4@@ -20,6 +20,12 @@
5 name="+index"
6 template="../../lib/lp/code/templates/sourcepackagerecipe-index.pt"
7 permission="zope.Public"/>
8+ <browser:page
9+ for="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipe"
10+ class="lp.code.browser.sourcepackagerecipe.SourcePackageRecipeRequestBuildsView"
11+ name="+request-builds"
12+ template="../../lib/lp/code/templates/sourcepackagerecipe-request-builds.pt"
13+ permission="zope.Public"/>
14 </facet>
15 <facet facet="branches">
16 <browser:defaultView
17@@ -32,5 +38,23 @@
18 name="+index"
19 template="../../lib/lp/code/templates/sourcepackagerecipe-index.pt"
20 permission="zope.Public"/>
21+ <browser:menus
22+ classes="SourcePackageRecipeContextMenu"
23+ module="lp.code.browser.sourcepackagerecipe"/>
24+
25 </facet>
26+ <securedutility
27+ name="BuildableDistroSeries"
28+ component="lp.code.browser.sourcepackagerecipe.buildable_distroseries_vocabulary"
29+ provides="zope.schema.interfaces.IVocabularyFactory"
30+ >
31+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
32+ </securedutility>
33+ <securedutility
34+ name="TargetPPAs"
35+ component="lp.code.browser.sourcepackagerecipe.target_ppas_vocabulary"
36+ provides="zope.schema.interfaces.IVocabularyFactory"
37+ >
38+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
39+ </securedutility>
40 </configure>
41
42=== modified file 'lib/canonical/launchpad/browser/__init__.py'
43--- lib/canonical/launchpad/browser/__init__.py 2010-02-16 20:36:48 +0000
44+++ lib/canonical/launchpad/browser/__init__.py 2010-04-13 13:23:28 +0000
45@@ -12,15 +12,11 @@
46
47 # XXX flacoste 2009/03/18 We should use specific imports instead of
48 # importing from this module.
49-from lp.soyuz.browser.archive import *
50-from lp.code.browser.bazaar import *
51 from lp.soyuz.browser.binarypackagerelease import *
52 from lp.code.browser.branchmergeproposal import *
53 from lp.code.browser.branchref import *
54 from lp.code.browser.branchsubscription import *
55 from lp.code.browser.branchvisibilitypolicy import *
56-from lp.soyuz.browser.build import *
57-from lp.soyuz.browser.builder import *
58 from lp.code.browser.codeimport import *
59 from lp.code.browser.codeimportmachine import *
60 from lp.registry.browser.codeofconduct import *
61
62=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
63--- lib/canonical/launchpad/icing/style-3-0.css.in 2010-04-08 19:19:13 +0000
64+++ lib/canonical/launchpad/icing/style-3-0.css.in 2010-04-13 13:23:28 +0000
65@@ -1407,5 +1407,16 @@
66 .distromirrorstatusONEWEEKBEHIND {color: red;}
67 .distromirrorstatusUNKNOWN {color: grey;}
68
69+table.code {
70+ margin-bottom: 1em;
71+}
72+table.code th {
73+ text-align: left;
74+ font-weight: bold;
75+}
76+table.code th, table.code td {
77+ padding-right: 3em;
78+}
79+
80 tr.bug-branch-summary td { padding: 0px 6px; }
81 tr.bug-branch-summary td.first { padding-left: 0px; }
82
83=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
84--- lib/lp/code/browser/sourcepackagerecipe.py 2010-03-23 14:42:14 +0000
85+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-04-13 13:23:28 +0000
86@@ -8,9 +8,38 @@
87 __all__ = []
88
89
90+from zope.component import getUtility
91+from zope.interface import Interface
92+from zope.schema import Choice, List
93+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
94+from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget
95+
96+from canonical.launchpad.interfaces import ILaunchBag
97 from canonical.launchpad.webapp import (
98- LaunchpadView)
99+ action, canonical_url, ContextMenu, custom_widget, LaunchpadFormView,
100+ LaunchpadView, Link)
101+from canonical.launchpad.webapp.authorization import check_permission
102 from lp.buildmaster.interfaces.buildbase import BuildStatus
103+from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
104+from lp.soyuz.browser.archive import make_archive_vocabulary
105+from lp.soyuz.interfaces.archive import (
106+ IArchiveSet)
107+from lp.registry.interfaces.distroseries import IDistroSeriesSet
108+from lp.registry.interfaces.pocket import PackagePublishingPocket
109+
110+
111+class SourcePackageRecipeContextMenu(ContextMenu):
112+ """Context menu for sourcepackage recipes."""
113+
114+ usedfor = ISourcePackageRecipe
115+
116+ facet = 'branches'
117+
118+ links = ('request_builds',)
119+
120+ def request_builds(self):
121+ """Provide a link for requesting builds of a recipe."""
122+ return Link('+request-builds', 'Request build(s)', icon='add')
123
124
125 class SourcePackageRecipeView(LaunchpadView):
126@@ -38,6 +67,62 @@
127 return builds
128
129
130+def buildable_distroseries_vocabulary(context):
131+ """Return a vocabulary of buildable distroseries."""
132+ dsset = getUtility(IDistroSeriesSet).search()
133+ terms = [SimpleTerm(distro, distro.id, distro.displayname)
134+ for distro in dsset if distro.active]
135+ return SimpleVocabulary(terms)
136+
137+def target_ppas_vocabulary(context):
138+ """Return a vocabulary of ppas that the current user can target."""
139+ ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
140+ return make_archive_vocabulary(
141+ ppa for ppa in ppas
142+ if check_permission('launchpad.Append', ppa))
143+
144+
145+class SourcePackageRecipeRequestBuildsView(LaunchpadFormView):
146+ """A view for requesting builds of a SourcePackageRecipe."""
147+
148+ @property
149+ def initial_values(self):
150+ """Set initial values for the widgets.
151+
152+ The distroseries function as defaults for requesting a build.
153+ """
154+ return {'distros': self.context.distroseries}
155+
156+ class schema(Interface):
157+ """Schema for requesting a build."""
158+ distros = List(
159+ Choice(vocabulary='BuildableDistroSeries'),
160+ title=u'Distribution series')
161+ archive = Choice(vocabulary='TargetPPAs', title=u'Archive')
162+
163+ custom_widget('distros', LabeledMultiCheckBoxWidget)
164+
165+ @property
166+ def title(self):
167+ return 'Request builds for %s' % self.context.name
168+
169+ label = title
170+
171+ @property
172+ def next_url(self):
173+ return canonical_url(self.context)
174+
175+ cancel_url = next_url
176+
177+ @action('Request builds', name='request')
178+ def request_action(self, action, data):
179+ """User action for requesting a number of builds."""
180+ for distroseries in data['distros']:
181+ self.context.requestBuild(
182+ data['archive'], self.user, distroseries,
183+ PackagePublishingPocket.RELEASE)
184+
185+
186 class SourcePackageRecipeBuildView(LaunchpadView):
187 """Default view of a SourcePackageRecipeBuild."""
188
189
190=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
191--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-04-08 11:40:09 +0000
192+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-04-13 13:23:28 +0000
193@@ -1,5 +1,6 @@
194 # Copyright 2010 Canonical Ltd. This software is licensed under the
195 # GNU Affero General Public License version 3 (see the file LICENSE).
196+# pylint: disable-msg=F0401
197
198 """Tests for the product view classes and templates."""
199
200@@ -18,34 +19,48 @@
201 from canonical.launchpad.testing.pages import extract_text, find_main_content
202 from lp.buildmaster.interfaces.buildbase import BuildStatus
203 from lp.code.browser.sourcepackagerecipe import SourcePackageRecipeView
204-from lp.testing import (TestCaseWithFactory)
205+from lp.testing import (ANONYMOUS, login, TestCaseWithFactory)
206
207
208 class TestSourcePackageRecipeView(TestCaseWithFactory):
209
210 layer = DatabaseFunctionalLayer
211
212+ def setUp(self):
213+ """Provide useful defaults."""
214+ super(TestSourcePackageRecipeView, self).setUp()
215+ self.chef = self.factory.makePerson(
216+ displayname='Master Chef', name='chef', password='test')
217+ self.ppa = self.factory.makeArchive(
218+ displayname='Secret PPA', owner=self.chef)
219+ self.squirrel = self.factory.makeDistroSeries(
220+ displayname='Secret Squirrel', name='secret')
221+
222 def makeRecipe(self):
223- chef = self.factory.makePerson(displayname='Master Chef',
224- name='chef')
225+ """Create and return a specific recipe."""
226 chocolate = self.factory.makeProduct(name='chocolate')
227 cake_branch = self.factory.makeProductBranch(
228- owner=chef, name='cake', product=chocolate)
229- distroseries = self.factory.makeDistroSeries(
230- displayname='Secret Squirrel')
231+ owner=self.chef, name='cake', product=chocolate)
232 return self.factory.makeSourcePackageRecipe(
233- None, chef, distroseries, None, u'cake_recipe',
234+ None, self.chef, self.squirrel, None, u'cake_recipe',
235 u'This recipe builds a foo for disto bar, with my Secret Squirrel'
236 ' changes.', cake_branch)
237
238- def getMainText(self, recipe):
239- browser = self.getUserBrowser(canonical_url(recipe))
240+ def getRecipeBrowser(self, recipe, view_name=None):
241+ """Return a browser for the specified recipe, opened as Chef."""
242+ login(ANONYMOUS)
243+ url = canonical_url(recipe, view_name=view_name)
244+ return self.getUserBrowser(url, self.chef)
245+
246+ def getMainText(self, recipe, view_name=None):
247+ """Return the main text of a recipe page, as seen by Chef."""
248+ browser = self.getRecipeBrowser(recipe, view_name)
249 return extract_text(find_main_content(browser.contents))
250
251 def test_index(self):
252 recipe = self.makeRecipe()
253 build = removeSecurityProxy(self.factory.makeSourcePackageRecipeBuild(
254- recipe=recipe))
255+ recipe=recipe, distroseries=self.squirrel, archive=self.ppa))
256 build.buildstate = BuildStatus.FULLYBUILT
257 build.datebuilt = datetime(2010, 03, 16, tzinfo=utc)
258 pattern = re.compile(dedent("""\
259@@ -63,7 +78,15 @@
260 Distribution series:
261 Secret Squirrel
262 Build records
263- Successful build.on 2010-03-16
264+ Status
265+ Time
266+ Distribution series
267+ Archive
268+ Successful build
269+ on 2010-03-16
270+ Secret Squirrel
271+ Secret PPA
272+ Request build\(s\)
273 Recipe contents
274 # bzr-builder format 0.2 deb-version 1.0
275 lp://dev/~chef/chocolate/cake"""), re.S)
276@@ -72,28 +95,45 @@
277
278 def test_index_no_suitable_builders(self):
279 recipe = self.makeRecipe()
280- build = removeSecurityProxy(self.factory.makeSourcePackageRecipeBuild(
281- recipe=recipe))
282+ removeSecurityProxy(self.factory.makeSourcePackageRecipeBuild(
283+ recipe=recipe, distroseries=self.squirrel, archive=self.ppa))
284 pattern = re.compile(dedent("""\
285 Build records
286+ Status
287+ Time
288+ Distribution series
289+ Archive
290 No suitable builders
291- Recipe contents"""), re.S)
292+ Secret Squirrel
293+ Secret PPA
294+ Request build\(s\)"""), re.S)
295 main_text = self.getMainText(recipe)
296 self.assertTrue(pattern.search(main_text), main_text)
297
298 def makeBuildJob(self, recipe):
299- build = self.factory.makeSourcePackageRecipeBuild(recipe=recipe)
300- buildjob = self.factory.makeSourcePackageRecipeBuildJob(
301- recipe_build=build)
302+ """Return a build associated with a buildjob."""
303+ build = self.factory.makeSourcePackageRecipeBuild(
304+ recipe=recipe, distroseries=self.squirrel, archive=self.ppa )
305+ self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
306 return build
307
308 def test_index_pending(self):
309+ """Test the listing of a pending build."""
310 recipe = self.makeRecipe()
311- buildjob = self.makeBuildJob(recipe)
312- builder = self.factory.makeBuilder()
313+ self.makeBuildJob(recipe)
314+ self.factory.makeBuilder()
315 pattern = re.compile(dedent("""\
316 Build records
317- Pending build.in .*\(estimated\)
318+ Status
319+ Time
320+ Distribution series
321+ Archive
322+ Pending build
323+ in .*
324+ \(estimated\)
325+ Secret Squirrel
326+ Secret PPA
327+ Request build\(s\)
328 Recipe contents"""), re.S)
329 main_text = self.getMainText(recipe)
330 self.assertTrue(pattern.search(main_text), main_text)
331@@ -125,4 +165,41 @@
332 set_day(build4, 13)
333 set_day(build5, 12)
334 set_day(build6, 11)
335- self.assertEqual([build5, build4, build3, build2, build1], view.builds)
336+ self.assertEqual(
337+ [build5, build4, build3, build2, build1], view.builds)
338+
339+ def test_request_builds_page(self):
340+ """Ensure the +request-builds page is sane."""
341+ recipe = self.makeRecipe()
342+ text = self.getMainText(recipe, '+request-builds')
343+ self.assertEqual(dedent(u"""\
344+ Request builds for cake_recipe
345+ Master Chef
346+ Branches
347+ Request builds for cake_recipe
348+ Archive:
349+ Secret PPA (chef/ppa)
350+ Distribution series:
351+ Warty
352+ Hoary
353+ Six
354+ 7.0
355+ Woody
356+ Sarge
357+ Guada2005
358+ Secret Squirrel
359+ or
360+ Cancel"""), text)
361+
362+ def test_request_builds_action(self):
363+ """Requesting a build creates pending builds."""
364+ recipe = self.makeRecipe()
365+ browser = self.getRecipeBrowser(recipe, '+request-builds')
366+ browser.getControl('Woody').click()
367+ browser.getControl('Request builds').click()
368+ build_distros = [
369+ build.distroseries.displayname for build in
370+ recipe.getBuilds(True)]
371+ build_distros.sort()
372+ # Secret Squirrel is checked by default.
373+ self.assertEqual(['Secret Squirrel', 'Woody'], build_distros)
374
375=== modified file 'lib/lp/code/templates/sourcepackagerecipe-index.pt'
376--- lib/lp/code/templates/sourcepackagerecipe-index.pt 2010-03-30 04:56:42 +0000
377+++ lib/lp/code/templates/sourcepackagerecipe-index.pt 2010-04-13 13:23:28 +0000
378@@ -22,58 +22,80 @@
379
380 <div metal:fill-slot="main">
381 <div class="yui-g first">
382+ <div class="yui-u first">
383+ <div class="portlet">
384+ <h2>Description</h2>
385+ <tal:description replace="context/description" />
386+ </div>
387+ </div>
388+ <div class="yui-u">
389+ <div class="portlet">
390+ <h2>Recipe information</h2>
391+ <div class="two-column-list">
392+ <dl id="owner">
393+ <dt>Owner:</dt>
394+ <dd tal:content="structure context/owner/fmt:link" />
395+ </dl>
396+ <dl id="base-branch">
397+ <dt>Base branch:</dt>
398+ <dd tal:content="structure context/base_branch/fmt:link" />
399+ </dl>
400+ <dl id="debian-version">
401+ <dt>Debian version:</dt>
402+ <dd tal:content="context/deb_version_template" />
403+ </dl>
404+ <dl id="distros">
405+ <dt>Distribution series:</dt>
406+ <dd>
407+ <ul>
408+ <li tal:repeat="curseries context/distroseries"
409+ tal:content="structure curseries/fmt:link" />
410+ </ul>
411+ </dd>
412+ </dl>
413+ </div>
414+ </div>
415+ </div>
416+ </div>
417+ <div class="yui-g">
418 <div class="portlet">
419- <h2>Description</h2>
420- <tal:description replace="context/description" />
421- </div>
422- </div>
423- <div class="yui-g">
424- <div class="yui-u first">
425- <div class="portlet">
426- <h2>Recipe information</h2>
427- <div class="two-column-list">
428- <dl id="owner">
429- <dt>Owner:</dt>
430- <dd tal:content="structure context/owner/fmt:link" />
431- </dl>
432- <dl id="base-branch">
433- <dt>Base branch:</dt>
434- <dd tal:content="structure context/base_branch/fmt:link" />
435- </dl>
436- <dl id="debian-version">
437- <dt>Debian version:</dt>
438- <dd tal:content="context/deb_version_template" />
439- </dl>
440- <dl id="distros">
441- <dt>Distribution series:</dt>
442- <dd>
443- <ul>
444- <li tal:repeat="curseries context/distroseries"
445- tal:content="structure curseries/fmt:link" />
446- </ul>
447- </dd>
448- </dl>
449- </div>
450- </div>
451- </div>
452- <div class="yui-u">
453- <div class="portlet">
454- <h2>Build records</h2>
455- <ul>
456- <li tal:repeat="build view/builds">
457- <tal:status replace="build/@@+index/status" />
458- <tal:date replace="build/@@+index/date/fmt:displaydate" />
459- <tal:estimate condition="build/@@+index/estimate">
460- (estimated)
461- </tal:estimate>
462- </li>
463- </ul>
464- </div>
465- </div>
466- </div>
467- <div class='portlet'>
468- <h2>Recipe contents</h2>
469- <pre tal:content="context/builder_recipe" />
470+ <h2>Build records</h2>
471+ <table class="listing narrow-listing" style='margin-bottom: 1em;'>
472+ <thead>
473+ <tr>
474+ <th>Status</th>
475+ <th>Time</th>
476+ <th>Distribution series</th>
477+ <th>Archive</th>
478+ </tr>
479+ </thead>
480+ <tbody>
481+ <tr tal:repeat="build view/builds">
482+ <td>
483+ <tal:status replace="build/@@+index/status" />
484+ </td>
485+ <td>
486+ <tal:date replace="build/@@+index/date/fmt:displaydate" />
487+ <tal:estimate condition="build/@@+index/estimate">
488+ (estimated)
489+ </tal:estimate>
490+ </td>
491+ <td>
492+ <tal:distro
493+ replace="structure build/distroseries/fmt:link:mainsite" />
494+ </td>
495+ <td>
496+ <tal:archive replace="structure build/archive/fmt:link"/>
497+ </td>
498+ </tr>
499+ </tbody>
500+ </table>
501+ <tal:request replace="structure context/menu:context/request_builds/fmt:link" />
502+ </div>
503+ <div class='portlet'>
504+ <h2>Recipe contents</h2>
505+ <pre tal:content="context/builder_recipe" />
506+ </div>
507 </div>
508 </div>
509 </body>
510
511=== added file 'lib/lp/code/templates/sourcepackagerecipe-request-builds.pt'
512--- lib/lp/code/templates/sourcepackagerecipe-request-builds.pt 1970-01-01 00:00:00 +0000
513+++ lib/lp/code/templates/sourcepackagerecipe-request-builds.pt 2010-04-13 13:23:28 +0000
514@@ -0,0 +1,27 @@
515+<html
516+ xmlns="http://www.w3.org/1999/xhtml"
517+ xmlns:tal="http://xml.zope.org/namespaces/tal"
518+ xmlns:metal="http://xml.zope.org/namespaces/metal"
519+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
520+ metal:use-macro="view/macro:page/main_side"
521+ i18n:domain="launchpad"
522+>
523+
524+
525+<body>
526+ <div metal:fill-slot="main">
527+ <div metal:use-macro="context/@@launchpad_form/form">
528+ <metal:formbody fill-slot="widgets">
529+ <table class="form">
530+ <tal:widget define="widget nocall:view/widgets/archive">
531+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
532+ </tal:widget>
533+ <tal:widget define="widget nocall:view/widgets/distros">
534+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
535+ </tal:widget>
536+ </table>
537+ </metal:formbody>
538+ </div>
539+ </div>
540+</body>
541+</html>
542
543=== modified file 'lib/lp/soyuz/browser/archive.py'
544--- lib/lp/soyuz/browser/archive.py 2010-03-12 06:23:04 +0000
545+++ lib/lp/soyuz/browser/archive.py 2010-04-13 13:23:28 +0000
546@@ -21,6 +21,7 @@
547 'ArchivePackagesView',
548 'ArchiveView',
549 'ArchiveViewBase',
550+ 'make_archive_vocabulary',
551 'traverse_distro_archive',
552 'traverse_named_ppa',
553 ]
554@@ -1141,6 +1142,15 @@
555 _messageNoValue = _("vocabulary-copy-to-same-series", "The same series")
556
557
558+def make_archive_vocabulary(archives):
559+ terms = []
560+ for archive in archives:
561+ token = '%s/%s' % (archive.owner.name, archive.name)
562+ label = '%s (%s)' % (archive.displayname, token)
563+ terms.append(SimpleTerm(archive, token, label))
564+ return SimpleVocabulary(terms)
565+
566+
567 class ArchivePackageCopyingView(ArchiveSourceSelectionFormView):
568 """Archive package copying view class.
569
570@@ -1200,27 +1210,15 @@
571
572 def createDestinationArchiveField(self):
573 """Create the 'destination_archive' field."""
574- terms = []
575- required = True
576-
577- for ppa in self.ppas_for_user:
578- # Do not include the context PPA in the dropdown widget
579- # and make this field not required. This way we can safely
580- # default to the context PPA when copying.
581- if self.can_copy_to_context_ppa and self.context == ppa:
582- required = False
583- continue
584- token = '%s/%s' % (ppa.owner.name, ppa.name)
585- terms.append(
586- SimpleTerm(ppa, token, '%s (%s)' % (ppa.displayname, token)))
587-
588+ # Do not include the context PPA in the dropdown widget.
589+ ppas = [ppa for ppa in self.ppas_for_user if self.context != ppa]
590 return form.Fields(
591 Choice(__name__='destination_archive',
592 title=_('Destination PPA'),
593- vocabulary=SimpleVocabulary(terms),
594+ vocabulary=make_archive_vocabulary(ppas),
595 description=_("Select the destination PPA."),
596 missing_value=self.context,
597- required=required))
598+ required=not self.can_copy_to_context_ppa))
599
600 def createDestinationSeriesField(self):
601 """Create the 'destination_series' field."""
602
603=== modified file 'lib/lp/soyuz/browser/configure.zcml'
604--- lib/lp/soyuz/browser/configure.zcml 2010-03-22 07:10:45 +0000
605+++ lib/lp/soyuz/browser/configure.zcml 2010-04-13 13:23:28 +0000
606@@ -674,7 +674,7 @@
607 name="+activate-ppa"
608 for="lp.registry.interfaces.person.IPerson"
609 facet="overview"
610- class="canonical.launchpad.browser.ArchiveActivateView"
611+ class="lp.soyuz.browser.archive.ArchiveActivateView"
612 permission="launchpad.Edit"
613 template="../templates/archive-activate.pt"/>
614 <browser:page
615
616=== modified file 'lib/lp/soyuz/browser/tests/build-views.txt'
617--- lib/lp/soyuz/browser/tests/build-views.txt 2010-03-16 10:47:00 +0000
618+++ lib/lp/soyuz/browser/tests/build-views.txt 2010-04-13 13:23:28 +0000
619@@ -21,7 +21,7 @@
620 For instance the 'PPA' action-menu link is not enabled for builds
621 targeted to the PRIMARY archive.
622
623- >>> from canonical.launchpad.browser import BuildContextMenu
624+ >>> from lp.soyuz.browser.build import BuildContextMenu
625 >>> build_menu = BuildContextMenu(hoary_failed_build)
626
627 >>> print build_menu.links
628
629=== modified file 'lib/lp/testing/__init__.py'
630--- lib/lp/testing/__init__.py 2010-04-05 16:06:06 +0000
631+++ lib/lp/testing/__init__.py 2010-04-13 13:23:28 +0000
632@@ -11,11 +11,14 @@
633 'FakeTime',
634 'get_lsb_information',
635 'is_logged_in',
636+ 'launchpadlib_for',
637+ 'launchpadlib_credentials_for',
638 'login',
639 'login_person',
640 'logout',
641 'map_branch_contents',
642 'normalize_whitespace',
643+ 'oauth_access_token_for',
644 'record_statements',
645 'run_with_login',
646 'run_with_storm_debug',
647@@ -402,18 +405,23 @@
648 self.factory = LaunchpadObjectFactory()
649 self.real_bzr_server = False
650
651- def getUserBrowser(self, url=None):
652+ def getUserBrowser(self, url=None, user=None, password='test'):
653 """Return a Browser logged in as a fresh user, maybe opened at `url`.
654+
655+ :param user: The user to open a browser for.
656+ :param password: The password to use. (This cannot be determined
657+ because it's stored as a hash.)
658 """
659 # Do the import here to avoid issues with import cycles.
660 from canonical.launchpad.testing.pages import setupBrowser
661 login(ANONYMOUS)
662- user = self.factory.makePerson(password='test')
663+ if user is None:
664+ user = self.factory.makePerson(password=password)
665 naked_user = removeSecurityProxy(user)
666 email = naked_user.preferredemail.email
667 logout()
668 browser = setupBrowser(
669- auth="Basic %s:test" % str(email))
670+ auth="Basic %s:%s" % (str(email), password))
671 if url is not None:
672 browser.open(url)
673 return browser
674
675=== modified file 'lib/lp/testing/factory.py'
676--- lib/lp/testing/factory.py 2010-04-13 13:13:45 +0000
677+++ lib/lp/testing/factory.py 2010-04-13 13:23:28 +0000
678@@ -1632,7 +1632,7 @@
679
680 def makeArchive(self, distribution=None, owner=None, name=None,
681 purpose=None, enabled=True, private=False,
682- virtualized=True, description=None):
683+ virtualized=True, description=None, displayname=None):
684 """Create and return a new arbitrary archive.
685
686 :param distribution: Supply IDistribution, defaults to a new one
687@@ -1665,8 +1665,9 @@
688
689 archive = getUtility(IArchiveSet).new(
690 owner=owner, purpose=purpose,
691- distribution=distribution, name=name, enabled=enabled,
692- require_virtualized=virtualized, description=description)
693+ distribution=distribution, name=name, displayname=displayname,
694+ enabled=enabled, require_virtualized=virtualized,
695+ description=description)
696
697 if private:
698 archive.private = True
699@@ -1745,12 +1746,16 @@
700
701 def makeSourcePackageRecipeBuild(self, sourcepackage=None, recipe=None,
702 requester=None, archive=None,
703- sourcename=None):
704+ sourcename=None, distroseries=None):
705 """Make a new SourcePackageRecipeBuild."""
706- if sourcepackage is None:
707- sourcepackage = self.makeSourcePackage(sourcename)
708 if recipe is None:
709- recipe = self.makeSourcePackageRecipe()
710+ recipe = self.makeSourcePackageRecipe(
711+ sourcepackagename=self.makeSourcePackageName(sourcename))
712+ if distroseries is None:
713+ distroseries = self.makeDistroSeries()
714+ if sourcepackage is None:
715+ sourcepackage = distroseries.getSourcePackage(
716+ recipe.sourcepackagename)
717 if requester is None:
718 requester = self.makePerson()
719 if archive is None:

Subscribers

People subscribed via source and target branches

to status/vote changes: