Merge lp:~edwin-grubbs/launchpad/bug-602385-register-project-from-sourcepackage-page into lp:launchpad

Proposed by Edwin Grubbs
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: 11326
Proposed branch: lp:~edwin-grubbs/launchpad/bug-602385-register-project-from-sourcepackage-page
Merge into: lp:launchpad
Diff against target: 695 lines (+401/-13)
10 files modified
lib/canonical/launchpad/browser/multistep.py (+19/-0)
lib/canonical/launchpad/webapp/launchpadform.py (+3/-0)
lib/lp/registry/browser/product.py (+30/-3)
lib/lp/registry/browser/sourcepackage.py (+47/-4)
lib/lp/registry/browser/tests/sourcepackage-views.txt (+14/-3)
lib/lp/registry/browser/tests/test_sourcepackage_views.py (+114/-0)
lib/lp/registry/model/sourcepackage.py (+0/-2)
lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt (+89/-1)
lib/lp/registry/templates/sourcepackage-edit-packaging.pt (+20/-0)
lib/lp/soyuz/tests/test_publishing.py (+65/-0)
To merge this branch: bzr merge lp:~edwin-grubbs/launchpad/bug-602385-register-project-from-sourcepackage-page
Reviewer Review Type Date Requested Status
Jelmer Vernooij (community) Approve
Registry Administrators Pending
Review via email: mp+31856@code.launchpad.net

Description of the change

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.

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

It would be nice to have some tests for get_register_upstream_url() - making sure dashes are converted properly, etc.

get_register_upstream_url() handles the case of source_package.summary being None - when does that situation occur?

Since it's likely that the user doesn't want to create a project for their own page this way, it might be a good idea to enable the "I do not want to maintain this project" checkbox when registering a project this way.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

The summary seems to be "<packagename>: summary" now, should the packagename perhaps be stripped out ?

It would also be nice to fill in the description field from the multi-line Debian description, or perhaps that is something that can be addressed later.

Revision history for this message
Jelmer Vernooij (jelmer) :
review: Needs Information (code)
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Hi Jelmer,

Thanks for the review.

> It would be nice to have some tests for get_register_upstream_url() - making
> sure dashes are converted properly, etc.

The method uses urllib.urlencode(), which is reliable at encoding url parameters. I can add a unit test for that if you still want one, but we generally haven't tested that.

> get_register_upstream_url() handles the case of source_package.summary being
> None - when does that situation occur?

The SourcePackage.summary is actually assembled by getting a list of the binary packages for the DistributionSourcePackageReleases. If there are no releases, the summary is None.

> Since it's likely that the user doesn't want to create a project for their own
> page this way, it might be a good idea to enable the "I do not want to
> maintain this project" checkbox when registering a project this way.

This was commented on in bug 602385, and we thought that the user should make the decision not to own a project explicitly.

> The summary seems to be "<packagename>: summary" now, should the packagename
> perhaps be stripped out ?

The summary is definitely the part that I'm least happy about. The source package may or may not have any summary info depending on whether it has binary packages. If there are multiple binary packages, it will be even uglier, looking like this:
  <packagename>: summary
  <packagename>: summary2

Do you think I could stop populating the summary this way and just make the user enter it?

> It would also be nice to fill in the description field from the multi-line
> Debian description, or perhaps that is something that can be addressed later.

I think this would have to be done in a follow up branch. As bug 602385 indicates, it's also desired to automatically connect the source package to the new project instead of just redirecting back to the source package. Do you think the Debian description or some other field would be good for filling in the summary field also?

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

On Thu, 2010-08-05 at 18:04 +0000, Edwin Grubbs wrote:
> Thanks for the review.
>
> > It would be nice to have some tests for get_register_upstream_url() - making
> > sure dashes are converted properly, etc.

> The method uses urllib.urlencode(), which is reliable at encoding url parameters. I can add a unit test for that if you still want one, but we generally haven't tested that.
I'm not concerned about the URL encoding, but about e.g. replacing
dashes in the source package name with spaces in the project title (as
is done on the first line of get_register_upstream_url), especially
since this function might get more complex in the future as more fields
are pre-filled-in.

> > Since it's likely that the user doesn't want to create a project for their own
> > page this way, it might be a good idea to enable the "I do not want to
> > maintain this project" checkbox when registering a project this way.
> This was commented on in bug 602385, and we thought that the user should make the decision not to own a project explicitly.
Fair enough.

> > The summary seems to be "<packagename>: summary" now, should the packagename
> > perhaps be stripped out ?
> The summary is definitely the part that I'm least happy about. The source package may or may not have any summary info depending on whether it has binary packages. If there are multiple binary packages, it will be even uglier, looking like this:
> <packagename>: summary
> <packagename>: summary2
> Do you think I could stop populating the summary this way and just make the user enter it?
I hadn't considered that we are basing the new project on a source
package and not on a binary package. As an optimization, I think it
would still be nice to strip out the "packagename:" bit if there is only
a single binary package for the source package, but I don't see a good
alternative in the other situation.

Certainly, having this field pre-filled-in, even when there are multiple
binary packages, is better than having an empty field imho.

> > It would also be nice to fill in the description field from the
> multi-line > Debian description, or perhaps that is something that can
> be addressed later. I think this would have to be done in a follow up
> branch. As bug 602385 indicates, it's also desired to automatically
> connect the source package to the new project instead of just
> redirecting back to the source package. Do you think the Debian
> description or some other field would be good for filling in the
> summary field also?
Using the Debian description to fill in the description field would also
be useful, but the same problem as above applies here as well
unfortunately as there can be multiple binary packages, each with their
own description.

Thanks for following up, nice work.

  review approve

Cheers,

Jelmer

review: Approve
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (3.2 KiB)

> On Thu, 2010-08-05 at 18:04 +0000, Edwin Grubbs wrote:
> > Thanks for the review.
> >
> > > It would be nice to have some tests for get_register_upstream_url() -
> making
> > > sure dashes are converted properly, etc.
>
> > The method uses urllib.urlencode(), which is reliable at encoding url
> parameters. I can add a unit test for that if you still want one, but we
> generally haven't tested that.
> I'm not concerned about the URL encoding, but about e.g. replacing
> dashes in the source package name with spaces in the project title (as
> is done on the first line of get_register_upstream_url), especially
> since this function might get more complex in the future as more fields
> are pre-filled-in.

I added tests for get_register_upstream_url().

> > > Since it's likely that the user doesn't want to create a project for their
> own
> > > page this way, it might be a good idea to enable the "I do not want to
> > > maintain this project" checkbox when registering a project this way.
> > This was commented on in bug 602385, and we thought that the user should
> make the decision not to own a project explicitly.
> Fair enough.
>
> > > The summary seems to be "<packagename>: summary" now, should the
> packagename
> > > perhaps be stripped out ?
> > The summary is definitely the part that I'm least happy about. The source
> package may or may not have any summary info depending on whether it has
> binary packages. If there are multiple binary packages, it will be even
> uglier, looking like this:
> > <packagename>: summary
> > <packagename>: summary2
> > Do you think I could stop populating the summary this way and just make the
> user enter it?
> I hadn't considered that we are basing the new project on a source
> package and not on a binary package. As an optimization, I think it
> would still be nice to strip out the "packagename:" bit if there is only
> a single binary package for the source package, but I don't see a good
> alternative in the other situation.
>
> Certainly, having this field pre-filled-in, even when there are multiple
> binary packages, is better than having an empty field imho.

I removed the parts before the colon. I also eliminated duplicates, since it would look really ugly to have a summary like the one from this sourcepackage.

https://edge.launchpad.net/ubuntu/lucid/+source/linux

I added tests for this also.

> > > It would also be nice to fill in the description field from the
> > multi-line > Debian description, or perhaps that is something that can
> > be addressed later. I think this would have to be done in a follow up
> > branch. As bug 602385 indicates, it's also desired to automatically
> > connect the source package to the new project instead of just
> > redirecting back to the source package. Do you think the Debian
> > description or some other field would be good for filling in the
> > summary field also?
> Using the Debian description to fill in the description field would also
> be useful, but the same problem as above applies here as well
> unfortunately as there can be multiple binary packages, each with their
> own description.

That would be nice. I'll look into it and open a bug ...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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-09 01:33:51 +0000
@@ -93,6 +93,14 @@
93 view.total_steps = self.total_steps93 view.total_steps = self.total_steps
94 view.is_step = self.getIsStepDict()94 view.is_step = self.getIsStepDict()
95 self.step_number += 195 self.step_number += 1
96
97 action_required = None
98 for name in self.request.form.keys():
99 if name.startswith('field.actions.'):
100 action_required = (name, self.request.form[name])
101 break
102
103 action_taken = view.action_taken
96 while view.next_step is not None:104 while view.next_step is not None:
97 view = view.next_step(self.context, self.request)105 view = view.next_step(self.context, self.request)
98 assert isinstance(view, StepView), 'Not a StepView: %s' % view106 assert isinstance(view, StepView), 'Not a StepView: %s' % view
@@ -102,8 +110,19 @@
102 view.is_step = self.getIsStepDict()110 view.is_step = self.getIsStepDict()
103 self.step_number += 1111 self.step_number += 1
104 view.injectStepNameInRequest()112 view.injectStepNameInRequest()
113 if view.action_taken is not None:
114 action_taken = view.action_taken
115
105 self.view = view116 self.view = view
106117
118 if action_required is not None and action_taken is None:
119 # This is mostly useful for catching tests that pass
120 # in invalid form data via a dictionary instead of
121 # using a test browser.
122 raise AssertionError(
123 'MultiStepView did not find action for %s=%r'
124 % action_required)
125
107 def render(self):126 def render(self):
108 return self.view.render()127 return self.view.render()
109128
110129
=== 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-09 01:33:51 +0000
@@ -74,6 +74,8 @@
7474
75 actions = ()75 actions = ()
7676
77 action_taken = None
78
77 render_context = False79 render_context = False
7880
79 form_result = None81 form_result = None
@@ -112,6 +114,7 @@
112 self.form_result = action.success(data)114 self.form_result = action.success(data)
113 if self.next_url:115 if self.next_url:
114 self.request.response.redirect(self.next_url)116 self.request.response.redirect(self.next_url)
117 self.action_taken = action
115118
116 def render(self):119 def render(self):
117 """Return the body of the response.120 """Return the body of the response.
118121
=== 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-09 01:33:51 +0000
@@ -120,7 +120,7 @@
120from canonical.launchpad.webapp.breadcrumb import Breadcrumb120from canonical.launchpad.webapp.breadcrumb import Breadcrumb
121from canonical.launchpad.webapp.launchpadform import (121from canonical.launchpad.webapp.launchpadform import (
122 action, custom_widget, LaunchpadEditFormView, LaunchpadFormView,122 action, custom_widget, LaunchpadEditFormView, LaunchpadFormView,
123 ReturnToReferrerMixin)123 ReturnToReferrerMixin, safe_action)
124from canonical.launchpad.webapp.menu import NavigationMenu124from canonical.launchpad.webapp.menu import NavigationMenu
125from canonical.launchpad.webapp.tales import MenuAPI125from canonical.launchpad.webapp.tales import MenuAPI
126from canonical.widgets.popup import PersonPickerWidget126from canonical.widgets.popup import PersonPickerWidget
@@ -1850,6 +1850,16 @@
1850 search_results_count = 01850 search_results_count = 0
18511851
1852 @property1852 @property
1853 def _return_url(self):
1854 """This view is using the hidden _return_url field.
1855
1856 It is not using the `ReturnToReferrerMixin`, since none
1857 of its other code is used, because multistep views can't
1858 have next_url set until the form submission succeeds.
1859 """
1860 return self.request.form.get('_return_url')
1861
1862 @property
1853 def _next_step(self):1863 def _next_step(self):
1854 """Define the next step.1864 """Define the next step.
18551865
@@ -1866,6 +1876,10 @@
1866 self.request.form['name'] = data['name'].lower()1876 self.request.form['name'] = data['name'].lower()
1867 self.request.form['summary'] = data['summary']1877 self.request.form['summary'] = data['summary']
18681878
1879 # Make this a safe_action, so that the sourcepackage page can skip
1880 # the first step with a link (GET request) providing form values.
1881 continue_action = safe_action(StepView.continue_action)
1882
18691883
1870class ProjectAddStepTwo(StepView, ProductLicenseMixin, ReturnToReferrerMixin):1884class ProjectAddStepTwo(StepView, ProductLicenseMixin, ReturnToReferrerMixin):
1871 """Step 2 (of 2) in the +new project add wizard."""1885 """Step 2 (of 2) in the +new project add wizard."""
@@ -1887,6 +1901,16 @@
1887 custom_widget('license_info', GhostWidget)1901 custom_widget('license_info', GhostWidget)
18881902
1889 @property1903 @property
1904 def _return_url(self):
1905 """This view is using the hidden _return_url field.
1906
1907 It is not using the `ReturnToReferrerMixin`, since none
1908 of its other code is used, because multistep views can't
1909 have next_url set until the form submission succeeds.
1910 """
1911 return self.request.form.get('_return_url')
1912
1913 @property
1890 def step_description(self):1914 def step_description(self):
1891 """See `MultiStepView`."""1915 """See `MultiStepView`."""
1892 if self.search_results_count > 0:1916 if self.search_results_count > 0:
@@ -2001,7 +2025,10 @@
2001 self.product = self.create_product(data)2025 self.product = self.create_product(data)
2002 self.notifyCommercialMailingList()2026 self.notifyCommercialMailingList()
2003 notify(ObjectCreatedEvent(self.product))2027 notify(ObjectCreatedEvent(self.product))
2004 self.next_url = canonical_url(self.product)2028 if self._return_url is None:
2029 self.next_url = canonical_url(self.product)
2030 else:
2031 self.next_url = self._return_url
20052032
20062033
2007class ProductAddView(MultiStepView):2034class ProductAddView(MultiStepView):
@@ -2027,7 +2054,7 @@
20272054
2028 driver = copy_field(IProduct['driver'])2055 driver = copy_field(IProduct['driver'])
20292056
2030 transfer_to_registry = Bool(2057 transfer_to_registry = Bool(
2031 title=_("I do not want to maintain this project"),2058 title=_("I do not want to maintain this project"),
2032 required=False,2059 required=False,
2033 description=_(2060 description=_(
20342061
=== 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-09 01:33:51 +0000
@@ -19,6 +19,8 @@
1919
20from apt_pkg import ParseSrcDepends20from apt_pkg import ParseSrcDepends
21from cgi import escape21from cgi import escape
22import string
23import urllib
22from z3c.ptcompat import ViewPageTemplateFile24from z3c.ptcompat import ViewPageTemplateFile
23from zope.app.form.browser import DropdownWidget25from zope.app.form.browser import DropdownWidget
24from zope.app.form.interfaces import IInputWidget26from zope.app.form.interfaces import IInputWidget
@@ -35,12 +37,14 @@
3537
36from canonical.launchpad import helpers38from canonical.launchpad import helpers
37from canonical.launchpad.browser.multistep import MultiStepView, StepView39from canonical.launchpad.browser.multistep import MultiStepView, StepView
40
38from lp.bugs.browser.bugtask import BugTargetTraversalMixin41from lp.bugs.browser.bugtask import BugTargetTraversalMixin
39from canonical.launchpad.browser.packagerelationship import (42from canonical.launchpad.browser.packagerelationship import (
40 relationship_builder)43 relationship_builder)
41from lp.answers.browser.questiontarget import (44from lp.answers.browser.questiontarget import (
42 QuestionTargetFacetMixin, QuestionTargetAnswersMenu)45 QuestionTargetFacetMixin, QuestionTargetAnswersMenu)
43from lp.services.worlddata.interfaces.country import ICountry46from lp.services.worlddata.interfaces.country import ICountry
47from lp.registry.browser.product import ProjectAddStepOne
44from lp.registry.interfaces.packaging import IPackaging, IPackagingUtil48from lp.registry.interfaces.packaging import IPackaging, IPackagingUtil
45from lp.registry.interfaces.pocket import PackagePublishingPocket49from lp.registry.interfaces.pocket import PackagePublishingPocket
46from lp.registry.interfaces.product import IProductSet50from lp.registry.interfaces.product import IProductSet
@@ -62,6 +66,30 @@
62from canonical.lazr.utils import smartquote66from canonical.lazr.utils import smartquote
6367
6468
69def get_register_upstream_url(source_package, return_url):
70 displayname = string.capwords(source_package.name.replace('-', ' '))
71 params = {
72 '_return_url': return_url,
73 'field.name': source_package.name,
74 'field.displayname': displayname,
75 'field.title': displayname,
76 'field.__visited_steps__': ProjectAddStepOne.step_name,
77 'field.actions.continue': 'Continue',
78 }
79 if len(source_package.releases) == 0:
80 params['field.summary'] = ''
81 else:
82 # This is based on the SourcePackageName.summary attribute, but
83 # it eliminates the binary.name and duplicate summary lines.
84 summary_set = set()
85 for binary in source_package.releases[0].sample_binary_packages:
86 summary_set.add(binary.summary)
87 params['field.summary'] = '\n'.join(sorted(summary_set))
88 query_string = urllib.urlencode(
89 sorted(params.items()), doseq=True)
90 return '/projects/+new?%s' % query_string
91
92
65class SourcePackageNavigation(GetitemNavigation, BugTargetTraversalMixin):93class SourcePackageNavigation(GetitemNavigation, BugTargetTraversalMixin):
6694
67 usedfor = ISourcePackage95 usedfor = ISourcePackage
@@ -190,6 +218,11 @@
190 self.next_step = SourcePackageChangeUpstreamStepTwo218 self.next_step = SourcePackageChangeUpstreamStepTwo
191 self.request.form['product'] = data['product']219 self.request.form['product'] = data['product']
192220
221 @property
222 def register_upstream_url(self):
223 return get_register_upstream_url(
224 self.context, return_url=self.request.getURL())
225
193226
194class SourcePackageChangeUpstreamStepTwo(ReturnToReferrerMixin, StepView):227class SourcePackageChangeUpstreamStepTwo(ReturnToReferrerMixin, StepView):
195 """A view to set the `IProductSeries` of a sourcepackage."""228 """A view to set the `IProductSeries` of a sourcepackage."""
@@ -345,7 +378,7 @@
345 def processForm(self):378 def processForm(self):
346 # look for an update to any of the things we track379 # look for an update to any of the things we track
347 form = self.request.form380 form = self.request.form
348 if form.has_key('packaging'):381 if 'packaging' in form:
349 if self.productseries_widget.hasValidInput():382 if self.productseries_widget.hasValidInput():
350 new_ps = self.productseries_widget.getInputValue()383 new_ps = self.productseries_widget.getInputValue()
351 # we need to create or update the packaging384 # we need to create or update the packaging
@@ -445,6 +478,7 @@
445 initial_focus_widget = None478 initial_focus_widget = None
446 max_suggestions = 9479 max_suggestions = 9
447 other_upstream = object()480 other_upstream = object()
481 register_upstream = object()
448482
449 def setUpFields(self):483 def setUpFields(self):
450 """See `LaunchpadFormView`."""484 """See `LaunchpadFormView`."""
@@ -467,9 +501,12 @@
467 vocab_terms.append(SimpleTerm(product, product.name, description))501 vocab_terms.append(SimpleTerm(product, product.name, description))
468 # Add an option to represent the user's decision to choose a502 # Add an option to represent the user's decision to choose a
469 # different project. Note that project names cannot be uppercase.503 # different project. Note that project names cannot be uppercase.
470 description = 'Choose another upstream project'504 vocab_terms.append(
471 vocab_terms.append(505 SimpleTerm(self.other_upstream, 'OTHER_UPSTREAM',
472 SimpleTerm(self.other_upstream, 'OTHER_UPSTREAM', description))506 'Choose another upstream project'))
507 vocab_terms.append(
508 SimpleTerm(self.register_upstream, 'REGISTER_UPSTREAM',
509 'Register the upstream project'))
473 upstream_vocabulary = SimpleVocabulary(vocab_terms)510 upstream_vocabulary = SimpleVocabulary(vocab_terms)
474511
475 self.form_fields = Fields(512 self.form_fields = Fields(
@@ -487,6 +524,12 @@
487 self.next_url = canonical_url(524 self.next_url = canonical_url(
488 self.context, view_name="+edit-packaging")525 self.context, view_name="+edit-packaging")
489 return526 return
527 elif upstream is self.register_upstream:
528 # The user wants to create a new project.
529 url = get_register_upstream_url(
530 self.context, return_url=self.request.getURL())
531 self.request.response.redirect(url)
532 return
490 self.context.setPackaging(upstream.development_focus, self.user)533 self.context.setPackaging(upstream.development_focus, self.user)
491 self.request.response.addInfoNotification(534 self.request.response.addInfoNotification(
492 'The project %s was linked to this source package.' %535 'The project %s was linked to this source package.' %
493536
=== 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-09 01:33:51 +0000
@@ -119,6 +119,7 @@
119empty.119empty.
120120
121 >>> form = {121 >>> form = {
122 ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step1',
122 ... 'field.product': '',123 ... 'field.product': '',
123 ... 'field.actions.continue': 'Continue',124 ... 'field.actions.continue': 'Continue',
124 ... }125 ... }
@@ -133,12 +134,18 @@
133but there is no notification message that the upstream link was updated.134but there is no notification message that the upstream link was updated.
134135
135 >>> form = {136 >>> form = {
136 ... 'field.productseries': 'bonkers/crazy',137 ... 'field.__visited_steps__': 'sourcepackage_change_upstream_step2',
137 ... 'field.actions.change': 'Change',138 ... 'field.product': 'bonkers',
139 ... 'field.productseries': 'crazy',
140 ... 'field.actions.continue': 'Continue',
138 ... }141 ... }
139 >>> view = create_initialized_view(142 >>> view = create_initialized_view(
140 ... package, name='+edit-packaging', form=form,143 ... package, name='+edit-packaging', form=form,
141 ... principal=product.owner)144 ... principal=product.owner)
145 >>> print view.view
146 <...SourcePackageChangeUpstreamStepTwo object...>
147 >>> print view.view.next_url
148 http://launchpad.dev/youbuntu/busy/+source/bonkers
142 >>> view.view.errors149 >>> view.view.errors
143 []150 []
144151
@@ -199,6 +206,7 @@
199 Registered upstream project:206 Registered upstream project:
200 Lernid207 Lernid
201 Choose another upstream project208 Choose another upstream project
209 Register the upstream project
202210
203The form does not steal focus because it is not the primary purpose of the211The form does not steal focus because it is not the primary purpose of the
204page.212page.
@@ -229,6 +237,7 @@
229 Lernid...237 Lernid...
230 Lernid Dev...238 Lernid Dev...
231 Choose another upstream project239 Choose another upstream project
240 Register the upstream project
232241
233Choosing the "Choose another upstream project" option redirects the user242Choosing the "Choose another upstream project" option redirects the user
234to the +edit-packaging page where the user can search for a project.243to the +edit-packaging page where the user can search for a project.
@@ -259,7 +268,8 @@
259 ... name='stinkyseries', product=product)268 ... name='stinkyseries', product=product)
260 >>> distroseries = factory.makeDistroRelease(name='wonky',269 >>> distroseries = factory.makeDistroRelease(name='wonky',
261 ... distribution=distribution)270 ... distribution=distribution)
262 >>> sourcepackagename = factory.makeSourcePackageName(name='stinkypackage')271 >>> sourcepackagename = factory.makeSourcePackageName(
272 ... name='stinkypackage')
263 >>> package = factory.makeSourcePackage(273 >>> package = factory.makeSourcePackage(
264 ... sourcepackagename=sourcepackagename, distroseries=distroseries)274 ... sourcepackagename=sourcepackagename, distroseries=distroseries)
265275
@@ -360,3 +370,4 @@
360 match for this source package. Can you help us find one?370 match for this source package. Can you help us find one?
361 Registered upstream project:371 Registered upstream project:
362 Choose another upstream project372 Choose another upstream project
373 Register the upstream project
363374
=== added file 'lib/lp/registry/browser/tests/test_sourcepackage_views.py'
--- lib/lp/registry/browser/tests/test_sourcepackage_views.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/test_sourcepackage_views.py 2010-08-09 01:33:51 +0000
@@ -0,0 +1,114 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for SourcePackage view code."""
5
6__metaclass__ = type
7
8import cgi
9import urllib
10
11from zope.component import getUtility
12
13from canonical.testing import DatabaseFunctionalLayer
14
15from lp.registry.browser.sourcepackage import get_register_upstream_url
16from lp.registry.interfaces.distroseries import IDistroSeriesSet
17from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
18from lp.testing import TestCaseWithFactory
19
20
21class TestSourcePackageViewHelpers(TestCaseWithFactory):
22 """Tests for SourcePackage view helper functions."""
23
24 layer = DatabaseFunctionalLayer
25
26 def test_get_register_upstream_url_displayname(self):
27 source_package = self.factory.makeSourcePackage(
28 sourcepackagename='python-super-package')
29 return_url = 'http://example.com/foo?a=b&c=d'
30 url = get_register_upstream_url(source_package, return_url)
31 expected_url = (
32 '/projects/+new?'
33 '_return_url='
34 'http%3A%2F%2Fexample.com%2Ffoo%3Fa%3Db%26c%3Dd'
35 '&field.__visited_steps__=projectaddstep1'
36 '&field.actions.continue=Continue'
37 # The sourcepackagename 'python-super-package' is split on
38 # the hyphens, and each word is capitalized.
39 '&field.displayname=Python+Super+Package'
40 '&field.name=python-super-package'
41 # The summary is empty, since the source package doesn't
42 # have a binary package release.
43 '&field.summary='
44 '&field.title=Python+Super+Package')
45 self.assertEqual(expected_url, url)
46
47 def test_get_register_upstream_url_summary(self):
48 test_publisher = SoyuzTestPublisher()
49 test_data = test_publisher.makeSourcePackageWithBinaryPackageRelease()
50 source_package_name = (
51 test_data['source_package'].sourcepackagename.name)
52 distroseries_id = test_data['distroseries'].id
53 test_publisher.updateDistroSeriesPackageCache(
54 test_data['distroseries'])
55
56 # updateDistroSeriesPackageCache reconnects the db, so the
57 # objects need to be reloaded.
58 distroseries = getUtility(IDistroSeriesSet).get(distroseries_id)
59 source_package = distroseries.getSourcePackage(source_package_name)
60 return_url = 'http://example.com/foo?a=b&c=d'
61 url = get_register_upstream_url(source_package, return_url)
62 expected_base = '/projects/+new'
63 expected_params = [
64 ('_return_url', 'http://example.com/foo?a=b&c=d'),
65 ('field.__visited_steps__', 'projectaddstep1'),
66 ('field.actions.continue', 'Continue'),
67 ('field.displayname', 'Bonkers'),
68 ('field.name', 'bonkers'),
69 ('field.summary', 'summary for flubber-bin\n'
70 + 'summary for flubber-lib'),
71 ('field.title', 'Bonkers'),
72 ]
73 base, query = urllib.splitquery(url)
74 params = cgi.parse_qsl(query)
75 self.assertEqual((expected_base, expected_params),
76 (base, params))
77
78 def test_get_register_upstream_url_summary_duplicates(self):
79
80 class FakeDistroSeriesBinaryPackage:
81 def __init__(self, summary):
82 self.summary = summary
83
84 class FakeDistributionSourcePackageRelease:
85 sample_binary_packages = [
86 FakeDistroSeriesBinaryPackage('summary for foo'),
87 FakeDistroSeriesBinaryPackage('summary for bar'),
88 FakeDistroSeriesBinaryPackage('summary for baz'),
89 FakeDistroSeriesBinaryPackage('summary for baz'),
90 ]
91
92 class FakeSourcePackage:
93 name = 'foo'
94 releases = [FakeDistributionSourcePackageRelease()]
95
96 source_package = FakeSourcePackage()
97 return_url = 'http://example.com/foo?a=b&c=d'
98 url = get_register_upstream_url(source_package, return_url)
99 expected_base = '/projects/+new'
100 expected_params = [
101 ('_return_url', 'http://example.com/foo?a=b&c=d'),
102 ('field.__visited_steps__', 'projectaddstep1'),
103 ('field.actions.continue', 'Continue'),
104 ('field.displayname', 'Foo'),
105 ('field.name', 'foo'),
106 ('field.summary', 'summary for bar\n'
107 + 'summary for baz\n'
108 + 'summary for foo'),
109 ('field.title', 'Foo'),
110 ]
111 base, query = urllib.splitquery(url)
112 params = cgi.parse_qsl(query)
113 self.assertEqual((expected_base, expected_params),
114 (base, params))
0115
=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py 2010-08-02 21:38:00 +0000
+++ lib/lp/registry/model/sourcepackage.py 2010-08-09 01:33:51 +0000
@@ -39,7 +39,6 @@
39from lp.registry.model.packaging import Packaging39from lp.registry.model.packaging import Packaging
40from lp.translations.model.potemplate import (40from lp.translations.model.potemplate import (
41 HasTranslationTemplatesMixin,41 HasTranslationTemplatesMixin,
42 POTemplate,
43 TranslationTemplatesCollection)42 TranslationTemplatesCollection)
44from canonical.launchpad.interfaces.lpstorm import IStore43from canonical.launchpad.interfaces.lpstorm import IStore
45from lp.soyuz.model.publishing import (44from lp.soyuz.model.publishing import (
@@ -52,7 +51,6 @@
52 SourcePackageRelease)51 SourcePackageRelease)
53from lp.translations.model.translationimportqueue import (52from lp.translations.model.translationimportqueue import (
54 HasTranslationImportsMixin)53 HasTranslationImportsMixin)
55from canonical.launchpad.helpers import shortlist
56from lp.soyuz.interfaces.buildrecords import IHasBuildRecords54from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
57from lp.registry.interfaces.packaging import PackagingType55from lp.registry.interfaces.packaging import PackagingType
58from lp.registry.interfaces.distribution import NoPartnerArchive56from lp.registry.interfaces.distribution import NoPartnerArchive
5957
=== 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-09 01:33:51 +0000
@@ -1,4 +1,15 @@
1= Packaging =1Packaging
2=========
3
4Create test data.
5
6 >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
7 >>> test_publisher = SoyuzTestPublisher()
8 >>> login('admin@canonical.com')
9 >>> test_data = test_publisher.makeSourcePackageWithBinaryPackageRelease()
10 >>> test_publisher.updateDistroSeriesPackageCache(
11 ... test_data['distroseries'])
12 >>> logout()
213
3No Privileges Person visit the distroseries upstream links page for Hoary14No Privileges Person visit the distroseries upstream links page for Hoary
4and sees that pmount is not linked.15and sees that pmount is not linked.
@@ -20,6 +31,7 @@
20 match for this source package. Can you help us find one?31 match for this source package. Can you help us find one?
21 Registered upstream project:32 Registered upstream project:
22 Choose another upstream project33 Choose another upstream project
34 Register the upstream project
2335
24No Privileges Person knows that the pmount package comes from the thunderbird36No Privileges Person knows that the pmount package comes from the thunderbird
25project. He sets the upstream packaging link and sees that it is set.37project. He sets the upstream packaging link and sees that it is set.
@@ -58,3 +70,79 @@
58 ... user_browser.contents, 'packages_list'))70 ... user_browser.contents, 'packages_list'))
59 The Hoary Hedgehog Release (active development) ...71 The Hoary Hedgehog Release (active development) ...
60 0.1-2 release (main) ... weeks ago72 0.1-2 release (main) ... weeks ago
73
74Register a project from a source package
75----------------------------------------
76
77If an upstream project doesn't already exist in Launchpad, it can
78be registered with data from the source package prefilling the first
79step of the multistep form.
80
81 >>> user_browser.open(
82 ... 'http://launchpad.dev/youbuntu/busy/+source/bonkers')
83 >>> user_browser.getControl(
84 ... 'Register the upstream project').selected = True
85 >>> user_browser.getControl("Link to Upstream Project").click()
86 >>> print user_browser.url.replace('&', '\n&')
87 http://launchpad.dev/projects/+new?_return_url=http...%2Bindex
88 &field.__visited_steps__=projectaddstep1
89 &field.actions.continue=Continue
90 &field.displayname=Bonkers
91 &field.name=bonkers
92 &field.summary=summary+for+flubber-bin%0Asummary+for+flubber-lib
93 &field.title=Bonkers
94 >>> print user_browser.getControl(name='field.name').value
95 bonkers
96 >>> print user_browser.getControl(name='field.displayname').value
97 Bonkers
98 >>> print user_browser.getControl(name='field.title').value
99 Bonkers
100 >>> print user_browser.getControl(name='field.summary').value
101 summary for flubber-bin
102 summary for flubber-lib
103 >>> print extract_text(
104 ... find_tag_by_id(user_browser.contents, 'step-title'))
105 Step 2 (of 2): Check for duplicate projects
106
107If the user selects "Choose another upstream project" and then finds out
108that the project doesn't exist, there is a also a link on the
109+edit-packaging page to register the project.
110
111 >>> user_browser.open(
112 ... 'http://launchpad.dev/youbuntu/busy/+source/bonkers/')
113 >>> user_browser.getControl(
114 ... 'Choose another upstream project').selected = True
115 >>> user_browser.getControl("Link to Upstream Project").click()
116 >>> print user_browser.url
117 http://launchpad.dev/youbuntu/busy/+source/bonkers/+edit-packaging
118
119 >>> user_browser.getLink("Register the upstream project").click()
120 >>> print user_browser.url.replace('&', '\n&')
121 http://launchpad.dev/projects/+new?_return_url=http...%2Bedit-packaging
122 &field.__visited_steps__=projectaddstep1
123 &field.actions.continue=Continue
124 &field.displayname=Bonkers
125 &field.name=bonkers
126 &field.summary=summary+for+flubber-bin%0Asummary+for+flubber-lib
127 &field.title=Bonkers
128 >>> print user_browser.getControl(name='field.name').value
129 bonkers
130 >>> print user_browser.getControl(name='field.displayname').value
131 Bonkers
132 >>> print user_browser.getControl(name='field.title').value
133 Bonkers
134 >>> print user_browser.getControl(name='field.summary').value
135 summary for flubber-bin
136 summary for flubber-lib
137 >>> print extract_text(
138 ... find_tag_by_id(user_browser.contents, 'step-title'))
139 Step 2 (of 2): Check for duplicate projects
140
141If there are no problems with the prefilled data, then the license
142just needs to be selected. The user will then be redirected back
143to the source package page so that it can be linked.
144
145 >>> user_browser.getControl(name='field.licenses').value = ['BSD']
146 >>> user_browser.getControl("Complete Registration").click()
147 >>> print user_browser.url
148 http://launchpad.dev/youbuntu/busy/+source/bonkers/+edit-packaging
61149
=== 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-09 01:33:51 +0000
@@ -27,6 +27,26 @@
27 If you need a new series created, contact the owner of27 If you need a new series created, contact the owner of
28 <a tal:content="structure view/product/fmt:link"/>.28 <a tal:content="structure view/product/fmt:link"/>.
29 </div>29 </div>
30
31 <div metal:fill-slot="buttons">
32 <input tal:repeat="action view/actions"
33 tal:replace="structure action/render"
34 />
35 &nbsp;or&nbsp;
36 <tal:comment condition="nothing">
37 This template is for a multistep view, and only the first
38 step provides the register_upstream_url.
39 </tal:comment>
40 <a id="register-upstream-link"
41 tal:condition="view/register_upstream_url | nothing"
42 tal:attributes="href view/register_upstream_url">
43 Register the upstream project
44 </a>
45 <tal:has-cancel-link condition="view/cancel_url">
46 &nbsp;or&nbsp;
47 <a tal:attributes="href view/cancel_url">Cancel</a>
48 </tal:has-cancel-link>
49 </div>
30 </div>50 </div>
3151
32</div>52</div>
3353
=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py 2010-08-05 17:11:43 +0000
+++ lib/lp/soyuz/tests/test_publishing.py 2010-08-09 01:33:51 +0000
@@ -10,6 +10,7 @@
10from StringIO import StringIO10from StringIO import StringIO
11import tempfile11import tempfile
1212
13import transaction
13import pytz14import pytz
14from zope.component import getUtility15from zope.component import getUtility
15from zope.security.proxy import removeSecurityProxy16from zope.security.proxy import removeSecurityProxy
@@ -18,13 +19,16 @@
18from canonical.database.constants import UTC_NOW19from canonical.database.constants import UTC_NOW
19from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet20from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
20from canonical.launchpad.webapp.errorlog import ErrorReportingUtility21from canonical.launchpad.webapp.errorlog import ErrorReportingUtility
22from canonical.testing.layers import reconnect_stores
21from canonical.testing import (23from canonical.testing import (
22 DatabaseFunctionalLayer, LaunchpadZopelessLayer)24 DatabaseFunctionalLayer, LaunchpadZopelessLayer)
25
23from lp.app.errors import NotFoundError26from lp.app.errors import NotFoundError
24from lp.archivepublisher.config import Config27from lp.archivepublisher.config import Config
25from lp.archivepublisher.diskpool import DiskPool28from lp.archivepublisher.diskpool import DiskPool
26from lp.buildmaster.interfaces.buildbase import BuildStatus29from lp.buildmaster.interfaces.buildbase import BuildStatus
27from lp.registry.interfaces.distribution import IDistributionSet30from lp.registry.interfaces.distribution import IDistributionSet
31from lp.registry.interfaces.distroseries import IDistroSeriesSet
28from lp.registry.interfaces.person import IPersonSet32from lp.registry.interfaces.person import IPersonSet
29from lp.registry.interfaces.pocket import PackagePublishingPocket33from lp.registry.interfaces.pocket import PackagePublishingPocket
30from lp.registry.interfaces.sourcepackage import SourcePackageUrgency34from lp.registry.interfaces.sourcepackage import SourcePackageUrgency
@@ -460,6 +464,67 @@
460464
461 return source465 return source
462466
467 def makeSourcePackageWithBinaryPackageRelease(self):
468 """Make test data for SourcePackage.summary.
469
470 The distroseries that is returned from this method needs to be
471 passed into updateDistroseriesPackageCache() so that
472 SourcePackage.summary can be populated.
473 """
474 distribution = self.factory.makeDistribution(
475 name='youbuntu', displayname='Youbuntu')
476 distroseries = self.factory.makeDistroRelease(name='busy',
477 distribution=distribution)
478 source_package_name = self.factory.makeSourcePackageName(
479 name='bonkers')
480 source_package = self.factory.makeSourcePackage(
481 sourcepackagename=source_package_name,
482 distroseries=distroseries)
483 component = self.factory.makeComponent('multiverse')
484 das = self.factory.makeDistroArchSeries(
485 distroseries=distroseries)
486 spph = self.factory.makeSourcePackagePublishingHistory(
487 sourcepackagename=source_package_name,
488 distroseries=distroseries,
489 component=component)
490
491 for name in ('flubber-bin', 'flubber-lib'):
492 binary_package_name = self.factory.makeBinaryPackageName(name)
493 build = self.factory.makeBinaryPackageBuild(
494 source_package_release=spph.sourcepackagerelease,
495 archive=self.factory.makeArchive(),
496 distroarchseries=das)
497 bpr = self.factory.makeBinaryPackageRelease(
498 binarypackagename=binary_package_name,
499 summary='summary for %s' % name,
500 build=build, component=component)
501 bpph = self.factory.makeBinaryPackagePublishingHistory(
502 binarypackagerelease=bpr, distroarchseries=das)
503 return dict(
504 distroseries=distroseries,
505 source_package=source_package)
506
507 def updateDistroSeriesPackageCache(
508 self, distroseries, restore_db_connection='launchpad'):
509 # XXX: EdwinGrubbs 2010-08-04 bug=396419. Currently there is no
510 # test api call to switchDbUser that works for non-zopeless layers.
511 # When bug 396419 is fixed, we can instead use
512 # DatabaseLayer.switchDbUser() instead of reconnect_stores()
513 transaction.commit()
514 reconnect_stores(config.statistician.dbuser)
515 distroseries = getUtility(IDistroSeriesSet).get(distroseries.id)
516
517 class TestLogger:
518 # Silent logger.
519 def debug(self, msg):
520 pass
521 distroseries.updateCompletePackageCache(
522 archive=distroseries.distribution.main_archive,
523 ztm=transaction,
524 log=TestLogger())
525 transaction.commit()
526 reconnect_stores(restore_db_connection)
527
463528
464class TestNativePublishingBase(TestCaseWithFactory, SoyuzTestPublisher):529class TestNativePublishingBase(TestCaseWithFactory, SoyuzTestPublisher):
465 layer = LaunchpadZopelessLayer530 layer = LaunchpadZopelessLayer