launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #00454
lp:~edwin-grubbs/launchpad/bug-602385-register-project-from-sourcepackage-page into lp:launchpad/devel
Edwin Grubbs has proposed merging lp:~edwin-grubbs/launchpad/bug-602385-register-project-from-sourcepackage-page into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Summary
-------
This branch makes it easy to register an upstream project from a source
package by prefilling the project registration form with data from the
source package.
Implementation details
----------------------
Added link to $sourcepackage/+edit-packaging and radio button to
$sourcepackage/+index
lib/lp/registry/browser/sourcepackage.py
lib/lp/registry/templates/sourcepackage-edit-packaging.pt
lib/lp/registry/browser/product.py
lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt
The makeBinaryPackagePublishingHistory() method had a
binarypackagerelease argument, but it completed ignored it.
lib/lp/testing/factory.py
When +edit-packaging became a MultiStepView, some of the tests
quit exercising the view completely, since no errors were expected, and
the view had just stopped doing anything. I added a check inside the
MultiStepView to help prevent this from happening.
lib/lp/registry/browser/tests/sourcepackage-views.txt
lib/canonical/launchpad/webapp/launchpadform.py
lib/canonical/launchpad/browser/multistep.py
Drive-by lint fixes.
lib/lp/registry/model/sourcepackage.py
Tests
-----
./bin/test -vv -t '/sourcepackage-views.txt|xx-sourcepackage-packaging.txt'
Demo and Q/A
------------
The summary field is not always populated, since the
only info a source package has is the summaries from its binary
packages that might not exist yet.
* Open http://launchpad.dev/ubuntu/warty/+source/foobar
* Select "Register the upstream project" radio button.
* Click the "Link to Upstream Project" button.
* The form should be prefilled.
* Enter the license.
* Submit the form.
* You should now be redirected to the source package page.
* Open http://launchpad.dev/ubuntu/warty/+source/pmount/+edit-packaging
* Click the "Register the upstream project" link.
* The form should be prefilled.
* Enter the license.
* Submit the form.
* You should now be redirected to the +edit-packaging page.
--
https://code.launchpad.net/~edwin-grubbs/launchpad/bug-602385-register-project-from-sourcepackage-page/+merge/31856
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~edwin-grubbs/launchpad/bug-602385-register-project-from-sourcepackage-page into lp:launchpad/devel.
=== modified file 'lib/canonical/launchpad/browser/multistep.py'
--- lib/canonical/launchpad/browser/multistep.py 2010-05-12 19:06:17 +0000
+++ lib/canonical/launchpad/browser/multistep.py 2010-08-05 16:17:58 +0000
@@ -93,6 +93,14 @@
view.total_steps = self.total_steps
view.is_step = self.getIsStepDict()
self.step_number += 1
+
+ action_required = None
+ for name in self.request.form.keys():
+ if name.startswith('field.actions.'):
+ action_required = (name, self.request.form[name])
+ break
+
+ action_taken = view.action_taken
while view.next_step is not None:
view = view.next_step(self.context, self.request)
assert isinstance(view, StepView), 'Not a StepView: %s' % view
@@ -102,8 +110,19 @@
view.is_step = self.getIsStepDict()
self.step_number += 1
view.injectStepNameInRequest()
+ if view.action_taken is not None:
+ action_taken = view.action_taken
+
self.view = view
+ if action_required is not None and action_taken is None:
+ # This is mostly useful for catching tests that pass
+ # in invalid form data via a dictionary instead of
+ # using a test browser.
+ raise AssertionError(
+ 'MultiStepView did not find action for %s=%r'
+ % action_required)
+
def render(self):
return self.view.render()
=== modified file 'lib/canonical/launchpad/webapp/launchpadform.py'
--- lib/canonical/launchpad/webapp/launchpadform.py 2010-06-23 23:07:10 +0000
+++ lib/canonical/launchpad/webapp/launchpadform.py 2010-08-05 16:17:58 +0000
@@ -74,6 +74,8 @@
actions = ()
+ action_taken = None
+
render_context = False
form_result = None
@@ -112,6 +114,7 @@
self.form_result = action.success(data)
if self.next_url:
self.request.response.redirect(self.next_url)
+ self.action_taken = action
def render(self):
"""Return the body of the response.
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2010-08-04 04:07:21 +0000
+++ lib/lp/registry/browser/product.py 2010-08-05 16:17:58 +0000
@@ -120,7 +120,7 @@
from canonical.launchpad.webapp.breadcrumb import Breadcrumb
from canonical.launchpad.webapp.launchpadform import (
action, custom_widget, LaunchpadEditFormView, LaunchpadFormView,
- ReturnToReferrerMixin)
+ ReturnToReferrerMixin, safe_action)
from canonical.launchpad.webapp.menu import NavigationMenu
from canonical.launchpad.webapp.tales import MenuAPI
from canonical.widgets.popup import PersonPickerWidget
@@ -1850,6 +1850,16 @@
search_results_count = 0
@property
+ def _return_url(self):
+ """This view is using the hidden _return_url field.
+
+ It is not using the `ReturnToReferrerMixin`, since none
+ of its other code is used, because multistep views can't
+ have next_url set until the form submission succeeds.
+ """
+ return self.request.form.get('_return_url')
+
+ @property
def _next_step(self):
"""Define the next step.
@@ -1866,6 +1876,10 @@
self.request.form['name'] = data['name'].lower()
self.request.form['summary'] = data['summary']
+ # Make this a safe_action, so that the sourcepackage page can skip
+ # the first step with a link (GET request) providing form values.
+ continue_action = safe_action(StepView.continue_action)
+
class ProjectAddStepTwo(StepView, ProductLicenseMixin, ReturnToReferrerMixin):
"""Step 2 (of 2) in the +new project add wizard."""
@@ -1887,6 +1901,16 @@
custom_widget('license_info', GhostWidget)
@property
+ def _return_url(self):
+ """This view is using the hidden _return_url field.
+
+ It is not using the `ReturnToReferrerMixin`, since none
+ of its other code is used, because multistep views can't
+ have next_url set until the form submission succeeds.
+ """
+ return self.request.form.get('_return_url')
+
+ @property
def step_description(self):
"""See `MultiStepView`."""
if self.search_results_count > 0:
@@ -2001,7 +2025,10 @@
self.product = self.create_product(data)
self.notifyCommercialMailingList()
notify(ObjectCreatedEvent(self.product))
- self.next_url = canonical_url(self.product)
+ if self._return_url is None:
+ self.next_url = canonical_url(self.product)
+ else:
+ self.next_url = self._return_url
class ProductAddView(MultiStepView):
@@ -2027,7 +2054,7 @@
driver = copy_field(IProduct['driver'])
- transfer_to_registry = Bool(
+ transfer_to_registry = Bool(
title=_("I do not want to maintain this project"),
required=False,
description=_(
=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py 2010-07-02 14:34:58 +0000
+++ lib/lp/registry/browser/sourcepackage.py 2010-08-05 16:17:58 +0000
@@ -19,6 +19,8 @@
from apt_pkg import ParseSrcDepends
from cgi import escape
+import string
+import urllib
from z3c.ptcompat import ViewPageTemplateFile
from zope.app.form.browser import DropdownWidget
from zope.app.form.interfaces import IInputWidget
@@ -35,12 +37,14 @@
from canonical.launchpad import helpers
from canonical.launchpad.browser.multistep import MultiStepView, StepView
+
from lp.bugs.browser.bugtask import BugTargetTraversalMixin
from canonical.launchpad.browser.packagerelationship import (
relationship_builder)
from lp.answers.browser.questiontarget import (
QuestionTargetFacetMixin, QuestionTargetAnswersMenu)
from lp.services.worlddata.interfaces.country import ICountry
+from lp.registry.browser.product import ProjectAddStepOne
from lp.registry.interfaces.packaging import IPackaging, IPackagingUtil
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.product import IProductSet
@@ -62,6 +66,25 @@
from canonical.lazr.utils import smartquote
+def get_register_upstream_url(source_package, return_url):
+ displayname = string.capwords(source_package.name.replace('-', ' '))
+ params = {
+ '_return_url': return_url,
+ 'field.name': source_package.name,
+ 'field.displayname': displayname,
+ 'field.title': displayname,
+ 'field.__visited_steps__': ProjectAddStepOne.step_name,
+ 'field.actions.continue': 'Continue',
+ }
+ if source_package.summary is None:
+ params['field.summary'] = ''
+ else:
+ params['field.summary'] = source_package.summary
+ query_string = urllib.urlencode(
+ sorted(params.items()), doseq=True)
+ return '/projects/+new?%s' % query_string
+
+
class SourcePackageNavigation(GetitemNavigation, BugTargetTraversalMixin):
usedfor = ISourcePackage
@@ -190,6 +213,11 @@
self.next_step = SourcePackageChangeUpstreamStepTwo
self.request.form['product'] = data['product']
+ @property
+ def register_upstream_url(self):
+ return get_register_upstream_url(
+ self.context, return_url=self.request.getURL())
+
class SourcePackageChangeUpstreamStepTwo(ReturnToReferrerMixin, StepView):
"""A view to set the `IProductSeries` of a sourcepackage."""
@@ -345,7 +373,7 @@
def processForm(self):
# look for an update to any of the things we track
form = self.request.form
- if form.has_key('packaging'):
+ if 'packaging' in form:
if self.productseries_widget.hasValidInput():
new_ps = self.productseries_widget.getInputValue()
# we need to create or update the packaging
@@ -445,6 +473,7 @@
initial_focus_widget = None
max_suggestions = 9
other_upstream = object()
+ register_upstream = object()
def setUpFields(self):
"""See `LaunchpadFormView`."""
@@ -467,9 +496,12 @@
vocab_terms.append(SimpleTerm(product, product.name, description))
# Add an option to represent the user's decision to choose a
# different project. Note that project names cannot be uppercase.
- description = 'Choose another upstream project'
- vocab_terms.append(
- SimpleTerm(self.other_upstream, 'OTHER_UPSTREAM', description))
+ vocab_terms.append(
+ SimpleTerm(self.other_upstream, 'OTHER_UPSTREAM',
+ 'Choose another upstream project'))
+ vocab_terms.append(
+ SimpleTerm(self.register_upstream, 'REGISTER_UPSTREAM',
+ 'Register the upstream project'))
upstream_vocabulary = SimpleVocabulary(vocab_terms)
self.form_fields = Fields(
@@ -487,6 +519,12 @@
self.next_url = canonical_url(
self.context, view_name="+edit-packaging")
return
+ elif upstream is self.register_upstream:
+ # The user wants to create a new project.
+ url = get_register_upstream_url(
+ self.context, return_url=self.request.getURL())
+ self.request.response.redirect(url)
+ return
self.context.setPackaging(upstream.development_focus, self.user)
self.request.response.addInfoNotification(
'The project %s was linked to this source package.' %
=== modified file 'lib/lp/registry/browser/tests/sourcepackage-views.txt'
--- lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-05-13 18:55:10 +0000
+++ lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-08-05 16:17:58 +0000
@@ -119,6 +119,7 @@
empty.
>>> form = {
+ ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step1',
... 'field.product': '',
... 'field.actions.continue': 'Continue',
... }
@@ -133,12 +134,18 @@
but there is no notification message that the upstream link was updated.
>>> form = {
- ... 'field.productseries': 'bonkers/crazy',
- ... 'field.actions.change': 'Change',
+ ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step2',
+ ... 'field.product': 'bonkers',
+ ... 'field.productseries': 'crazy',
+ ... 'field.actions.continue': 'Continue',
... }
>>> view = create_initialized_view(
... package, name='+edit-packaging', form=form,
... principal=product.owner)
+ >>> print view.view
+ <...SourcePackageChangeUpstreamStepTwo object...>
+ >>> print view.view.next_url
+ http://launchpad.dev/youbuntu/busy/+source/bonkers
>>> view.view.errors
[]
@@ -199,6 +206,7 @@
Registered upstream project:
Lernid
Choose another upstream project
+ Register the upstream project
The form does not steal focus because it is not the primary purpose of the
page.
@@ -229,6 +237,7 @@
Lernid...
Lernid Dev...
Choose another upstream project
+ Register the upstream project
Choosing the "Choose another upstream project" option redirects the user
to the +edit-packaging page where the user can search for a project.
@@ -259,7 +268,8 @@
... name='stinkyseries', product=product)
>>> distroseries = factory.makeDistroRelease(name='wonky',
... distribution=distribution)
- >>> sourcepackagename = factory.makeSourcePackageName(name='stinkypackage')
+ >>> sourcepackagename = factory.makeSourcePackageName(
+ ... name='stinkypackage')
>>> package = factory.makeSourcePackage(
... sourcepackagename=sourcepackagename, distroseries=distroseries)
@@ -360,3 +370,4 @@
match for this source package. Can you help us find one?
Registered upstream project:
Choose another upstream project
+ Register the upstream project
=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py 2010-07-15 15:01:18 +0000
+++ lib/lp/registry/model/sourcepackage.py 2010-08-05 16:17:58 +0000
@@ -39,7 +39,6 @@
from lp.registry.model.packaging import Packaging
from lp.translations.model.potemplate import (
HasTranslationTemplatesMixin,
- POTemplate,
TranslationTemplatesCollection)
from canonical.launchpad.interfaces.lpstorm import IStore
from lp.soyuz.model.publishing import (
@@ -52,7 +51,6 @@
SourcePackageRelease)
from lp.translations.model.translationimportqueue import (
HasTranslationImportsMixin)
-from canonical.launchpad.helpers import shortlist
from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
from lp.registry.interfaces.packaging import PackagingType
from lp.registry.interfaces.distribution import NoPartnerArchive
@@ -302,6 +300,7 @@
"""See `ISourcePackage`."""
releases = self.releases
if len(releases) == 0:
+ print '\n@@@@@@@@@@@@@@@@@@@@@@No releases'
return None
current = releases[0]
name_summaries = [
=== modified file 'lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt'
--- lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-05-18 17:05:29 +0000
+++ lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-08-05 16:17:58 +0000
@@ -1,4 +1,59 @@
-= Packaging =
+Packaging
+=========
+
+Create test data.
+
+ >>> login('admin@xxxxxxxxxxxxx')
+ >>> distribution = factory.makeDistribution(
+ ... name='youbuntu', displayname='Youbuntu')
+ >>> distroseries = factory.makeDistroRelease(name='busy',
+ ... distribution=distribution)
+ >>> sourcepackagename = factory.makeSourcePackageName(name='bonkers')
+ >>> package = factory.makeSourcePackage(
+ ... sourcepackagename=sourcepackagename, distroseries=distroseries)
+ >>> component = factory.makeComponent('multiverse')
+ >>> das = factory.makeDistroArchSeries(distroseries=distroseries)
+ >>> spph = factory.makeSourcePackagePublishingHistory(
+ ... sourcepackagename=sourcepackagename, distroseries=distroseries,
+ ... component=component)
+
+ # The setup below this point is needed to fill in the
+ # SourcePackageName.summary, which is assembled from the
+ # BinaryPackageRelease.summary info stored in the
+ # DistroSeriesPackageCache.summary.
+ >>> build = factory.makeBinaryPackageBuild(
+ ... source_package_release=spph.sourcepackagerelease,
+ ... distroarchseries=das)
+ >>> bpr = factory.makeBinaryPackageRelease(
+ ... binarypackagename=factory.makeBinaryPackageName('flubber'),
+ ... summary='floating rubber summary',
+ ... build=build, component=component)
+ >>> bpph = factory.makeBinaryPackagePublishingHistory(
+ ... binarypackagerelease=bpr, distroarchseries=das)
+ >>> class TestLogger:
+ ... def debug(self, msg):
+ ... print 'DEBUG:', msg
+ >>> import transaction
+ >>> from canonical.config import config
+ >>> from canonical.testing.layers import reconnect_stores
+ >>> transaction.commit()
+
+ # XXX: EdwinGrubbs 2010-08-04 bug=396419. Currently there is no
+ # test api call to switchDbUser that works for non-zopeless layers.
+ # When bug 396419 is fixed, we can instead use
+ # DatabaseLayer.switchDbUser() instead of reconnect_stores()
+ >>> reconnect_stores(config.statistician.dbuser)
+ >>> from zope.component import getUtility
+ >>> from lp.registry.interfaces.distroseries import IDistroSeriesSet
+ >>> distroseries = getUtility(IDistroSeriesSet).get(distroseries.id)
+ >>> distroseries.updateCompletePackageCache(
+ ... archive=distroseries.distribution.main_archive,
+ ... ztm=transaction,
+ ... log=TestLogger())
+ DEBUG: Considering binary 'flubber'...
+ >>> transaction.commit()
+ >>> reconnect_stores('launchpad')
+ >>> logout()
No Privileges Person visit the distroseries upstream links page for Hoary
and sees that pmount is not linked.
@@ -20,6 +75,7 @@
match for this source package. Can you help us find one?
Registered upstream project:
Choose another upstream project
+ Register the upstream project
No Privileges Person knows that the pmount package comes from the thunderbird
project. He sets the upstream packaging link and sees that it is set.
@@ -58,3 +114,77 @@
... user_browser.contents, 'packages_list'))
The Hoary Hedgehog Release (active development) ...
0.1-2 release (main) ... weeks ago
+
+Register a project from a source package
+----------------------------------------
+
+If an upstream project doesn't already exist in Launchpad, it can
+be registered with data from the source package prefilling the first
+step of the multistep form.
+
+ >>> user_browser.open(
+ ... 'http://launchpad.dev/youbuntu/busy/+source/bonkers')
+ >>> user_browser.getControl(
+ ... 'Register the upstream project').selected = True
+ >>> user_browser.getControl("Link to Upstream Project").click()
+ >>> print user_browser.url.replace('&', '\n&')
+ http://launchpad.dev/projects/+new?_return_url=http...%2Bindex
+ &field.__visited_steps__=projectaddstep1
+ &field.actions.continue=Continue
+ &field.displayname=Bonkers
+ &field.name=bonkers
+ &field.summary=flubber%3A+floating+rubber+summary
+ &field.title=Bonkers
+ >>> print user_browser.getControl(name='field.name').value
+ bonkers
+ >>> print user_browser.getControl(name='field.displayname').value
+ Bonkers
+ >>> print user_browser.getControl(name='field.title').value
+ Bonkers
+ >>> print user_browser.getControl(name='field.summary').value
+ flubber: floating rubber summary
+ >>> print extract_text(
+ ... find_tag_by_id(user_browser.contents, 'step-title'))
+ Step 2 (of 2): Check for duplicate projects
+
+If the user selects "Choose another upstream project" and then finds out
+that the project doesn't exist, there is a also a link on the
++edit-packaging page to register the project.
+
+ >>> user_browser.open(
+ ... 'http://launchpad.dev/youbuntu/busy/+source/bonkers/')
+ >>> user_browser.getControl(
+ ... 'Choose another upstream project').selected = True
+ >>> user_browser.getControl("Link to Upstream Project").click()
+ >>> print user_browser.url
+ http://launchpad.dev/youbuntu/busy/+source/bonkers/+edit-packaging
+
+ >>> user_browser.getLink("Register the upstream project").click()
+ >>> print user_browser.url.replace('&', '\n&')
+ http://launchpad.dev/projects/+new?_return_url=http...%2Bedit-packaging
+ &field.__visited_steps__=projectaddstep1
+ &field.actions.continue=Continue
+ &field.displayname=Bonkers
+ &field.name=bonkers
+ &field.summary=flubber%3A+floating+rubber+summary
+ &field.title=Bonkers
+ >>> print user_browser.getControl(name='field.name').value
+ bonkers
+ >>> print user_browser.getControl(name='field.displayname').value
+ Bonkers
+ >>> print user_browser.getControl(name='field.title').value
+ Bonkers
+ >>> print user_browser.getControl(name='field.summary').value
+ flubber: floating rubber summary
+ >>> print extract_text(
+ ... find_tag_by_id(user_browser.contents, 'step-title'))
+ Step 2 (of 2): Check for duplicate projects
+
+If there are no problems with the prefilled data, then the license
+just needs to be selected. The user will then be redirected back
+to the source package page so that it can be linked.
+
+ >>> user_browser.getControl(name='field.licenses').value = ['BSD']
+ >>> user_browser.getControl("Complete Registration").click()
+ >>> print user_browser.url
+ http://launchpad.dev/youbuntu/busy/+source/bonkers/+edit-packaging
=== modified file 'lib/lp/registry/templates/sourcepackage-edit-packaging.pt'
--- lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2010-02-16 17:37:36 +0000
+++ lib/lp/registry/templates/sourcepackage-edit-packaging.pt 2010-08-05 16:17:58 +0000
@@ -27,6 +27,26 @@
If you need a new series created, contact the owner of
<a tal:content="structure view/product/fmt:link"/>.
</div>
+
+ <div metal:fill-slot="buttons">
+ <input tal:repeat="action view/actions"
+ tal:replace="structure action/render"
+ />
+ or
+ <tal:comment condition="nothing">
+ This template is for a multistep view, and only the first
+ step provides the register_upstream_url.
+ </tal:comment>
+ <a id="register-upstream-link"
+ tal:condition="view/register_upstream_url | nothing"
+ tal:attributes="href view/register_upstream_url">
+ Register the upstream project
+ </a>
+ <tal:has-cancel-link condition="view/cancel_url">
+ or
+ <a tal:attributes="href view/cancel_url">Cancel</a>
+ </tal:has-cancel-link>
+ </div>
</div>
</div>
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-08-04 00:47:20 +0000
+++ lib/lp/testing/factory.py 2010-08-05 16:17:58 +0000
@@ -2520,6 +2520,7 @@
section_name=section_name,
priority=priority)
+<<<<<<< TREE
bpph = getUtility(IPublishingSet).newBinaryPublication(
archive, binarypackagerelease, distroarchseries,
binarypackagerelease.component, binarypackagerelease.section,
@@ -2530,6 +2531,19 @@
naked_bpph.scheduleddeletiondate = scheduleddeletiondate
naked_bpph.priority = priority
return bpph
+=======
+ return BinaryPackagePublishingHistory(
+ distroarchseries=distroarchseries,
+ binarypackagerelease=binarypackagerelease,
+ component=binarypackagerelease.component,
+ section=binarypackagerelease.section,
+ status=status,
+ dateremoved=dateremoved,
+ scheduleddeletiondate=scheduleddeletiondate,
+ pocket=pocket,
+ priority=priority,
+ archive=archive)
+>>>>>>> MERGE-SOURCE
def makeBinaryPackageName(self, name=None):
if name is None: