Merge lp:~edwin-grubbs/launchpad/bug-525956-unlink-button into lp:launchpad
- bug-525956-unlink-button
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Curtis Hovey |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~edwin-grubbs/launchpad/bug-525956-unlink-button |
Merge into: | lp:launchpad |
Diff against target: |
1398 lines (+373/-417) 26 files modified
lib/canonical/launchpad/icing/style-3-0.css.in (+1/-1) lib/canonical/launchpad/templates/launchpad-form.pt (+7/-0) lib/canonical/launchpad/webapp/launchpadform.py (+31/-3) lib/lp/registry/browser/configure.zcml (+16/-0) lib/lp/registry/browser/distributionsourcepackage.py (+1/-22) lib/lp/registry/browser/packaging.py (+0/-96) lib/lp/registry/browser/product.py (+11/-48) lib/lp/registry/browser/sourcepackage.py (+48/-15) lib/lp/registry/browser/tests/packaging-views.txt (+41/-46) lib/lp/registry/browser/tests/sourcepackage-views.txt (+52/-6) lib/lp/registry/browser/tests/test_packaging.py (+4/-2) lib/lp/registry/model/productseries.py (+4/-2) lib/lp/registry/model/sourcepackage.py (+5/-4) lib/lp/registry/stories/distroseries/xx-show-distroseries-packaging.txt (+5/-4) lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.txt (+9/-14) lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.txt (+10/-6) lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt (+1/-3) lib/lp/registry/stories/product/xx-product-package-pages.txt (+11/-8) lib/lp/registry/stories/productseries/xx-productseries-delete.txt (+4/-2) lib/lp/registry/templates/distributionsourcepackage-index.pt (+3/-29) lib/lp/registry/templates/distroseries-packaging.pt (+2/-37) lib/lp/registry/templates/product-packages.pt (+8/-19) lib/lp/registry/templates/sourcepackage-portlet-associations.pt (+1/-47) lib/lp/registry/templates/sourcepackage-remove-packaging.pt (+34/-0) lib/lp/registry/templates/sourcepackage-upstream-connections.pt (+63/-0) lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt (+1/-3) |
To merge this branch: | bzr merge lp:~edwin-grubbs/launchpad/bug-525956-unlink-button |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey (community) | code + ui | Approve | |
Eleanor Berger (community) | code ui* | Approve | |
Review via email: mp+20334@code.launchpad.net |
Commit message
Added ability to unlink sourcepackages and project series from the sourcepackage index page.
Description of the change
Edwin Grubbs (edwin-grubbs) wrote : | # |
Eleanor Berger (intellectronica) : | # |
Curtis Hovey (sinzui) wrote : | # |
Hi Edwin.
This this was a nice feature to use on the sp page. I think we should consider using it on the dsp page: https:/
I think we should remove the edit/remove links from the +packaging listing. There is not enough information on this page to make that decision.
Edwin Grubbs (edwin-grubbs) wrote : | # |
> Hi Edwin.
>
> This this was a nice feature to use on the sp page. I think we should consider
> using it on the dsp page: https:/
> have a post back that some people have expressed concern about how easy it is
> to make a mistake. I would not say this is change we must do because the post
> back always returns the user to the dsp page--using the sp as the next_url
> would be confusing.
>
> I think we should remove the edit/remove links from the +packaging listing.
> There is not enough information on this page to make that decision.
This was supposed to be a simple change for the UI review. It has grown
quite large, so I can create a new merge proposal if you would like.
I started off trying to use +remove-packaging page for the
$distrosourcepa
was also used by $product/+packages page, so I changed that page also.
After I got the +remove-packaging page to return to the previous page, I
discovered that the edit link on the $distrosourcepa
returning you to a different page.
All these changes broke a ton of tests.
Pages to check
--------------
The edit and remove buttons for each series are hidden now.
https:/
The remove button now takes you to the $sourcepackage/
and the edit button no
https:/
The remove button now takes you to the $sourcepackage/
https:/
This page shouldn't have changed, but if you want to check that the
show_edit_buttons annotation is passed correctly, you'll need to look at it.
https:/
Implementation details
-------
Removed trailing white-space.
lib/
Added Mixin to enable forms to return to their referring page.
lib/
lib/
Use the ReturnToReferre
Hide the edit/remove buttons by default, but show them in the
annotations portlet, because its view sets
request.
sourcepackage-
it can't share the view with annotations portlet or show_edit_buttons
would always be true.
lib/
lib/
lib/
lib/
Removed PackagingDeleteView and related code.
lib/
lib/
lib/
lib/
lib/
Added error message for when a user loads this page after the
upstream link has been deleted by someone else.
lib/
Tests:
lib/
lib/lp/...
Curtis Hovey (sinzui) wrote : | # |
Hi Edwin.
in retrospect, saying no to updating the DSP page would have been better. On the other hand, I and Adi have wanted a mixin as you have created for sometime. I think the pain was worth it, though it was untimely.
I have one question from the diff. What is "irumpy hotter" in lp/registry/
Edwin Grubbs (edwin-grubbs) wrote : | # |
> I have one question from the diff. What is "irumpy hotter" in
> lp/registry/
> I am not aware of a irumpty test distroseries in the test
Doh. "irumpy" is so nonsensical and undesirable that irumpy.com is available. Fixed.
Preview Diff
1 | === modified file 'lib/canonical/launchpad/icing/style-3-0.css.in' |
2 | --- lib/canonical/launchpad/icing/style-3-0.css.in 2010-02-17 00:43:19 +0000 |
3 | +++ lib/canonical/launchpad/icing/style-3-0.css.in 2010-03-06 06:27:33 +0000 |
4 | @@ -814,7 +814,7 @@ |
5 | } |
6 | input[type="submit"].icon-only { |
7 | vertical-align: middle; |
8 | - border: 0; |
9 | + border: 0; |
10 | padding: 0; |
11 | height: 16px; |
12 | width: 16px; |
13 | |
14 | === modified file 'lib/canonical/launchpad/templates/launchpad-form.pt' |
15 | --- lib/canonical/launchpad/templates/launchpad-form.pt 2009-11-26 09:18:22 +0000 |
16 | +++ lib/canonical/launchpad/templates/launchpad-form.pt 2010-03-06 06:27:33 +0000 |
17 | @@ -16,6 +16,13 @@ |
18 | accept-charset="UTF-8"> |
19 | |
20 | <div metal:define-macro="formbody"> |
21 | + <tal:comment condition="nothing"> |
22 | + This field is used by the ReturnToReferrerMixin. |
23 | + </tal:comment> |
24 | + <input type="hidden" |
25 | + name="_return_url" |
26 | + tal:condition="view/_return_url | nothing" |
27 | + tal:attributes="value view/_return_url" /> |
28 | |
29 | <p metal:define-slot="extra_info" tal:replace="nothing"> |
30 | This is the description of the form. |
31 | |
32 | === modified file 'lib/canonical/launchpad/webapp/launchpadform.py' |
33 | --- lib/canonical/launchpad/webapp/launchpadform.py 2010-02-19 01:50:36 +0000 |
34 | +++ lib/canonical/launchpad/webapp/launchpadform.py 2010-03-06 06:27:33 +0000 |
35 | @@ -7,10 +7,11 @@ |
36 | __metaclass__ = type |
37 | |
38 | __all__ = [ |
39 | - 'LaunchpadFormView', |
40 | - 'LaunchpadEditFormView', |
41 | 'action', |
42 | 'custom_widget', |
43 | + 'LaunchpadEditFormView', |
44 | + 'LaunchpadFormView', |
45 | + 'ReturnToReferrerMixin', |
46 | 'safe_action', |
47 | ] |
48 | |
49 | @@ -32,7 +33,7 @@ |
50 | IMultiLineWidgetLayout, ICheckBoxWidgetLayout, |
51 | IAlwaysSubmittedWidget, UnsafeFormGetSubmissionError) |
52 | from canonical.launchpad.webapp.menu import escape |
53 | -from canonical.launchpad.webapp.publisher import LaunchpadView |
54 | +from canonical.launchpad.webapp.publisher import canonical_url, LaunchpadView |
55 | |
56 | |
57 | classImplements(CheckBoxWidget, ICheckBoxWidgetLayout) |
58 | @@ -434,3 +435,30 @@ |
59 | """ |
60 | action.is_safe = True |
61 | return action |
62 | + |
63 | + |
64 | +class ReturnToReferrerMixin: |
65 | + """Return to the previous page after submitting the form. |
66 | + |
67 | + The _return_url is stored in a hidden field in the launchpad-form.pt |
68 | + between the request to view the form and submitting the form. |
69 | + """ |
70 | + |
71 | + @property |
72 | + def _return_url(self): |
73 | + """See `LaunchpadFormView`.""" |
74 | + # The referer header we want is only available before the view's |
75 | + # form submits to itself. This field is a hidden input in the form. |
76 | + referrer = self.request.form.get('_return_url') |
77 | + if referrer is None: |
78 | + # "referer" is misspelled in the HTTP specification. |
79 | + referrer = self.request.getHeader('referer') |
80 | + |
81 | + if (referrer is not None |
82 | + and referrer.startswith(self.request.getApplicationURL())): |
83 | + return referrer |
84 | + else: |
85 | + return canonical_url(self.context) |
86 | + |
87 | + next_url = _return_url |
88 | + cancel_url = _return_url |
89 | |
90 | === modified file 'lib/lp/registry/browser/configure.zcml' |
91 | --- lib/lp/registry/browser/configure.zcml 2010-03-05 14:18:18 +0000 |
92 | +++ lib/lp/registry/browser/configure.zcml 2010-03-06 06:27:33 +0000 |
93 | @@ -1946,6 +1946,14 @@ |
94 | facet="overview" |
95 | class="lp.registry.browser.sourcepackage.SourcePackageAssociationPortletView" |
96 | template="../templates/sourcepackage-portlet-associations.pt"/> |
97 | + |
98 | + <browser:page |
99 | + for="lp.registry.interfaces.sourcepackage.ISourcePackage" |
100 | + permission="zope.Public" |
101 | + name="+upstream-connections" |
102 | + facet="overview" |
103 | + class="lp.registry.browser.sourcepackage.SourcePackageUpstreamConnectionsView" |
104 | + template="../templates/sourcepackage-upstream-connections.pt"/> |
105 | <browser:page |
106 | for="lp.registry.interfaces.sourcepackage.ISourcePackage" |
107 | permission="zope.Public" |
108 | @@ -2122,6 +2130,14 @@ |
109 | facet="overview" |
110 | template="../templates/sourcepackage-edit-packaging.pt" |
111 | /> |
112 | + <browser:page |
113 | + for="lp.registry.interfaces.sourcepackage.ISourcePackage" |
114 | + permission="launchpad.AnyPerson" |
115 | + class="lp.registry.browser.sourcepackage.SourcePackageRemoveUpstreamView" |
116 | + name="+remove-packaging" |
117 | + facet="overview" |
118 | + template="../templates/sourcepackage-remove-packaging.pt" |
119 | + /> |
120 | |
121 | <browser:page |
122 | for="lp.registry.interfaces.structuralsubscription.IStructuralSubscriptionTarget" |
123 | |
124 | === modified file 'lib/lp/registry/browser/distributionsourcepackage.py' |
125 | --- lib/lp/registry/browser/distributionsourcepackage.py 2010-01-06 12:02:38 +0000 |
126 | +++ lib/lp/registry/browser/distributionsourcepackage.py 2010-03-06 06:27:33 +0000 |
127 | @@ -49,7 +49,6 @@ |
128 | from lp.soyuz.interfaces.distributionsourcepackagerelease import ( |
129 | IDistributionSourcePackageRelease) |
130 | from lp.soyuz.interfaces.packagediff import IPackageDiffSet |
131 | -from lp.registry.browser.packaging import PackagingDeleteView |
132 | from lp.registry.interfaces.pocket import pocketsuffix |
133 | from lp.translations.browser.customlanguagecode import ( |
134 | HasCustomLanguageCodesTraversalMixin) |
135 | @@ -243,7 +242,7 @@ |
136 | |
137 | |
138 | class DistributionSourcePackageView(DistributionSourcePackageBaseView, |
139 | - PackagingDeleteView): |
140 | + LaunchpadView): |
141 | """View class for DistributionSourcePackage.""" |
142 | implements(IDistributionSourcePackageActionMenu) |
143 | |
144 | @@ -257,14 +256,6 @@ |
145 | return canonical_url(self.context) |
146 | |
147 | @property |
148 | - def all_packaging(self): |
149 | - """See `PackagingDeleteView`.""" |
150 | - for sourcepackage in self.context.get_distroseries_packages(): |
151 | - packaging = sourcepackage.direct_packaging |
152 | - if packaging is not None: |
153 | - yield packaging |
154 | - |
155 | - @property |
156 | def all_published_in_active_distroseries(self): |
157 | """Return a list of publishings in each active distroseries. |
158 | |
159 | @@ -408,16 +399,6 @@ |
160 | for distroseries in self.active_series: |
161 | # The first row for each series is the "title" row. |
162 | packaging = packages_by_series[distroseries].direct_packaging |
163 | - if packaging is None: |
164 | - delete_packaging_form_id = None |
165 | - hidden_packaging_field = None |
166 | - else: |
167 | - delete_packaging_form_id = "delete_%s_%s_%s" % ( |
168 | - packaging.distroseries.name, |
169 | - packaging.productseries.product.name, |
170 | - packaging.productseries.name) |
171 | - hidden_packaging_field = self._renderHiddenPackagingField( |
172 | - packaging) |
173 | package = packages_by_series[distroseries] |
174 | title_row = { |
175 | 'blank_row': False, |
176 | @@ -426,8 +407,6 @@ |
177 | 'distroseries': distroseries, |
178 | 'series_package': package, |
179 | 'packaging': packaging, |
180 | - 'hidden_packaging_field': hidden_packaging_field, |
181 | - 'delete_packaging_form_id': delete_packaging_form_id, |
182 | } |
183 | rows.append(title_row) |
184 | |
185 | |
186 | === removed file 'lib/lp/registry/browser/packaging.py' |
187 | --- lib/lp/registry/browser/packaging.py 2010-01-21 23:46:24 +0000 |
188 | +++ lib/lp/registry/browser/packaging.py 1970-01-01 00:00:00 +0000 |
189 | @@ -1,96 +0,0 @@ |
190 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
191 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
192 | - |
193 | -__metaclass__ = type |
194 | - |
195 | -__all__ = [ |
196 | - 'PackagingDeleteView', |
197 | - ] |
198 | - |
199 | -from zope.component import getUtility |
200 | -from zope.formlib import form |
201 | -from zope.schema import Choice |
202 | -from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary |
203 | - |
204 | -from canonical.launchpad import _ |
205 | -from lp.registry.interfaces.packaging import IPackagingUtil |
206 | -from canonical.launchpad.webapp.launchpadform import action, LaunchpadFormView |
207 | - |
208 | - |
209 | -class PackagingDeleteView(LaunchpadFormView): |
210 | - """A base view that provides packaging link deletion.""" |
211 | - |
212 | - @property |
213 | - def all_packaging(self): |
214 | - """An iterator of the context's packaging links.""" |
215 | - raise NotImplementedError |
216 | - |
217 | - def setUpFields(self): |
218 | - """See `LaunchpadFormView`.""" |
219 | - # No schema is set in this form, because all fields are created with |
220 | - # custom vocabularies. So we must not call the inherited setUpField |
221 | - # method. |
222 | - self.form_fields = self._createPackagingField() |
223 | - |
224 | - @property |
225 | - def can_delete_packaging(self): |
226 | - """Whether the user can delete existing packaging links.""" |
227 | - return self.user is not None |
228 | - |
229 | - def _createPackagingField(self): |
230 | - """Create a field to specify a Packaging association. |
231 | - |
232 | - Create a contextual vocabulary that can specify one of the Packaging |
233 | - associated to this DistributionSourcePackage. |
234 | - """ |
235 | - terms = [] |
236 | - for packaging in self.all_packaging: |
237 | - terms.append(SimpleTerm(packaging, packaging.id)) |
238 | - return form.Fields( |
239 | - Choice(__name__='packaging', vocabulary=SimpleVocabulary(terms), |
240 | - required=True)) |
241 | - |
242 | - def _renderHiddenPackagingField(self, packaging): |
243 | - """Render a hidden input that fills in the packaging field.""" |
244 | - if not self.can_delete_packaging: |
245 | - return None |
246 | - vocabulary = self.form_fields['packaging'].field.vocabulary |
247 | - return '<input type="hidden" name="field.packaging" value="%s" />' % ( |
248 | - vocabulary.getTerm(packaging).token) |
249 | - |
250 | - def renderDeletePackagingAction(self): |
251 | - """Render a submit input for the delete_packaging_action.""" |
252 | - assert self.can_delete_packaging, 'User cannot delete Packaging.' |
253 | - return ('<input type="image" value="Delete Link" ' |
254 | - 'src="/@@/remove" title="Delete upsteam link" ' |
255 | - 'name="%s"/>' % self.delete_packaging_action.__name__) |
256 | - |
257 | - def handleDeletePackagingError(self, action, data, errors): |
258 | - """Handle errors on package link deletion. |
259 | - |
260 | - If 'packaging' is not set in the form data, we assume that means the |
261 | - provided Packaging id was not found, which should only happen if the |
262 | - same Packaging object was concurrently deleted. In this case, we want |
263 | - to display a more informative error message than the default 'Invalid |
264 | - value'. |
265 | - """ |
266 | - if data.get('packaging') is None: |
267 | - self.setFieldError( |
268 | - 'packaging', |
269 | - _("This upstream association was deleted already.")) |
270 | - |
271 | - @action(_("Delete Link"), name='delete_packaging', |
272 | - failure=handleDeletePackagingError) |
273 | - def delete_packaging_action(self, action, data): |
274 | - """Delete a Packaging association.""" |
275 | - packaging = data['packaging'] |
276 | - productseries = packaging.productseries |
277 | - distroseries = packaging.distroseries |
278 | - getUtility(IPackagingUtil).deletePackaging( |
279 | - productseries, packaging.sourcepackagename, distroseries) |
280 | - self.request.response.addNotification( |
281 | - _("Removed upstream association between ${product} " |
282 | - "${productseries} and ${distroseries}.", mapping=dict( |
283 | - product=productseries.product.displayname, |
284 | - productseries=productseries.displayname, |
285 | - distroseries=distroseries.displayname))) |
286 | |
287 | === modified file 'lib/lp/registry/browser/product.py' |
288 | --- lib/lp/registry/browser/product.py 2010-02-16 21:21:14 +0000 |
289 | +++ lib/lp/registry/browser/product.py 2010-03-06 06:27:33 +0000 |
290 | @@ -81,7 +81,6 @@ |
291 | from lp.registry.browser.distribution import UsesLaunchpadMixin |
292 | from lp.registry.browser.menu import ( |
293 | IRegistryCollectionNavigationMenu, RegistryCollectionActionMenuBase) |
294 | -from lp.registry.browser.packaging import PackagingDeleteView |
295 | from lp.answers.browser.faqtarget import FAQTargetNavigationMixin |
296 | from canonical.launchpad.browser.feeds import FeedsMixin |
297 | from lp.registry.browser.productseries import get_series_branch_error |
298 | @@ -95,13 +94,15 @@ |
299 | StructuralSubscriptionTargetTraversalMixin) |
300 | from canonical.launchpad.mail import format_address, simple_sendmail |
301 | from canonical.launchpad.webapp import ( |
302 | - ApplicationMenu, LaunchpadEditFormView, LaunchpadFormView, LaunchpadView, |
303 | - Link, Navigation, StandardLaunchpadFacets, action, canonical_url, |
304 | - custom_widget, enabled_with_permission, sorted_version_numbers, |
305 | + ApplicationMenu, canonical_url, enabled_with_permission, LaunchpadView, |
306 | + Link, Navigation, sorted_version_numbers, StandardLaunchpadFacets, |
307 | stepthrough, stepto, structured) |
308 | from canonical.launchpad.webapp.authorization import check_permission |
309 | from canonical.launchpad.webapp.batching import BatchNavigator |
310 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
311 | +from canonical.launchpad.webapp.launchpadform import ( |
312 | + action, custom_widget, LaunchpadEditFormView, LaunchpadFormView, |
313 | + ReturnToReferrerMixin) |
314 | from canonical.launchpad.webapp.menu import NavigationMenu |
315 | from canonical.widgets.popup import PersonPickerWidget |
316 | from canonical.widgets.date import DateWidget |
317 | @@ -897,17 +898,11 @@ |
318 | check_permission('launchpad.Commercial', self.context)) |
319 | |
320 | |
321 | -class ProductPackagesView(PackagingDeleteView): |
322 | +class ProductPackagesView(LaunchpadView): |
323 | """View for displaying product packaging""" |
324 | |
325 | label = 'Linked packages' |
326 | - |
327 | - @property |
328 | - def all_packaging(self): |
329 | - """See `PackagingDeleteView`.""" |
330 | - for series in self.context.series: |
331 | - for packaging in series.packagings: |
332 | - yield packaging |
333 | + page_title = label |
334 | |
335 | @cachedproperty |
336 | def series_packages(self): |
337 | @@ -921,23 +916,11 @@ |
338 | field: '<input type=''hidden' ...>}, |
339 | }] |
340 | """ |
341 | - # This method is a superset of all_packaging. While all_packaging will |
342 | - # be called several times as data is mutated, series_packages should |
343 | - # only be called during render(). |
344 | packaged_series = [] |
345 | for series in self.context.series: |
346 | packagings = [] |
347 | for packaging in series.packagings: |
348 | - form_id = 'delete-%s-%s-%s' % ( |
349 | - packaging.distroseries.name, |
350 | - packaging.sourcepackagename.name, |
351 | - packaging.productseries.name, |
352 | - ) |
353 | - packaging_field = dict( |
354 | - packaging=packaging, |
355 | - form_id=form_id, |
356 | - field=self._renderHiddenPackagingField(packaging)) |
357 | - packagings.append(packaging_field) |
358 | + packagings.append(packaging) |
359 | packaged_series.append(dict( |
360 | series=series, packagings=packagings)) |
361 | return packaged_series |
362 | @@ -1262,7 +1245,8 @@ |
363 | return canonical_url(self.context) |
364 | |
365 | |
366 | -class ProductReviewLicenseView(ProductEditView, EditPrivateBugsMixin): |
367 | +class ProductReviewLicenseView(ReturnToReferrerMixin, |
368 | + ProductEditView, EditPrivateBugsMixin): |
369 | """A view to review a project and change project privileges.""" |
370 | label = "Review project" |
371 | field_names = [ |
372 | @@ -1302,27 +1286,6 @@ |
373 | # supervisor. |
374 | self.validate_private_bugs(data) |
375 | |
376 | - @property |
377 | - def next_url(self): |
378 | - """See `LaunchpadFormView`.""" |
379 | - # The referer header we want is only available before the view's |
380 | - # form submits to itself. This field is a hidden input in the form. |
381 | - referrer = self.request.form.get('next_url') |
382 | - if referrer is None: |
383 | - referrer = self.request.getHeader('referer') |
384 | - |
385 | - if (referrer is not None |
386 | - and referrer.startswith(self.request.getApplicationURL())): |
387 | - return referrer |
388 | - else: |
389 | - return canonical_url(self.context) |
390 | - |
391 | - @property |
392 | - def cancel_url(self): |
393 | - """See `LaunchpadFormView`.""" |
394 | - return self.next_url |
395 | - |
396 | - |
397 | class ProductAddSeriesView(LaunchpadFormView): |
398 | """A form to add new product series""" |
399 | |
400 | @@ -1613,7 +1576,7 @@ |
401 | self.request.form['summary'] = data['summary'] |
402 | |
403 | |
404 | -class ProjectAddStepTwo(StepView, ProductLicenseMixin): |
405 | +class ProjectAddStepTwo(StepView, ProductLicenseMixin, ReturnToReferrerMixin): |
406 | """Step 2 (of 2) in the +new project add wizard.""" |
407 | |
408 | _field_names = ['displayname', 'name', 'title', 'summary', |
409 | |
410 | === modified file 'lib/lp/registry/browser/sourcepackage.py' |
411 | --- lib/lp/registry/browser/sourcepackage.py 2010-02-23 19:43:58 +0000 |
412 | +++ lib/lp/registry/browser/sourcepackage.py 2010-03-06 06:27:33 +0000 |
413 | @@ -12,7 +12,8 @@ |
414 | 'SourcePackageFacets', |
415 | 'SourcePackageHelpView', |
416 | 'SourcePackageNavigation', |
417 | - 'SourcePackagePackaging', |
418 | + 'SourcePackageRemoveUpstreamView', |
419 | + 'SourcePackageUpstreamConnectionsView', |
420 | 'SourcePackageView', |
421 | ] |
422 | |
423 | @@ -40,7 +41,7 @@ |
424 | from lp.answers.browser.questiontarget import ( |
425 | QuestionTargetFacetMixin, QuestionTargetAnswersMenu) |
426 | from lp.services.worlddata.interfaces.country import ICountry |
427 | -from lp.registry.interfaces.packaging import IPackaging |
428 | +from lp.registry.interfaces.packaging import IPackaging, IPackagingUtil |
429 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
430 | from lp.registry.interfaces.product import IProductSet |
431 | from lp.registry.interfaces.productseries import IProductSeries |
432 | @@ -49,12 +50,15 @@ |
433 | from lp.translations.interfaces.potemplate import IPOTemplateSet |
434 | from canonical.launchpad import _ |
435 | from canonical.launchpad.webapp import ( |
436 | - action, ApplicationMenu, custom_widget, GetitemNavigation, |
437 | - LaunchpadFormView, Link, redirection, StandardLaunchpadFacets, stepto) |
438 | + ApplicationMenu, GetitemNavigation, Link, redirection, |
439 | + StandardLaunchpadFacets, stepto) |
440 | +from canonical.launchpad.webapp.launchpadform import ( |
441 | + action, custom_widget, LaunchpadFormView, ReturnToReferrerMixin) |
442 | from canonical.launchpad.webapp import canonical_url |
443 | from canonical.launchpad.webapp.authorization import check_permission |
444 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
445 | from canonical.launchpad.webapp.menu import structured |
446 | +from canonical.launchpad.webapp.publisher import LaunchpadView |
447 | |
448 | from canonical.lazr.utils import smartquote |
449 | |
450 | @@ -108,7 +112,7 @@ |
451 | usedfor = ISourcePackage |
452 | facet = 'overview' |
453 | links = [ |
454 | - 'distribution_source_package', 'edit_packaging', |
455 | + 'distribution_source_package', 'edit_packaging', 'remove_packaging', |
456 | 'changelog', 'builds', 'set_upstream', |
457 | ] |
458 | |
459 | @@ -124,6 +128,10 @@ |
460 | def edit_packaging(self): |
461 | return Link('+edit-packaging', 'Change upstream link', icon='edit') |
462 | |
463 | + def remove_packaging(self): |
464 | + return Link( |
465 | + '+remove-packaging', 'Remove upstream link', icon='remove') |
466 | + |
467 | def set_upstream(self): |
468 | return Link("+edit-packaging", "Set upstream link", icon="add") |
469 | |
470 | @@ -143,7 +151,7 @@ |
471 | return Link('+gethelp', 'Help and support options', icon='info') |
472 | |
473 | |
474 | -class SourcePackageChangeUpstreamStepOne(StepView): |
475 | +class SourcePackageChangeUpstreamStepOne(ReturnToReferrerMixin, StepView): |
476 | """A view to set the `IProductSeries` of a sourcepackage.""" |
477 | schema = Interface |
478 | _field_names = [] |
479 | @@ -167,9 +175,8 @@ |
480 | IProductSeries['product'], default=default) |
481 | self.form_fields += Fields(product_field) |
482 | |
483 | - @property |
484 | - def cancel_url(self): |
485 | - return canonical_url(self.context) |
486 | + # Override ReturnToReferrerMixin.next_url. |
487 | + next_url = None |
488 | |
489 | def main_action(self, data): |
490 | """See `MultiStepView`.""" |
491 | @@ -177,7 +184,7 @@ |
492 | self.request.form['product'] = data['product'] |
493 | |
494 | |
495 | -class SourcePackageChangeUpstreamStepTwo(StepView): |
496 | +class SourcePackageChangeUpstreamStepTwo(ReturnToReferrerMixin, StepView): |
497 | """A view to set the `IProductSeries` of a sourcepackage.""" |
498 | schema = IProductSeries |
499 | _field_names = ['product'] |
500 | @@ -196,10 +203,6 @@ |
501 | custom_widget('product', DropdownWidget, visible=False) |
502 | custom_widget('productseries', LaunchpadRadioWidget) |
503 | |
504 | - @property |
505 | - def cancel_url(self): |
506 | - return canonical_url(self.context) |
507 | - |
508 | def setUpFields(self): |
509 | super(SourcePackageChangeUpstreamStepTwo, self).setUpFields() |
510 | |
511 | @@ -261,12 +264,16 @@ |
512 | Fields(display_product_field, productseries_choice) |
513 | + self.form_fields) |
514 | |
515 | + # Override ReturnToReferrerMixin.next_url until the main_action() |
516 | + # is called. |
517 | + next_url = None |
518 | + |
519 | main_action_label = u'Change' |
520 | def main_action(self, data): |
521 | productseries = data['productseries'] |
522 | # Because it is part of a multistep view, the next_url can't |
523 | # be set until the action is called, or it will skip the step. |
524 | - self.next_url = canonical_url(self.context) |
525 | + self.next_url = self._return_url |
526 | if self.context.productseries == productseries: |
527 | # There is nothing to do. |
528 | return |
529 | @@ -282,6 +289,27 @@ |
530 | first_step = SourcePackageChangeUpstreamStepOne |
531 | |
532 | |
533 | +class SourcePackageRemoveUpstreamView(ReturnToReferrerMixin, |
534 | + LaunchpadFormView): |
535 | + """A view for removing the link to an upstream package.""" |
536 | + |
537 | + schema = Interface |
538 | + field_names = [] |
539 | + label = 'Unlink an upstream project' |
540 | + page_title = label |
541 | + |
542 | + @action('Unlink') |
543 | + def unlink(self, action, data): |
544 | + old_series = self.context.productseries |
545 | + getUtility(IPackagingUtil).deletePackaging( |
546 | + self.context.productseries, |
547 | + self.context.sourcepackagename, |
548 | + self.context.distroseries) |
549 | + self.request.response.addInfoNotification( |
550 | + 'Removed upstream association between %s and %s.' % ( |
551 | + old_series.title, self.context.distroseries.displayname)) |
552 | + |
553 | + |
554 | class SourcePackageView: |
555 | """A view for (distro series) source packages.""" |
556 | |
557 | @@ -411,6 +439,7 @@ |
558 | def setUpFields(self): |
559 | """See `LaunchpadFormView`.""" |
560 | super(SourcePackageAssociationPortletView, self).setUpFields() |
561 | + self.request.annotations['show_edit_buttons'] = True |
562 | # Find registered products that are similarly named to the source |
563 | # package. |
564 | product_vocab = getVocabularyRegistry().get(None, 'Product') |
565 | @@ -444,6 +473,10 @@ |
566 | upstream.displayname) |
567 | self.next_url = self.request.getURL() |
568 | |
569 | + |
570 | +class SourcePackageUpstreamConnectionsView(LaunchpadView): |
571 | + """A shared view with upstream connection info.""" |
572 | + |
573 | @property |
574 | def has_bugtracker(self): |
575 | """Does the product have a bugtracker set?""" |
576 | |
577 | === modified file 'lib/lp/registry/browser/tests/packaging-views.txt' |
578 | --- lib/lp/registry/browser/tests/packaging-views.txt 2010-02-18 18:58:26 +0000 |
579 | +++ lib/lp/registry/browser/tests/packaging-views.txt 2010-03-06 06:27:33 +0000 |
580 | @@ -273,32 +273,19 @@ |
581 | >>> print view.label |
582 | Linked packages |
583 | |
584 | -The view defines the all_packages property used by the PackagingDeleteView |
585 | -to create a vocabulary. |
586 | - |
587 | - >>> for package in view.all_packaging: |
588 | - ... print package.distroseries.name, package.productseries.name |
589 | - grumpy hotter |
590 | - hoary hotter |
591 | - |
592 | The view provides the series_packages property that returns a list of |
593 | -dicts. Each dict as a series and a list of package dicts. The package dict |
594 | -contains the package and field for form actions. |
595 | +dicts. Each dict as a series and a list of packages. |
596 | |
597 | - >>> for series_dict in view.series_packages: |
598 | - ... print series_dict['series'].name |
599 | - ... for package_dict in series_dict['packagings']: |
600 | - ... print package_dict['packaging'].distroseries.name |
601 | - ... print package_dict['form_id'] |
602 | - ... print package_dict['field'] |
603 | + >>> def print_packages(view): |
604 | + ... for series_dict in view.series_packages: |
605 | + ... print series_dict['series'].name |
606 | + ... for package in series_dict['packagings']: |
607 | + ... print package.distroseries.name |
608 | + >>> print_packages(view) |
609 | cold |
610 | hotter |
611 | grumpy |
612 | - delete-grumpy-hot-hotter |
613 | - <input type="hidden" name="field.packaging" .../> |
614 | hoary |
615 | - delete-hoary-thunderbird-hotter |
616 | - <input type="hidden" name="field.packaging" .../> |
617 | trunk |
618 | |
619 | The view provides the distro_packaging property that is a list of |
620 | @@ -306,40 +293,48 @@ |
621 | sorted by distribution with Ubuntu first and the rest in alphabetic |
622 | order. |
623 | |
624 | - >>> view = create_initialized_view(product, name='+packages') |
625 | >>> for distro_dict in view.distro_packaging: |
626 | ... print distro_dict['distribution'].name |
627 | ubuntu |
628 | |
629 | -The +packages named view descends from PackagingDeleteView to provide remove |
630 | -link actions for the product's linked packages. |
631 | - |
632 | - >>> from lp.registry.browser.packaging import PackagingDeleteView |
633 | - |
634 | - >>> isinstance(view, PackagingDeleteView) |
635 | - True |
636 | - |
637 | A packaging link can be deleted if the owner believes it is an error. The |
638 | package linked to hoary is wrong; thunderbird is the wrong sourcepackage. |
639 | |
640 | + >>> from canonical.launchpad.testing.pages import find_tag_by_id |
641 | + >>> view = create_initialized_view( |
642 | + ... product, name='+packages', principal=a_user) |
643 | + >>> print_packages(view) |
644 | + cold |
645 | + hotter |
646 | + grumpy |
647 | + hoary |
648 | + trunk |
649 | + |
650 | + # There are links to the +remove-packaging page. |
651 | + >>> table = find_tag_by_id(view.render(), 'packages-hotter') |
652 | + >>> for link in table.findAll('a'): |
653 | + ... if '+remove-packaging' in link['href']: |
654 | + ... print link['href'] |
655 | + http://launchpad.dev/ubuntu/grumpy/+source/hot/+remove-packaging |
656 | + http://launchpad.dev/ubuntu/hoary/+source/thunderbird/+remove-packaging |
657 | + |
658 | + >>> [hoary_package] = [ |
659 | + ... package for series_dict in view.series_packages |
660 | + ... for package in series_dict['packagings'] |
661 | + ... if package.distroseries.name == 'hoary'] |
662 | + >>> form = {'field.actions.unlink': 'Unlink'} |
663 | + >>> unlink_view = create_initialized_view( |
664 | + ... hoary_package.sourcepackage, name='+remove-packaging', form=form) |
665 | + >>> unlink_view.errors |
666 | + [] |
667 | + |
668 | + # The view has to be reloaded since view.series_packages is cached. |
669 | >>> view = create_initialized_view(product, name='+packages') |
670 | - >>> for package in view.all_packaging: |
671 | - ... print package.distroseries.name, package.productseries.name |
672 | - grumpy hotter |
673 | - hoary hotter |
674 | - |
675 | - >>> [hoary_package] = [package for package in view.all_packaging |
676 | - ... if package.distroseries.name == 'hoary'] |
677 | - >>> form = { |
678 | - ... 'field.packaging': '%s' % hoary_package.id, |
679 | - ... 'field.actions.delete_packaging': 'Delete upstream link', |
680 | - ... } |
681 | - >>> view = create_initialized_view(product, name='+packages', form=form) |
682 | - >>> view.errors |
683 | - [] |
684 | - >>> for package in view.all_packaging: |
685 | - ... print package.distroseries.name, package.productseries.name |
686 | - grumpy hotter |
687 | + >>> print_packages(view) |
688 | + cold |
689 | + hotter |
690 | + grumpy |
691 | + trunk |
692 | |
693 | |
694 | Distro series +packaging view |
695 | |
696 | === modified file 'lib/lp/registry/browser/tests/sourcepackage-views.txt' |
697 | --- lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-02-18 20:39:01 +0000 |
698 | +++ lib/lp/registry/browser/tests/sourcepackage-views.txt 2010-03-06 06:27:33 +0000 |
699 | @@ -145,6 +145,7 @@ |
700 | >>> print view.request.response.notifications |
701 | [] |
702 | |
703 | + |
704 | Upstream associations portlet |
705 | ----------------------------- |
706 | |
707 | @@ -163,12 +164,18 @@ |
708 | |
709 | >>> from canonical.launchpad.testing.pages import ( |
710 | ... extract_text, find_tag_by_id) |
711 | - >>> content = extract_text(find_tag_by_id(view.render(), 'upstreams')) |
712 | - >>> print content |
713 | - Bonkers project |
714 | + >>> content = find_tag_by_id(view.render(), 'upstreams') |
715 | + >>> for link in content.findAll('a'): |
716 | + ... print link['href'] |
717 | + /bonkers |
718 | + /bonkers/crazy |
719 | + .../+source/bonkers/+edit-packaging |
720 | + .../+source/bonkers/+remove-packaging |
721 | + |
722 | + >>> print extract_text(content) |
723 | + Bonkers...crazy... |
724 | Bug supervisor: no |
725 | Bug tracker: no |
726 | - Bonkers crazy series |
727 | Branch: no |
728 | |
729 | A new source project that is not linked to an upstream will result in |
730 | @@ -211,6 +218,9 @@ |
731 | Lernid Dev... |
732 | |
733 | |
734 | +Upstream connections view |
735 | +------------------------- |
736 | + |
737 | The view includes a property for determining if the project has a bug |
738 | tracker, though the rules are somewhat complicated. |
739 | |
740 | @@ -226,7 +236,8 @@ |
741 | >>> package = factory.makeSourcePackage( |
742 | ... sourcepackagename=sourcepackagename, distroseries=distroseries) |
743 | |
744 | - >>> view = create_initialized_view(package, name='+portlet-associations') |
745 | + >>> view = create_initialized_view( |
746 | + ... package, name='+upstream-connections') |
747 | |
748 | >>> print package.productseries |
749 | None |
750 | @@ -242,7 +253,8 @@ |
751 | If a product is not part of a project group and its bug tracker is not |
752 | set then the view property is false. |
753 | |
754 | - >>> view = create_initialized_view(package, name='+portlet-associations') |
755 | + >>> view = create_initialized_view( |
756 | + ... package, name='+upstream-connections') |
757 | |
758 | >>> print product.official_malone |
759 | False |
760 | @@ -285,3 +297,37 @@ |
761 | >>> project.bugtracker = bugtracker |
762 | >>> print view.has_bugtracker |
763 | True |
764 | + |
765 | + |
766 | +Remove packaging view |
767 | +--------------------- |
768 | + |
769 | +This view allows removal of the packaging link from the sourcepackage |
770 | +to the project series. |
771 | + |
772 | + >>> view = create_initialized_view(package, name='+remove-packaging') |
773 | + >>> print view.label |
774 | + Unlink an upstream project |
775 | + |
776 | + >>> print view.page_title |
777 | + Unlink an upstream project |
778 | + |
779 | + >>> print view.cancel_url |
780 | + http://launchpad.dev/youbuntu/wonky/+source/stinkypackage |
781 | + |
782 | + >>> form = {'field.actions.unlink': 'Unlink'} |
783 | + >>> view = create_initialized_view( |
784 | + ... package, name='+remove-packaging', form=form, |
785 | + ... principal=product.owner) |
786 | + >>> view.errors |
787 | + [] |
788 | + |
789 | + >>> for notification in view.request.response.notifications: |
790 | + ... print notification.message |
791 | + Removed upstream association between Stinky stinkyseries series and Wonky. |
792 | + |
793 | + >>> view = create_initialized_view(package, name='+portlet-associations') |
794 | + >>> print extract_text(find_tag_by_id(view.render(), 'no-upstreams')) |
795 | + There are no projects registered in Launchpad that are a potential |
796 | + match for this source package. Can you help us find one? |
797 | + Set upstream link |
798 | |
799 | === modified file 'lib/lp/registry/browser/tests/test_packaging.py' |
800 | --- lib/lp/registry/browser/tests/test_packaging.py 2009-10-23 12:53:21 +0000 |
801 | +++ lib/lp/registry/browser/tests/test_packaging.py 2010-03-06 06:27:33 +0000 |
802 | @@ -55,8 +55,10 @@ |
803 | # Delete the packaging |
804 | user_browser = self.user_browser |
805 | user_browser.open('http://launchpad.dev/ubuntu/+source/alsa-utils') |
806 | - form = user_browser.getForm("delete_warty_alsa-utils_trunk") |
807 | - form.getControl(name="field.actions.delete_packaging").click() |
808 | + link = user_browser.getLink( |
809 | + url='/ubuntu/warty/+source/alsa-utils/+remove-packaging') |
810 | + link.click() |
811 | + user_browser.getControl('Unlink').click() |
812 | # Check that the change was committed. |
813 | login('no-priv@canonical.com') |
814 | self.assertFalse(packaging_util.packagingEntryExists( |
815 | |
816 | === modified file 'lib/lp/registry/model/productseries.py' |
817 | --- lib/lp/registry/model/productseries.py 2010-02-25 21:37:02 +0000 |
818 | +++ lib/lp/registry/model/productseries.py 2010-03-06 06:27:33 +0000 |
819 | @@ -412,8 +412,10 @@ |
820 | |
821 | # ok, we didn't find a packaging record that matches, let's go ahead |
822 | # and create one |
823 | - pkg = Packaging(distroseries=distroseries, |
824 | - sourcepackagename=sourcepackagename, productseries=self, |
825 | + pkg = Packaging( |
826 | + distroseries=distroseries, |
827 | + sourcepackagename=sourcepackagename, |
828 | + productseries=self, |
829 | packaging=PackagingType.PRIME, |
830 | owner=owner) |
831 | pkg.sync() # convert UTC_NOW to actual datetime |
832 | |
833 | === modified file 'lib/lp/registry/model/sourcepackage.py' |
834 | --- lib/lp/registry/model/sourcepackage.py 2010-02-25 21:37:02 +0000 |
835 | +++ lib/lp/registry/model/sourcepackage.py 2010-03-06 06:27:33 +0000 |
836 | @@ -477,10 +477,11 @@ |
837 | target.datecreated = UTC_NOW |
838 | else: |
839 | # ok, we need to create a new one |
840 | - Packaging(distroseries=self.distroseries, |
841 | - sourcepackagename=self.sourcepackagename, |
842 | - productseries=productseries, owner=user, |
843 | - packaging=PackagingType.PRIME) |
844 | + Packaging( |
845 | + distroseries=self.distroseries, |
846 | + sourcepackagename=self.sourcepackagename, |
847 | + productseries=productseries, owner=user, |
848 | + packaging=PackagingType.PRIME) |
849 | # and make sure this change is immediately available |
850 | flush_database_updates() |
851 | |
852 | |
853 | === modified file 'lib/lp/registry/stories/distroseries/xx-show-distroseries-packaging.txt' |
854 | --- lib/lp/registry/stories/distroseries/xx-show-distroseries-packaging.txt 2010-02-25 16:36:02 +0000 |
855 | +++ lib/lp/registry/stories/distroseries/xx-show-distroseries-packaging.txt 2010-03-06 06:27:33 +0000 |
856 | @@ -17,12 +17,13 @@ |
857 | Source Package Upstream Project Upstream Contributor Connections |
858 | netapplet |
859 | NetApplet ... The Novell Network Applet |
860 | - NetApplet ... project: Bug supervisor: no Bug tracker: yes |
861 | - NetApplet trunk series: Branch: no |
862 | + GNOME...NetApplet...trunk |
863 | + ...Bug supervisor: no Bug tracker: yes Branch: no |
864 | evolution |
865 | Evolution ... Evolution is an email client, addressbook ... |
866 | - Evolution ... project: Bug supervisor: no Bug tracker: yes |
867 | - Evolution trunk series: Branch: yes Translations: no |
868 | + GNOME...Evolution...trunk |
869 | + ...Bug supervisor: no Bug tracker: yes |
870 | + Branch: yes Translations: no |
871 | |
872 | Any use can see that this page is related to the needs packaging report. It |
873 | is linked, but the link to this page is not enabled. |
874 | |
875 | === modified file 'lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.txt' |
876 | --- lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.txt 2009-10-23 12:53:21 +0000 |
877 | +++ lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.txt 2010-03-06 06:27:33 +0000 |
878 | @@ -16,33 +16,28 @@ |
879 | Then the user click the "Delete Link" button in the first tab. The |
880 | deletion succeeds and the usual informational message is displayed. |
881 | |
882 | - >>> form = first_browser.getForm("delete_warty_alsa-utils_trunk") |
883 | - >>> form.getControl(name="field.actions.delete_packaging").click() |
884 | + >>> link = first_browser.getLink( |
885 | + ... url='/ubuntu/warty/+source/alsa-utils/+remove-packaging') |
886 | + >>> link.click() |
887 | + >>> first_browser.getControl('Unlink').click() |
888 | >>> content = first_browser.contents |
889 | >>> for tag in find_tags_by_class(content, 'error'): |
890 | ... print extract_text(tag) |
891 | >>> for tag in find_tags_by_class(content, 'informational'): |
892 | ... print extract_text(tag) |
893 | - Removed upstream association between alsa-utils trunk and Warty. |
894 | + Removed upstream association between alsa-utils trunk series and Warty. |
895 | |
896 | A few minutes later, the user sees the same packaging association in the |
897 | second tab, and clicks the "Delete Link" button again. |
898 | |
899 | The packaging object has been deleted already, so this action cannot |
900 | -succeed. We yield a more useful error message than the mysterious |
901 | -"Invalid value" displayed by default. |
902 | - |
903 | -XXX: We would like to display an informational message and no error |
904 | -message, but it would be too much trouble for a corner case like this. |
905 | -So we resort to just displaying a helpful error message. |
906 | --- David Allouche 2007-12-07 |
907 | - |
908 | - >>> form = second_browser.getForm("delete_warty_alsa-utils_trunk") |
909 | - >>> form.getControl(name="field.actions.delete_packaging").click() |
910 | +succeed. |
911 | + |
912 | + >>> second_browser.getLink( |
913 | + ... url='/ubuntu/warty/+source/alsa-utils/+remove-packaging').click() |
914 | >>> content = second_browser.contents |
915 | >>> for tag in find_tags_by_class(content, 'informational'): |
916 | ... print extract_text(tag) |
917 | >>> for tag in find_tags_by_class(content, 'error'): |
918 | ... print extract_text(tag) |
919 | - There is 1 error. |
920 | This upstream association was deleted already. |
921 | |
922 | === modified file 'lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.txt' |
923 | --- lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.txt 2009-10-23 12:53:21 +0000 |
924 | +++ lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.txt 2010-03-06 06:27:33 +0000 |
925 | @@ -15,6 +15,7 @@ |
926 | The Hoary Hedgehog Release (active development) Set upstream link |
927 | 1.0.9a-4ubuntu1 release (main) ... weeks ago |
928 | The Warty Warthog Release (current stable release) alsa-utils trunk series |
929 | + ... |
930 | 1.0.8-1ubuntu1 release (main) ... weeks ago |
931 | 1.0.9a-4 release (main) ... weeks ago |
932 | |
933 | @@ -26,9 +27,10 @@ |
934 | packaging links. |
935 | |
936 | >>> user_browser.open('http://launchpad.dev/ubuntu/+source/alsa-utils') |
937 | - >>> form = user_browser.getForm("delete_warty_alsa-utils_trunk") |
938 | - >>> print form.getControl(name="field.actions.delete_packaging") |
939 | - <ImageControl name='field.actions.delete_packaging' type='image'> |
940 | + >>> link = user_browser.getLink( |
941 | + ... url='/ubuntu/warty/+source/alsa-utils/+remove-packaging') |
942 | + >>> print link |
943 | + <Link text='Remove upstream link'... |
944 | |
945 | This button is not displayed to anonymous users. |
946 | |
947 | @@ -40,14 +42,16 @@ |
948 | |
949 | Clicking this button deletes the corresponding packaging association. |
950 | |
951 | - >>> form = user_browser.getForm("delete_warty_alsa-utils_trunk") |
952 | - >>> form.getControl(name="field.actions.delete_packaging").click() |
953 | + >>> link = user_browser.getLink( |
954 | + ... url='/ubuntu/warty/+source/alsa-utils/+remove-packaging') |
955 | + >>> link.click() |
956 | + >>> user_browser.getControl('Unlink').click() |
957 | >>> content = user_browser.contents |
958 | >>> for tag in find_tags_by_class(content, 'error'): |
959 | ... print extract_text(tag) |
960 | >>> for tag in find_tags_by_class(content, 'informational'): |
961 | ... print extract_text(tag) |
962 | - Removed upstream association between alsa-utils trunk and Warty. |
963 | + Removed upstream association between alsa-utils trunk series and Warty. |
964 | >>> print extract_text(find_tag_by_id(content, 'packages_list')) |
965 | The Hoary Hedgehog Release (active development) Set upstream link |
966 | 1.0.9a-4ubuntu1 release (main) ... weeks ago |
967 | |
968 | === modified file 'lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt' |
969 | --- lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-02-18 20:41:01 +0000 |
970 | +++ lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.txt 2010-03-06 06:27:33 +0000 |
971 | @@ -29,11 +29,9 @@ |
972 | >>> user_browser.getControl("Change").click() |
973 | >>> print extract_text(find_tag_by_id( |
974 | ... user_browser.contents, 'upstreams')) |
975 | - Mozilla Thunderbird ... project |
976 | - Project Group: the Mozilla Project |
977 | + the Mozilla Project...Mozilla Thunderbird...trunk... |
978 | Bug supervisor: no |
979 | Bug tracker: no |
980 | - Mozilla Thunderbird trunk series |
981 | Branch: no |
982 | Translations: no |
983 | |
984 | |
985 | === modified file 'lib/lp/registry/stories/product/xx-product-package-pages.txt' |
986 | --- lib/lp/registry/stories/product/xx-product-package-pages.txt 2010-01-12 22:34:51 +0000 |
987 | +++ lib/lp/registry/stories/product/xx-product-package-pages.txt 2010-03-06 06:27:33 +0000 |
988 | @@ -30,8 +30,8 @@ |
989 | >>> print extract_text(find_tag_by_id( |
990 | ... evo_owner.contents, 'packages-trunk')) |
991 | Distribution Distribution series Source package Version |
992 | - Ubuntu Warty (4.10) evolution |
993 | - Ubuntu Hoary (5.04) evolution 1.0 |
994 | + Ubuntu Warty (4.10) evolution Remove... |
995 | + Ubuntu Hoary (5.04) evolution 1.0 Remove... |
996 | |
997 | >>> evo_owner.getLink(url='/ubuntu/hoary/+source/evolution') is not None |
998 | True |
999 | @@ -54,16 +54,19 @@ |
1000 | |
1001 | Packaging links can be deleted if they were created in error. |
1002 | |
1003 | - >>> form = evo_owner.getForm("delete-warty-evolution-trunk") |
1004 | - >>> form.getControl(name="field.actions.delete_packaging").click() |
1005 | - >>> print evo_owner.title |
1006 | - Linked packages ... |
1007 | + >>> evo_owner.getLink( |
1008 | + ... url='/ubuntu/warty/+source/evolution/+remove-packaging').click() |
1009 | + >>> print evo_owner.title |
1010 | + Unlink an upstream project... |
1011 | + >>> evo_owner.getControl('Unlink').click() |
1012 | + >>> print evo_owner.title |
1013 | + Linked packages... |
1014 | |
1015 | >>> for message in get_feedback_messages(evo_owner.contents): |
1016 | ... print message |
1017 | - Removed upstream association between Evolution trunk and Warty. |
1018 | + Removed upstream association between Evolution trunk series and Warty. |
1019 | |
1020 | >>> print extract_text(find_tag_by_id( |
1021 | ... evo_owner.contents, 'packages-trunk')) |
1022 | Distribution Distribution series Source package Version |
1023 | - Ubuntu Hoary (5.04) evolution 1.0 |
1024 | + Ubuntu Hoary (5.04) evolution 1.0 Remove... |
1025 | |
1026 | === modified file 'lib/lp/registry/stories/productseries/xx-productseries-delete.txt' |
1027 | --- lib/lp/registry/stories/productseries/xx-productseries-delete.txt 2009-12-14 13:49:03 +0000 |
1028 | +++ lib/lp/registry/stories/productseries/xx-productseries-delete.txt 2010-03-06 06:27:33 +0000 |
1029 | @@ -46,8 +46,10 @@ |
1030 | Mozilla Firefox in Launchpad |
1031 | |
1032 | >>> owner_browser.getLink('All packages').click() |
1033 | - >>> owner_browser.getControl( |
1034 | - ... name='field.actions.delete_packaging', index=1).click() |
1035 | + >>> link = owner_browser.getLink( |
1036 | + ... url='/ubuntu/warty/+source/mozilla-firefox/+remove-packaging') |
1037 | + >>> link.click() |
1038 | + >>> owner_browser.getControl('Unlink').click() |
1039 | |
1040 | Then he returns to delete trunk. He is informed that deletion is permanent, |
1041 | and that the milestones, releases, and files will also be deleted. The |
1042 | |
1043 | === modified file 'lib/lp/registry/templates/distributionsourcepackage-index.pt' |
1044 | --- lib/lp/registry/templates/distributionsourcepackage-index.pt 2010-02-09 15:38:13 +0000 |
1045 | +++ lib/lp/registry/templates/distributionsourcepackage-index.pt 2010-03-06 06:27:33 +0000 |
1046 | @@ -28,21 +28,6 @@ |
1047 | </tal:side> |
1048 | |
1049 | <tal:main metal:fill-slot="main"> |
1050 | - <p class="error message" |
1051 | - tal:condition="view/errors" |
1052 | - tal:content="view/error_count" /> |
1053 | - <p class="error message" |
1054 | - tal:repeat="form_wide_error view/form_wide_errors" |
1055 | - tal:content="structure form_wide_error"> |
1056 | - Schema validation errors. |
1057 | - </p> |
1058 | - <p class="error message" |
1059 | - tal:define="error python:view.getFieldError('packaging')" |
1060 | - tal:condition="error" |
1061 | - tal:content="structure error"> |
1062 | - Field specific error for the hidden packaging field. |
1063 | - </p> |
1064 | - |
1065 | <div class="top-portlet" id="bugs-and-questions-summary" |
1066 | tal:define="newbugs context/new_bugtasks/count; |
1067 | open_questions view/open_questions/count"> |
1068 | @@ -141,25 +126,14 @@ |
1069 | <a tal:attributes="href row/series_package/fmt:url" |
1070 | tal:content="row/distroseries/title"/> |
1071 | (<span tal:replace="row/distroseries/status/title/lower"/>) |
1072 | - <div style="float:right;"> |
1073 | + <div style="float:right; white-space: nowrap"> |
1074 | <a tal:condition="not: row/packaging" |
1075 | tal:replace="structure row/series_package/menu:overview/set_upstream/fmt:link"/> |
1076 | <tal:has_packaging condition="row/packaging"> |
1077 | <img tal:replace="structure row/packaging/productseries/image:icon"/> |
1078 | <a tal:replace="structure row/packaging/productseries/fmt:link"/> |
1079 | - <form style="display: inline" |
1080 | - method="POST" |
1081 | - tal:condition="row/hidden_packaging_field" |
1082 | - tal:attributes="action request/URL; |
1083 | - id row/delete_packaging_form_id"> |
1084 | - <tal:hidden replace="structure row/hidden_packaging_field" /> |
1085 | - <tal:action replace="structure view/renderDeletePackagingAction" /> |
1086 | - </form> |
1087 | - <a tal:attributes=" |
1088 | - href row/series_package/menu:overview/edit_packaging/url; |
1089 | - title string:Edit upsteam link"> |
1090 | - <img src="/@@/edit"/> |
1091 | - </a> |
1092 | + <a tal:replace="structure row/series_package/menu:overview/edit_packaging/fmt:icon"/> |
1093 | + <a tal:replace="structure row/series_package/menu:overview/remove_packaging/fmt:icon" /> |
1094 | </tal:has_packaging> |
1095 | </div><!--float right--> |
1096 | </td> |
1097 | |
1098 | === modified file 'lib/lp/registry/templates/distroseries-packaging.pt' |
1099 | --- lib/lp/registry/templates/distroseries-packaging.pt 2010-02-16 19:07:42 +0000 |
1100 | +++ lib/lp/registry/templates/distroseries-packaging.pt 2010-03-06 06:27:33 +0000 |
1101 | @@ -18,7 +18,7 @@ |
1102 | <p> |
1103 | The packages are listed by priority based on the greatest need to |
1104 | forward bugs, sync translations, or specify the latest |
1105 | - development branch. |
1106 | + development branch. |
1107 | </p> |
1108 | |
1109 | <ul id="related-pages" class="horizontal"> |
1110 | @@ -72,42 +72,7 @@ |
1111 | tal:condition="product/summary" |
1112 | tal:content="product/summary"/> |
1113 | </td> |
1114 | - |
1115 | - <td> |
1116 | - <dl> |
1117 | - <dt> |
1118 | - <a tal:replace="structure product/fmt:link" /> project: |
1119 | - </dt> |
1120 | - <dd title="Project bug supervisor"> |
1121 | - Bug supervisor: |
1122 | - <tal:yes-no |
1123 | - replace="structure product/bug_supervisor/image:boolean" /> |
1124 | - </dd> |
1125 | - <dd title="Project bug tracker" |
1126 | - tal:define="bool product/project|bugtracker|product/bugtracker|product/official_malone"> |
1127 | - Bug tracker: |
1128 | - <tal:yes-no replace="structure bool/image:boolean"/> |
1129 | - </dd> |
1130 | - </dl> |
1131 | - </td> |
1132 | - |
1133 | - <td> |
1134 | - <dl> |
1135 | - <dt style="margin-bottom: 6px;"> |
1136 | - <a tal:replace="structure series/fmt:link" />: |
1137 | - </dt> |
1138 | - <dd title="Series branch"> |
1139 | - Branch: |
1140 | - <tal:yes-no replace="structure series/branch/image:boolean"/> |
1141 | - </dd> |
1142 | - <dd title="Series translations auto import" |
1143 | - tal:condition="translations" |
1144 | - tal:define="bool not:series/translations_autoimport_mode/enumvalue:NO_IMPORT"> |
1145 | - Translations: |
1146 | - <tal:yes-no replace="structure bool/image:boolean"/> |
1147 | - </dd> |
1148 | - </dl> |
1149 | - </td> |
1150 | + <td tal:content="structure package/@@+upstream-connections"/> |
1151 | </tr> |
1152 | </tal:packaging> |
1153 | </tbody> |
1154 | |
1155 | === modified file 'lib/lp/registry/templates/product-packages.pt' |
1156 | --- lib/lp/registry/templates/product-packages.pt 2010-01-11 20:58:42 +0000 |
1157 | +++ lib/lp/registry/templates/product-packages.pt 2010-03-06 06:27:33 +0000 |
1158 | @@ -40,16 +40,11 @@ |
1159 | <th>Distribution series</th> |
1160 | <th>Source package</th> |
1161 | <th>Version</th> |
1162 | - <th style="width: 1em;" |
1163 | - tal:condition="view/can_delete_packaging"> </th> |
1164 | + <th> </th> |
1165 | </tr> |
1166 | </thead> |
1167 | <tbody> |
1168 | - <tr tal:repeat="packaging_dict packagings"> |
1169 | - <tal:packaging_field |
1170 | - define="packaging packaging_dict/packaging; |
1171 | - form_id packaging_dict/form_id; |
1172 | - field packaging_dict/field"> |
1173 | + <tr tal:repeat="packaging packagings"> |
1174 | <td> |
1175 | <a tal:replace="structure packaging/distroseries/distribution/fmt:link" /> |
1176 | </td> |
1177 | @@ -69,17 +64,11 @@ |
1178 | <tal:currentrelease |
1179 | replace="packaging/sourcepackage/currentrelease/version|nothing"> |
1180 | 2.3.4-1 |
1181 | - </tal:currentrelease> |
1182 | - </td> |
1183 | - <td tal:condition="view/can_delete_packaging"> |
1184 | - <form style="display: inline" method="post" |
1185 | - tal:attributes="action request/URL; |
1186 | - id form_id"> |
1187 | - <tal:hidden replace="structure field" /> |
1188 | - <tal:action replace="structure view/renderDeletePackagingAction" /> |
1189 | - </form> |
1190 | - </td> |
1191 | - </tal:packaging_field> |
1192 | + </tal:currentrelease> |
1193 | + </td> |
1194 | + <td> |
1195 | + <a tal:replace="structure packaging/sourcepackage/menu:overview/remove_packaging/fmt:icon" /> |
1196 | + </td> |
1197 | </tr> |
1198 | </tbody> |
1199 | </table> |
1200 | @@ -99,7 +88,7 @@ |
1201 | <h2>Packages by distribution</h2> |
1202 | |
1203 | <tal:distribution repeat="distro view/distro_packaging"> |
1204 | - |
1205 | + |
1206 | <h3> |
1207 | <a tal:attributes="href distro/distribution/fmt:url" |
1208 | tal:content="distro/distribution/title">Ubuntu Linux</a> |
1209 | |
1210 | === modified file 'lib/lp/registry/templates/sourcepackage-portlet-associations.pt' |
1211 | --- lib/lp/registry/templates/sourcepackage-portlet-associations.pt 2010-02-16 19:13:31 +0000 |
1212 | +++ lib/lp/registry/templates/sourcepackage-portlet-associations.pt 2010-03-06 06:27:33 +0000 |
1213 | @@ -10,47 +10,7 @@ |
1214 | <div class="portletBody portletContent" |
1215 | tal:define="series context/productseries"> |
1216 | <tal:has_series condition="series"> |
1217 | - <div id="upstreams" class="two-column-list"> |
1218 | - <dl> |
1219 | - <dt> |
1220 | - <a tal:replace="structure series/product/fmt:link" /> project |
1221 | - </dt> |
1222 | - |
1223 | - <tal:has_pg define="project series/product/project" |
1224 | - condition="project"> |
1225 | - <dd title="Project group"> |
1226 | - Project Group: |
1227 | - <a tal:replace="structure project/fmt:link" /> |
1228 | - </dd> |
1229 | - </tal:has_pg> |
1230 | - |
1231 | - <dd title="Bug supervisor"> |
1232 | - Bug supervisor: |
1233 | - <tal:yes-no replace="structure series/product/bug_supervisor/image:boolean"/> |
1234 | - </dd> |
1235 | - |
1236 | - <dd title="Bug tracker"> |
1237 | - Bug tracker: |
1238 | - <tal:yes-no replace="structure view/has_bugtracker/image:boolean"/> |
1239 | - </dd> |
1240 | - </dl> |
1241 | - <dl> |
1242 | - <dt> |
1243 | - <a tal:replace="structure series/fmt:link" /> |
1244 | - </dt> |
1245 | - <dd title="Series branch"> |
1246 | - Branch: |
1247 | - <tal:yes-no replace="structure series/branch/image:boolean"/> |
1248 | - </dd> |
1249 | - <dd title="Series translations auto import" |
1250 | - tal:condition="context/getTranslationTemplates" |
1251 | - tal:define="bool not:series/translations_autoimport_mode/enumvalue:NO_IMPORT"> |
1252 | - Translations: |
1253 | - <tal:yes-no replace="structure bool/image:boolean"/> |
1254 | - </dd> |
1255 | - </dl> |
1256 | - |
1257 | - </div> |
1258 | + <div tal:content="structure context/@@+upstream-connections"/> |
1259 | |
1260 | <ul class="horizontal"> |
1261 | <li> |
1262 | @@ -58,12 +18,6 @@ |
1263 | tal:attributes="href series/product/menu:overview/packages/fmt:url" |
1264 | >Show upstream links</a> |
1265 | </li> |
1266 | - <li> |
1267 | - <a class="sprite edit" |
1268 | - tal:attributes=" |
1269 | - href context/menu:overview/edit_packaging/fmt:url" |
1270 | - >Change upstream project and series</a> |
1271 | - </li> |
1272 | </ul> |
1273 | </tal:has_series> |
1274 | |
1275 | |
1276 | === added file 'lib/lp/registry/templates/sourcepackage-remove-packaging.pt' |
1277 | --- lib/lp/registry/templates/sourcepackage-remove-packaging.pt 1970-01-01 00:00:00 +0000 |
1278 | +++ lib/lp/registry/templates/sourcepackage-remove-packaging.pt 2010-03-06 06:27:33 +0000 |
1279 | @@ -0,0 +1,34 @@ |
1280 | +<html |
1281 | + xmlns="http://www.w3.org/1999/xhtml" |
1282 | + xmlns:tal="http://xml.zope.org/namespaces/tal" |
1283 | + xmlns:metal="http://xml.zope.org/namespaces/metal" |
1284 | + metal:use-macro="view/macro:page/main_only" |
1285 | +> |
1286 | + |
1287 | +<body> |
1288 | + |
1289 | +<div metal:fill-slot="main"> |
1290 | + |
1291 | + <div tal:condition="not: context/productseries" |
1292 | + class="error"> |
1293 | + This upstream association was deleted already. |
1294 | + </div> |
1295 | + <div tal:condition="context/productseries"> |
1296 | + <div metal:use-macro="context/@@launchpad_form/form"> |
1297 | + <div metal:fill-slot="extra_info"> |
1298 | + <p> |
1299 | + Do you want to remove the upstream link to |
1300 | + <a tal:replace="structure context/productseries/fmt:link"/>? |
1301 | + </p> |
1302 | + <p> |
1303 | + Links from distribution packages to upstream project series let |
1304 | + distribution and upstream maintainers share bugs, patches, and |
1305 | + translations efficiently. |
1306 | + </p> |
1307 | + </div> |
1308 | + </div> |
1309 | + </div> |
1310 | + |
1311 | +</div> |
1312 | +</body> |
1313 | +</html> |
1314 | |
1315 | === added file 'lib/lp/registry/templates/sourcepackage-upstream-connections.pt' |
1316 | --- lib/lp/registry/templates/sourcepackage-upstream-connections.pt 1970-01-01 00:00:00 +0000 |
1317 | +++ lib/lp/registry/templates/sourcepackage-upstream-connections.pt 2010-03-06 06:27:33 +0000 |
1318 | @@ -0,0 +1,63 @@ |
1319 | +<tal:root |
1320 | + xmlns:tal="http://xml.zope.org/namespaces/tal" |
1321 | + xmlns:metal="http://xml.zope.org/namespaces/metal" |
1322 | + xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1323 | + omit-tag=""> |
1324 | + |
1325 | +<tal:comment condition="nothing"> |
1326 | + view.request.annotations['show_edit_buttons'] can be set in views |
1327 | + that include this page to display the edit and remove buttons. |
1328 | +</tal:comment> |
1329 | +<div id="upstreams" tal:define="series context/productseries"> |
1330 | + <dl> |
1331 | + <dd> |
1332 | + <tal:has_pg condition="series/product/project"> |
1333 | + <a tal:replace="structure series/product/project/fmt:link" /> ⇒ |
1334 | + </tal:has_pg> |
1335 | + <a tal:replace="structure series/product/fmt:link" /> ⇒ |
1336 | + <a tal:content="series/name" |
1337 | + tal:attributes="href series/fmt:url" /> |
1338 | + <span tal:condition="request/annotations/show_edit_buttons | nothing"> |
1339 | + <a tal:replace=" |
1340 | + structure context/menu:overview/edit_packaging/fmt:icon" /> |
1341 | + <a tal:replace=" |
1342 | + structure context/menu:overview/remove_packaging/fmt:icon " /> |
1343 | + </span> |
1344 | + </dd> |
1345 | + </dl> |
1346 | + |
1347 | + <style> |
1348 | + #upstream-fields dd { |
1349 | + margin: 0; |
1350 | + padding: 0; |
1351 | + } |
1352 | + </style> |
1353 | + <div id="upstream-fields" class="two-column-list"> |
1354 | + <dl> |
1355 | + <dd title="Bug supervisor"> |
1356 | + Bug supervisor: |
1357 | + <tal:yes-no replace="structure series/product/bug_supervisor/image:boolean"/> |
1358 | + </dd> |
1359 | + |
1360 | + <dd title="Bug tracker"> |
1361 | + Bug tracker: |
1362 | + <tal:yes-no replace="structure view/has_bugtracker/image:boolean"/> |
1363 | + </dd> |
1364 | + </dl> |
1365 | + <dl> |
1366 | + <dd title="Series branch"> |
1367 | + Branch: |
1368 | + <tal:yes-no replace="structure series/branch/image:boolean"/> |
1369 | + </dd> |
1370 | + <dd title="Series translations auto import" |
1371 | + tal:condition="context/getTranslationTemplates" |
1372 | + tal:define="bool not:series/translations_autoimport_mode/enumvalue:NO_IMPORT"> |
1373 | + Translations: |
1374 | + <tal:yes-no replace="structure bool/image:boolean"/> |
1375 | + </dd> |
1376 | + </dl> |
1377 | + |
1378 | + </div> |
1379 | +</div> |
1380 | + |
1381 | +</tal:root> |
1382 | |
1383 | === modified file 'lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt' |
1384 | --- lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt 2010-02-15 10:40:56 +0000 |
1385 | +++ lib/lp/soyuz/stories/soyuz/xx-distroseries-sources.txt 2010-03-06 06:27:33 +0000 |
1386 | @@ -72,11 +72,9 @@ |
1387 | Product, Branches, and Bugs: |
1388 | |
1389 | >>> print extract_text(find_tag_by_id(browser.contents, 'upstreams')) |
1390 | - Mozilla Firefox ... project |
1391 | - Project Group: the Mozilla Project |
1392 | + the Mozilla Project...Mozilla Firefox...trunk... |
1393 | Bug supervisor: no |
1394 | Bug tracker: yes |
1395 | - Mozilla Firefox trunk series |
1396 | Branch: no |
1397 | |
1398 | The user can also download the files for the "currentrelease" (last |
Summary
-------
The sourcepackage index page had an edit button but no way to remove
a link to the upstream via the project series.
The $distroseries/ +packaging page also displayed identical info,
so I moved that into a template that could be shared.
Implementation details ------- ------- -
-------
Moved identical formatting of upstream connection info from packaging. pt and sourcepackage- portlet- associations. pt upstream- connections. pt. Added the (-) removal link project/ series info. lp/registry/ browser/ configure. zcml lp/registry/ templates/ distroseries- packaging. pt lp/registry/ templates/ sourcepackage- portlet- associations. pt lp/registry/ templates/ sourcepackage- upstream- connections. pt
distroseries-
into sourcepackage-
and moved the edit link next to the projectgroup/
lib/
lib/
lib/
lib/
Added $sourcepackage/ +remove- packaging and $sourcepackage/ upstream- connections. lp/registry/ browser/ sourcepackage. py lp/registry/ browser/ tests/sourcepac kage-views. txt lp/registry/ stories/ distroseries/ xx-show- distroseries- packaging. txt lp/registry/ templates/ sourcepackage- remove- packaging. pt
lib/
lib/
lib/
lib/
Drive-by improvement of formatting. lp/registry/ model/productse ries.py lp/registry/ model/sourcepac kage.py
lib/
lib/
Tests
-----
./bin/test -vv -t 'sourcepackage- views.txt| xx-show- distroseries- packaging. txt'
Demo and Q/A
------------
* Open https:/ /launchpad. dev/ubuntu/ hoary/+ source/ evolution /launchpad. dev/ubuntu/ hoary/+ packaging project/ series like so:
* If there is a "Link to Upstream Project" button, use that
since the removal button won't show up without an existing link.
* The Upstream Connections portlet should show:
GNOME ⇒ Evolution ⇒ trunk (E) (-)
where (E) is the edit button, and (-) is the remove button.
* Click on the (-) button.
* Click on the Unlink button.
* The Upstream Connections portlet should now show the
"Link to Upstream Project" button again.
* Open https:/
* The table should now show the projectgroup/
GNOME ⇒ Evolution ⇒ trunk (E) (-)