Merge lp:~sinzui/launchpad/package-link-validation-3 into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Merged at revision: not available
Proposed branch: lp:~sinzui/launchpad/package-link-validation-3
Merge into: lp:launchpad
Diff against target: 1099 lines
14 files modified
lib/canonical/launchpad/icing/style-3-0.css (+3/-0)
lib/lp/registry/browser/configure.zcml (+0/-3)
lib/lp/registry/browser/distributionsourcepackage.py (+22/-84)
lib/lp/registry/browser/packaging.py (+83/-0)
lib/lp/registry/browser/tests/packaging-views.txt (+237/-0)
lib/lp/registry/browser/tests/productseries-views.txt (+0/-229)
lib/lp/registry/browser/tests/test_packaging.py (+1/-1)
lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.txt (+4/-3)
lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.txt (+7/-5)
lib/lp/registry/templates/distributionsourcepackage-index.pt (+6/-5)
lib/lp/registry/templates/productseries-index.pt (+2/-37)
lib/lp/registry/templates/productseries-portlet-packages.pt (+42/-21)
lib/lp/registry/templates/sourcepackage-index.pt (+1/-3)
lib/lp/registry/templates/sourcepackage-portlet-upstream.pt (+0/-17)
To merge this branch: bzr merge lp:~sinzui/launchpad/package-link-validation-3
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+13844@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (5.5 KiB)

This is my fourth branch to ensure valid upstream package links. There
are many oopses relating to the creation and efforts to fix invalid packages.
The root cause is a bad DB constraint and two views that do not do the
required sanity checks: +addpackage and +ubuntupkg. The views are fixed,
but we have not provided users with a way to delete packaging links
to unbuilt packages. User need to do this to fix their mistakes. We want
to do this so that we can delete the duplicate packages.

This branch refactors portlets and forms so that my next branch can add
delete links to the product +packaing report.

    lp:~sinzui/launchpad/package-link-validation-3
    Diff size: 565
    Launchpad bug: https://bugs.launchpad.net/bugs/276409
    Test command: ./bin/test -vv -t 'lp.(reg|soyuz).*(productseries|packaging)'
    Pre-implementation: flacoste, beuno
    Target release: 3.1.10

== Fixing upstream packaging links ==

Bug 276409 ["Delete Link" is too easy on distribution source package page]
    On the page for a distribution source package with upstream links, such as
    <https://launchpad.net/ubuntu/+source/hwdb-client>, the button for
    deleting an upstream link is quite large and prominent, much more so than
    the button for correcting the link.

    A small improvement would be to change the "Delete Link" button to an
    icon-only button, but that would still leave deleting much easier than
    updating.

    There is little value in making the delete action an editing action
    because the user does not have enough information to select another
    series and sourcepackage. The user will almost always delete. Fixing the
    icon is all that is needed.

== Rules ==

Bug 276409 ["Delete Link" is too easy on distribution source package page]
    * Add a link from the productseries packaing portlet to the product
      +packages view.
    * Extract the delete packaging link rules to a separate view so that
      they can be reused
    * Change the Delete link button to a remove icon.

== QA ==

On staging
    * Visit a bzr series
    * Verify the packaging portlet has a link to All packages
    * Visit the bzr DSP
    * Verify eack packaging link has a the remove and edit icons int this
      order: (-) (/)

== Lint ==

Linting changed files:
  lib/canonical/launchpad/icing/style-3-0.css
  lib/lp/registry/browser/configure.zcml
  lib/lp/registry/browser/distributionsourcepackage.py
  lib/lp/registry/browser/packaging.py
  lib/lp/registry/browser/tests/test_packaging.py
  lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.txt
  lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.txt
  lib/lp/registry/templates/distributionsourcepackage-index.pt
  lib/lp/registry/templates/productseries-index.pt
  lib/lp/registry/templates/productseries-portlet-packages.pt
  lib/lp/registry/templates/sourcepackage-index.pt
  lib/lp/registry/templates/team-portlet-membership.pt

== Test ==

    * lib/lp/registry/browser/tests/test_packaging.py
      * Updated the test to use the submit's name since images do not have
        labels.
    * lib/lp/registry/stories/packaging/xx-distributionsourc...

Read more...

Revision history for this message
Curtis Hovey (sinzui) wrote :
Revision history for this message
Abel Deuring (adeuring) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
--- lib/canonical/launchpad/icing/style-3-0.css 2009-10-22 15:22:42 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css 2009-10-24 20:01:11 +0000
@@ -383,6 +383,9 @@
383form table tbody th {383form table tbody th {
384 font-weight: bold;384 font-weight: bold;
385 }385 }
386input[type='image'] {
387 vertical-align: middle;
388 }
386em {389em {
387 font-style: italic;390 font-style: italic;
388 }391 }
389392
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2009-10-21 01:55:17 +0000
+++ lib/lp/registry/browser/configure.zcml 2009-10-24 20:01:11 +0000
@@ -2001,9 +2001,6 @@
2001 facet="overview"2001 facet="overview"
2002 permission="zope.Public">2002 permission="zope.Public">
2003 <browser:page2003 <browser:page
2004 name="+portlet-upstream"
2005 template="../templates/sourcepackage-portlet-upstream.pt"/>
2006 <browser:page
2007 name="+portlet-releases"2004 name="+portlet-releases"
2008 template="../templates/sourcepackage-portlet-releases.pt"/>2005 template="../templates/sourcepackage-portlet-releases.pt"/>
2009 </browser:pages>2006 </browser:pages>
20102007
=== modified file 'lib/lp/registry/browser/distributionsourcepackage.py'
--- lib/lp/registry/browser/distributionsourcepackage.py 2009-10-23 22:09:49 +0000
+++ lib/lp/registry/browser/distributionsourcepackage.py 2009-10-24 20:01:11 +0000
@@ -20,20 +20,16 @@
20import pytz20import pytz
2121
22from zope.component import getUtility22from zope.component import getUtility
23from zope.formlib import form
24from zope.interface import implements, Interface23from zope.interface import implements, Interface
25from zope.schema import Choice
26from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
2724
28from canonical.cachedproperty import cachedproperty25from canonical.cachedproperty import cachedproperty
29from canonical.launchpad import _
30from canonical.launchpad.interfaces import IBugSet26from canonical.launchpad.interfaces import IBugSet
31from lp.answers.interfaces.questionenums import QuestionStatus27from lp.answers.interfaces.questionenums import QuestionStatus
32from lp.soyuz.interfaces.archive import IArchiveSet28from lp.soyuz.interfaces.archive import IArchiveSet
33from lp.soyuz.interfaces.distributionsourcepackagerelease import (29from lp.soyuz.interfaces.distributionsourcepackagerelease import (
34 IDistributionSourcePackageRelease)30 IDistributionSourcePackageRelease)
35from lp.soyuz.interfaces.packagediff import IPackageDiffSet31from lp.soyuz.interfaces.packagediff import IPackageDiffSet
36from lp.registry.interfaces.packaging import IPackagingUtil32from lp.registry.browser.packaging import PackagingDeleteView
37from lp.registry.interfaces.pocket import pocketsuffix33from lp.registry.interfaces.pocket import pocketsuffix
38from lp.registry.interfaces.product import IDistributionSourcePackage34from lp.registry.interfaces.product import IDistributionSourcePackage
39from lp.bugs.browser.bugtask import BugTargetTraversalMixin35from lp.bugs.browser.bugtask import BugTargetTraversalMixin
@@ -42,7 +38,7 @@
42from canonical.launchpad.browser.structuralsubscription import (38from canonical.launchpad.browser.structuralsubscription import (
43 StructuralSubscriptionTargetTraversalMixin)39 StructuralSubscriptionTargetTraversalMixin)
44from canonical.launchpad.webapp import (40from canonical.launchpad.webapp import (
45 ApplicationMenu, LaunchpadEditFormView, LaunchpadFormView, LaunchpadView,41 ApplicationMenu, LaunchpadEditFormView, LaunchpadView,
46 Link, Navigation, StandardLaunchpadFacets, action, canonical_url,42 Link, Navigation, StandardLaunchpadFacets, action, canonical_url,
47 redirection)43 redirection)
48from canonical.launchpad.webapp.menu import (44from canonical.launchpad.webapp.menu import (
@@ -57,6 +53,7 @@
5753
58class DistributionSourcePackageBreadcrumb(Breadcrumb):54class DistributionSourcePackageBreadcrumb(Breadcrumb):
59 """Builds a breadcrumb for an `IDistributionSourcePackage`."""55 """Builds a breadcrumb for an `IDistributionSourcePackage`."""
56
60 @property57 @property
61 def text(self):58 def text(self):
62 return smartquote('"%s" package') % (59 return smartquote('"%s" package') % (
@@ -71,6 +68,7 @@
7168
7269
73class DistributionSourcePackageLinksMixin:70class DistributionSourcePackageLinksMixin:
71
74 def subscribe(self):72 def subscribe(self):
75 return Link('+subscribe', 'Subscribe to bug mail', icon='edit')73 return Link('+subscribe', 'Subscribe to bug mail', icon='edit')
7674
@@ -184,6 +182,8 @@
184 """Common features to all `DistributionSourcePackage` views."""182 """Common features to all `DistributionSourcePackage` views."""
185183
186 def releases(self):184 def releases(self):
185 """All releases for this `IDistributionSourcePackage`."""
186
187 def not_empty(text):187 def not_empty(text):
188 return (188 return (
189 text is not None and isinstance(text, basestring)189 text is not None and isinstance(text, basestring)
@@ -215,11 +215,10 @@
215 result_set = self.context.getPersonsByEmail(unique_emails)215 result_set = self.context.getPersonsByEmail(unique_emails)
216 # Ignore the persons who want their email addresses hidden.216 # Ignore the persons who want their email addresses hidden.
217 self._person_data = dict(217 self._person_data = dict(
218 [(email.email,person) for (email,person) in result_set218 [(email.email, person) for (email, person) in result_set
219 if not person.hide_email_addresses])219 if not person.hide_email_addresses])
220 else:220 else:
221 self._person_data = None221 self._person_data = None
222
223 # Collate diffs for relevant SourcePackageReleases222 # Collate diffs for relevant SourcePackageReleases
224 pkg_diffs = getUtility(IPackageDiffSet).getDiffsToReleases(sprs)223 pkg_diffs = getUtility(IPackageDiffSet).getDiffsToReleases(sprs)
225 spr_diffs = {}224 spr_diffs = {}
@@ -235,7 +234,7 @@
235234
236235
237class DistributionSourcePackageView(DistributionSourcePackageBaseView,236class DistributionSourcePackageView(DistributionSourcePackageBaseView,
238 LaunchpadFormView):237 PackagingDeleteView):
239 """View class for DistributionSourcePackage."""238 """View class for DistributionSourcePackage."""
240 implements(IDistributionSourcePackageActionMenu)239 implements(IDistributionSourcePackageActionMenu)
241240
@@ -243,17 +242,18 @@
243 def label(self):242 def label(self):
244 return self.context.title243 return self.context.title
245244
246 def setUpFields(self):245 @property
246 def next_url(self):
247 """See `LaunchpadFormView`."""247 """See `LaunchpadFormView`."""
248 # No schema is set in this form, because all fields are created with248 return canonical_url(self.context)
249 # custom vocabularies. So we must not call the inherited setUpField
250 # method.
251 self.form_fields = self._createPackagingField()
252249
253 @property250 @property
254 def can_delete_packaging(self):251 def all_packaging(self):
255 """Whether the user can delete existing packaging links."""252 """See `PackagingDeleteView`."""
256 return self.user is not None253 for sourcepackage in self.context.get_distroseries_packages():
254 packaging = sourcepackage.direct_packaging
255 if packaging is not None:
256 yield packaging
257257
258 @property258 @property
259 def all_published_in_active_distroseries(self):259 def all_published_in_active_distroseries(self):
@@ -267,11 +267,11 @@
267 for pub in self.context.current_publishing_records:267 for pub in self.context.current_publishing_records:
268 if pub.distroseries.active:268 if pub.distroseries.active:
269 entry = {269 entry = {
270 "suite" : (pub.distroseries.name.capitalize() +270 "suite": (pub.distroseries.name.capitalize() +
271 pocketsuffix[pub.pocket]),271 pocketsuffix[pub.pocket]),
272 "description" : "(%s): %s/%s" % (272 "description": "(%s): %s/%s" % (
273 pub.sourcepackagerelease.version,273 pub.sourcepackagerelease.version,
274 pub.component.name, pub.section.name)274 pub.component.name, pub.section.name),
275 }275 }
276 results.append(entry)276 results.append(entry)
277 return results277 return results
@@ -331,12 +331,12 @@
331 versions.append(331 versions.append(
332 "%s (%s)" % (332 "%s (%s)" % (
333 pub.distroseries.displayname,333 pub.distroseries.displayname,
334 pub.source_package_version334 pub.source_package_version,
335 )335 )
336 )336 )
337 archive_versions.append({337 archive_versions.append({
338 'archive': archive,338 'archive': archive,
339 'versions': ", ".join(versions)339 'versions': ", ".join(versions),
340 })340 })
341341
342 return archive_versions342 return archive_versions
@@ -350,68 +350,6 @@
350 self.context.name,350 self.context.name,
351 )351 )
352352
353 def _createPackagingField(self):
354 """Create a field to specify a Packaging association.
355
356 Create a contextual vocabulary that can specify one of the Packaging
357 associated to this DistributionSourcePackage.
358 """
359 terms = []
360 for sourcepackage in self.context.get_distroseries_packages():
361 packaging = sourcepackage.direct_packaging
362 if packaging is None:
363 continue
364 terms.append(SimpleTerm(packaging, packaging.id))
365 return form.Fields(
366 Choice(__name__='packaging', vocabulary=SimpleVocabulary(terms),
367 required=True))
368
369 def _renderHiddenPackagingField(self, packaging):
370 """Render a hidden input that fills in the packaging field."""
371 if not self.can_delete_packaging:
372 return None
373 vocabulary = self.form_fields['packaging'].field.vocabulary
374 return '<input type="hidden" name="field.packaging" value="%s" />' % (
375 vocabulary.getTerm(packaging).token)
376
377 def renderDeletePackagingAction(self):
378 """Render a submit input for the delete_packaging_action."""
379 assert self.can_delete_packaging, 'User cannot delete Packaging.'
380 return ('<input type="submit" class="button" value="Delete Link" '
381 'style="padding: 0pt; font-size: 80%%" '
382 'name="%s"/>' % (self.delete_packaging_action.__name__,))
383
384 def handleDeletePackagingError(self, action, data, errors):
385 """Handle errors on package link deletion.
386
387 If 'packaging' is not set in the form data, we assume that means the
388 provided Packaging id was not found, which should only happen if the
389 same Packaging object was concurrently deleted. In this case, we want
390 to display a more informative error message than the default 'Invalid
391 value'.
392 """
393 if data.get('packaging') is None:
394 self.setFieldError(
395 'packaging',
396 _("This upstream association was deleted already."))
397
398 @action(_("Delete Link"), name='delete_packaging',
399 failure=handleDeletePackagingError)
400 def delete_packaging_action(self, action, data):
401 """Delete a Packaging association."""
402 packaging = data['packaging']
403 productseries = packaging.productseries
404 distroseries = packaging.distroseries
405 getUtility(IPackagingUtil).deletePackaging(
406 productseries, packaging.sourcepackagename, distroseries)
407 self.request.response.addNotification(
408 _("Removed upstream association between ${product} "
409 "${productseries} and ${distroseries}.", mapping=dict(
410 product=productseries.product.displayname,
411 productseries=productseries.displayname,
412 distroseries=distroseries.displayname)))
413 self.next_url = canonical_url(self.context)
414
415 @cachedproperty353 @cachedproperty
416 def active_distroseries_packages(self):354 def active_distroseries_packages(self):
417 """Cached proxy call to context/get_distroseries_packages."""355 """Cached proxy call to context/get_distroseries_packages."""
418356
=== modified file 'lib/lp/registry/browser/packaging.py'
--- lib/lp/registry/browser/packaging.py 2009-10-21 21:16:09 +0000
+++ lib/lp/registry/browser/packaging.py 2009-10-24 20:01:11 +0000
@@ -5,9 +5,13 @@
55
6__all__ = [6__all__ = [
7 'PackagingAddView',7 'PackagingAddView',
8 'PackagingDeleteView',
8 ]9 ]
910
10from zope.component import getUtility11from zope.component import getUtility
12from zope.formlib import form
13from zope.schema import Choice
14from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
1115
12from canonical.launchpad import _16from canonical.launchpad import _
13from lp.registry.interfaces.packaging import (17from lp.registry.interfaces.packaging import (
@@ -77,3 +81,82 @@
77 getUtility(IPackagingUtil).createPackaging(81 getUtility(IPackagingUtil).createPackaging(
78 productseries, data['sourcepackagename'], data['distroseries'],82 productseries, data['sourcepackagename'], data['distroseries'],
79 data['packaging'], owner=self.user)83 data['packaging'], owner=self.user)
84
85
86class PackagingDeleteView(LaunchpadFormView):
87 """A base view that provides packaging link deletion."""
88
89 @property
90 def all_packaging(self):
91 """An iterator of the context's packaging links."""
92 raise NotImplementedError
93
94 def setUpFields(self):
95 """See `LaunchpadFormView`."""
96 # No schema is set in this form, because all fields are created with
97 # custom vocabularies. So we must not call the inherited setUpField
98 # method.
99 self.form_fields = self._createPackagingField()
100
101 @property
102 def can_delete_packaging(self):
103 """Whether the user can delete existing packaging links."""
104 return self.user is not None
105
106 def _createPackagingField(self):
107 """Create a field to specify a Packaging association.
108
109 Create a contextual vocabulary that can specify one of the Packaging
110 associated to this DistributionSourcePackage.
111 """
112 terms = []
113 for packaging in self.all_packaging:
114 terms.append(SimpleTerm(packaging, packaging.id))
115 return form.Fields(
116 Choice(__name__='packaging', vocabulary=SimpleVocabulary(terms),
117 required=True))
118
119 def _renderHiddenPackagingField(self, packaging):
120 """Render a hidden input that fills in the packaging field."""
121 if not self.can_delete_packaging:
122 return None
123 vocabulary = self.form_fields['packaging'].field.vocabulary
124 return '<input type="hidden" name="field.packaging" value="%s" />' % (
125 vocabulary.getTerm(packaging).token)
126
127 def renderDeletePackagingAction(self):
128 """Render a submit input for the delete_packaging_action."""
129 assert self.can_delete_packaging, 'User cannot delete Packaging.'
130 return ('<input type="image" value="Delete Link" '
131 'src="/@@/remove" title="Delete upsteam link" '
132 'name="%s"/>' % self.delete_packaging_action.__name__)
133
134 def handleDeletePackagingError(self, action, data, errors):
135 """Handle errors on package link deletion.
136
137 If 'packaging' is not set in the form data, we assume that means the
138 provided Packaging id was not found, which should only happen if the
139 same Packaging object was concurrently deleted. In this case, we want
140 to display a more informative error message than the default 'Invalid
141 value'.
142 """
143 if data.get('packaging') is None:
144 self.setFieldError(
145 'packaging',
146 _("This upstream association was deleted already."))
147
148 @action(_("Delete Link"), name='delete_packaging',
149 failure=handleDeletePackagingError)
150 def delete_packaging_action(self, action, data):
151 """Delete a Packaging association."""
152 packaging = data['packaging']
153 productseries = packaging.productseries
154 distroseries = packaging.distroseries
155 getUtility(IPackagingUtil).deletePackaging(
156 productseries, packaging.sourcepackagename, distroseries)
157 self.request.response.addNotification(
158 _("Removed upstream association between ${product} "
159 "${productseries} and ${distroseries}.", mapping=dict(
160 product=productseries.product.displayname,
161 productseries=productseries.displayname,
162 distroseries=distroseries.displayname)))
80163
=== added file 'lib/lp/registry/browser/tests/packaging-views.txt'
--- lib/lp/registry/browser/tests/packaging-views.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/packaging-views.txt 2009-10-24 20:01:11 +0000
@@ -0,0 +1,237 @@
1Packaging views
2===============
3
4Packaging links connect a sourcepackage to a distroseries and a productseries.
5
6
7Productseries linking packages
8------------------------------
9
10Distro series sourcepackages can be linked to product series using the
11+addpackage named view.
12
13 >>> from canonical.launchpad.interfaces.launchpad import (
14 ... ILaunchpadCelebrities)
15
16 >>> ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
17 >>> hoary = ubuntu.getSeries('hoary')
18 >>> sourcepackagename = factory.makeSourcePackageName('hot')
19 >>> sourcepackage = factory.makeSourcePackage(
20 ... sourcepackagename=sourcepackagename, distroseries=hoary)
21 >>> product = factory.makeProduct(name="hot", displayname='Hot')
22 >>> productseries = factory.makeProductSeries(
23 ... product=product, name='hotter')
24 >>> productseries.sourcepackages
25 []
26
27The view has a label and requires a distro series, source package name,
28and a packaging contents.
29
30 >>> view = create_view(productseries, '+addpackage')
31 >>> print view.label
32 Packaging of hotter in distributions
33
34 >>> print view.page_title
35 Packaging of hotter in distributions
36
37 >>> print view.field_names
38 ['distroseries', 'sourcepackagename', 'packaging']
39
40 >>> print view.cancel_url
41 http://launchpad.dev/hot/hotter
42
43 >>> form = {
44 ... 'field.distroseries': 'ubuntu/hoary',
45 ... 'field.sourcepackagename': 'hot',
46 ... 'field.packaging': 'Primary Product',
47 ... 'field.actions.continue': 'Continue',
48 ... }
49 >>> view = create_initialized_view(
50 ... productseries, '+addpackage', form=form)
51 >>> view.errors
52 []
53 >>> for package in productseries.sourcepackages:
54 ... print package.name
55 hot
56
57 >>> transaction.commit()
58
59It is an error to link a series to the same package and distro series twice.
60
61 >>> form = {
62 ... 'field.distroseries': 'ubuntu/hoary',
63 ... 'field.sourcepackagename': 'hot',
64 ... 'field.packaging': 'Primary Product',
65 ... 'field.actions.continue': 'Continue',
66 ... }
67 >>> view = create_initialized_view(
68 ... productseries, '+addpackage', form=form)
69 >>> for error in view.errors:
70 ... print error
71 This series is already packaged in Hoary.
72
73Once a distro series sourcepackage is linked to a product series, no other
74product series can link to it.
75
76 >>> other_productseries = factory.makeProductSeries(
77 ... product=product, name='hotest')
78 >>> form = {
79 ... 'field.distroseries': 'ubuntu/hoary',
80 ... 'field.sourcepackagename': 'hot',
81 ... 'field.packaging': 'Primary Product',
82 ... 'field.actions.continue': 'Continue',
83 ... }
84 >>> view = create_initialized_view(
85 ... other_productseries, '+addpackage', form=form)
86 >>> for error in view.errors:
87 ... print error
88 The <a href=".../hoary/+source/hot">hot</a> package in Hoary is already
89 linked to another series.
90
91A source package name must be provided.
92
93 >>> form = {
94 ... 'field.distroseries': 'ubuntu/hoary',
95 ... 'field.sourcepackagename': '',
96 ... 'field.packaging': 'Primary Product',
97 ... 'field.actions.continue': 'Continue',
98 ... }
99 >>> view = create_initialized_view(
100 ... productseries, '+addpackage', form=form)
101 >>> for error in view.errors:
102 ... print error
103 ('sourcepackagename', u'Source Package Name', RequiredMissing())
104 You must choose the source package name.
105
106The +addpackage view provides the default_distroseries property. It is None
107by default, but subclasses may change it.
108
109 >>> print view.default_distroseries
110 None
111
112
113Productseries linking Ubuntu packages
114-------------------------------------
115
116The +ubuntupkg named view is a subclass of the +addpackage named view. It
117allows the user to update the current linked Ubuntu package.
118
119 >>> from lp.registry.browser.packaging import PackagingAddView
120
121 >>> view = create_initialized_view(productseries, '+ubuntupkg')
122 >>> isinstance(view, PackagingAddView)
123 True
124
125 >>> print view.label
126 Ubuntu source packaging
127
128 >>> print view.page_title
129 Ubuntu source packaging
130
131 >>> print view.field_names
132 ['sourcepackagename']
133
134 >>> print view.cancel_url
135 http://launchpad.dev/hot/hotter
136
137The view restricts the packaging to the current Ubuntu series.
138
139 >>> print view.default_distroseries.name
140 hoary
141
142The sourcepackagename is None if the package link was never set. The view's
143packaging history is empty, and the sourcepackagename widget is empty.
144
145 >>> new_productseries = factory.makeProductSeries(
146 ... product=product, name='cold')
147 >>> view = create_initialized_view(new_productseries, '+ubuntupkg')
148
149 >>> print view.default_sourcepackagename
150 None
151
152 >>> print view.widgets.get('sourcepackagename')._getFormValue()
153 <BLANKLINE>
154
155 >>> print view.ubuntu_history
156 []
157
158Series have been packaged in Ubuntu do have the current information and
159a history.
160
161 >>> view = create_initialized_view(productseries, '+ubuntupkg')
162 >>> print view.default_sourcepackagename.name
163 hot
164
165 >>> print view.widgets.get('sourcepackagename')._getFormValue().name
166 hot
167
168 >>> for packaging in view.ubuntu_history:
169 ... print packaging.distroseries.name
170 ... print packaging.sourcepackagename.name
171 hoary hot
172
173The package in the current Ubuntu series can be updated.
174
175 >>> form = {
176 ... 'field.sourcepackagename': 'thunderbird',
177 ... 'field.actions.continue': 'Update',
178 ... }
179 >>> view = create_initialized_view(
180 ... productseries, '+ubuntupkg', form=form)
181 >>> view.errors
182 []
183
184 >>> for packaging in view.ubuntu_history:
185 ... print packaging.distroseries.name
186 ... print packaging.sourcepackagename.name
187 hoary thunderbird
188
189It is not an error to submit the same sourcepackagename information, the
190action is ignored because there is no change
191
192 >>> form = {
193 ... 'field.sourcepackagename': 'thunderbird',
194 ... 'field.actions.continue': 'Update',
195 ... }
196 >>> view = create_initialized_view(
197 ... productseries, '+ubuntupkg', form=form)
198 >>> view.errors
199 []
200
201 >>> for packaging in view.ubuntu_history:
202 ... print packaging.distroseries.name
203 ... print packaging.sourcepackagename.name
204 hoary thunderbird
205
206When the current Ubuntu series changes, the sourcepackagename is not known,
207and a new entry can be added to the packaging history.
208
209 >>> from lp.registry.interfaces.distroseries import DistroSeriesStatus
210
211 >>> login('admin@canonical.com')
212 >>> hoary.status = DistroSeriesStatus.CURRENT
213 >>> grumpy_series = ubuntu.getSeries('grumpy')
214 >>> grumpy_series.status = DistroSeriesStatus.FROZEN
215
216 >>> a_user = factory.makePerson(name="hedgehog")
217 >>> login_person(a_user)
218 >>> form = {
219 ... 'field.sourcepackagename': 'hot',
220 ... 'field.actions.continue': 'Update',
221 ... }
222 >>> view = create_initialized_view(
223 ... productseries, '+ubuntupkg', form=form)
224 >>> view.errors
225 []
226
227 >>> print view.default_distroseries.name
228 grumpy
229
230 >>> print view.default_sourcepackagename
231 None
232
233 >>> for packaging in view.ubuntu_history:
234 ... print packaging.distroseries.name
235 ... print packaging.sourcepackagename.name
236 grumpy hot
237 hoary thunderbird
0238
=== modified file 'lib/lp/registry/browser/tests/productseries-views.txt'
--- lib/lp/registry/browser/tests/productseries-views.txt 2009-10-21 17:09:39 +0000
+++ lib/lp/registry/browser/tests/productseries-views.txt 2009-10-24 20:01:11 +0000
@@ -323,232 +323,3 @@
323 True323 True
324 >>> print productseries.name324 >>> print productseries.name
325 field-rabbit-20090501-193424325 field-rabbit-20090501-193424
326
327
328Linking packages
329----------------
330
331Distro series sourcepackages can be linked to product series using the
332+addpackage named view.
333
334 >>> ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
335 >>> hoary = ubuntu.getSeries('hoary')
336 >>> sourcepackagename = factory.makeSourcePackageName('hot')
337 >>> sourcepackage = factory.makeSourcePackage(
338 ... sourcepackagename=sourcepackagename, distroseries=hoary)
339 >>> product = factory.makeProduct(name="hot", displayname='Hot')
340 >>> productseries = factory.makeProductSeries(
341 ... product=product, name='hotter')
342 >>> productseries.sourcepackages
343 []
344
345The view has a label and requires a distro series, source package name,
346and a packaging contents.
347
348 >>> view = create_view(productseries, '+addpackage')
349 >>> print view.label
350 Packaging of hotter in distributions
351
352 >>> print view.page_title
353 Packaging of hotter in distributions
354
355 >>> print view.field_names
356 ['distroseries', 'sourcepackagename', 'packaging']
357
358 >>> print view.cancel_url
359 http://launchpad.dev/hot/hotter
360
361 >>> form = {
362 ... 'field.distroseries': 'ubuntu/hoary',
363 ... 'field.sourcepackagename': 'hot',
364 ... 'field.packaging': 'Primary Product',
365 ... 'field.actions.continue': 'Continue',
366 ... }
367 >>> view = create_initialized_view(
368 ... productseries, '+addpackage', form=form)
369 >>> view.errors
370 []
371 >>> for package in productseries.sourcepackages:
372 ... print package.name
373 hot
374
375 >>> transaction.commit()
376
377It is an error to link a series to the same package and distro series twice.
378
379 >>> form = {
380 ... 'field.distroseries': 'ubuntu/hoary',
381 ... 'field.sourcepackagename': 'hot',
382 ... 'field.packaging': 'Primary Product',
383 ... 'field.actions.continue': 'Continue',
384 ... }
385 >>> view = create_initialized_view(
386 ... productseries, '+addpackage', form=form)
387 >>> for error in view.errors:
388 ... print error
389 This series is already packaged in Hoary.
390
391Once a distro series sourcepackage is linked to a product series, no other
392product series can link to it.
393
394 >>> other_productseries = factory.makeProductSeries(
395 ... product=product, name='hotest')
396 >>> form = {
397 ... 'field.distroseries': 'ubuntu/hoary',
398 ... 'field.sourcepackagename': 'hot',
399 ... 'field.packaging': 'Primary Product',
400 ... 'field.actions.continue': 'Continue',
401 ... }
402 >>> view = create_initialized_view(
403 ... other_productseries, '+addpackage', form=form)
404 >>> for error in view.errors:
405 ... print error
406 The <a href=".../hoary/+source/hot">hot</a> package in Hoary is already
407 linked to another series.
408
409A source package name must be provided.
410
411 >>> form = {
412 ... 'field.distroseries': 'ubuntu/hoary',
413 ... 'field.sourcepackagename': '',
414 ... 'field.packaging': 'Primary Product',
415 ... 'field.actions.continue': 'Continue',
416 ... }
417 >>> view = create_initialized_view(
418 ... productseries, '+addpackage', form=form)
419 >>> for error in view.errors:
420 ... print error
421 ('sourcepackagename', u'Source Package Name', RequiredMissing())
422 You must choose the source package name.
423
424The +addpackage view provides the default_distroseries property. It is None
425by default, but subclasses may change it.
426
427 >>> print view.default_distroseries
428 None
429
430
431Linking Ubuntu packages
432-----------------------
433
434The +ubuntupkg named view is a subclass of the +addpackage named view. It
435allows the user to update the current linked Ubuntu package.
436
437 >>> from lp.registry.browser.packaging import PackagingAddView
438
439 >>> view = create_initialized_view(productseries, '+ubuntupkg')
440 >>> isinstance(view, PackagingAddView)
441 True
442
443 >>> print view.label
444 Ubuntu source packaging
445
446 >>> print view.page_title
447 Ubuntu source packaging
448
449 >>> print view.field_names
450 ['sourcepackagename']
451
452 >>> print view.cancel_url
453 http://launchpad.dev/hot/hotter
454
455The view restricts the packaging to the current Ubuntu series.
456
457 >>> print view.default_distroseries.name
458 hoary
459
460The sourcepackagename is None if the package link was never set. The view's
461packaging history is empty, and the sourcepackagename widget is empty.
462
463 >>> new_productseries = factory.makeProductSeries(
464 ... product=product, name='cold')
465 >>> view = create_initialized_view(new_productseries, '+ubuntupkg')
466
467 >>> print view.default_sourcepackagename
468 None
469
470 >>> print view.widgets.get('sourcepackagename')._getFormValue()
471 <BLANKLINE>
472
473 >>> print view.ubuntu_history
474 []
475
476Series have been packaged in Ubuntu do have the current information and
477a history.
478
479 >>> view = create_initialized_view(productseries, '+ubuntupkg')
480 >>> print view.default_sourcepackagename.name
481 hot
482
483 >>> print view.widgets.get('sourcepackagename')._getFormValue().name
484 hot
485
486 >>> for packaging in view.ubuntu_history:
487 ... print packaging.distroseries.name
488 ... print packaging.sourcepackagename.name
489 hoary hot
490
491The package in the current Ubuntu series can be updated.
492
493 >>> form = {
494 ... 'field.sourcepackagename': 'thunderbird',
495 ... 'field.actions.continue': 'Update',
496 ... }
497 >>> view = create_initialized_view(
498 ... productseries, '+ubuntupkg', form=form)
499 >>> view.errors
500 []
501
502 >>> for packaging in view.ubuntu_history:
503 ... print packaging.distroseries.name
504 ... print packaging.sourcepackagename.name
505 hoary thunderbird
506
507It is not an error to submit the same sourcepackagename information, the
508action is ignored because there is no change
509
510 >>> form = {
511 ... 'field.sourcepackagename': 'thunderbird',
512 ... 'field.actions.continue': 'Update',
513 ... }
514 >>> view = create_initialized_view(
515 ... productseries, '+ubuntupkg', form=form)
516 >>> view.errors
517 []
518
519 >>> for packaging in view.ubuntu_history:
520 ... print packaging.distroseries.name
521 ... print packaging.sourcepackagename.name
522 hoary thunderbird
523
524When the current Ubuntu series changes, the sourcepackagename is not known,
525and a new entry can be added to the packaging history.
526
527 >>> from lp.registry.interfaces.distroseries import DistroSeriesStatus
528
529 >>> login('admin@canonical.com')
530 >>> hoary.status = DistroSeriesStatus.CURRENT
531 >>> grumpy_series = ubuntu.getSeries('grumpy')
532 >>> grumpy_series.status = DistroSeriesStatus.FROZEN
533
534 >>> login_person(a_user)
535 >>> form = {
536 ... 'field.sourcepackagename': 'hot',
537 ... 'field.actions.continue': 'Update',
538 ... }
539 >>> view = create_initialized_view(
540 ... productseries, '+ubuntupkg', form=form)
541 >>> view.errors
542 []
543
544 >>> print view.default_distroseries.name
545 grumpy
546
547 >>> print view.default_sourcepackagename
548 None
549
550 >>> for packaging in view.ubuntu_history:
551 ... print packaging.distroseries.name
552 ... print packaging.sourcepackagename.name
553 grumpy hot
554 hoary thunderbird
555326
=== modified file 'lib/lp/registry/browser/tests/test_packaging.py'
--- lib/lp/registry/browser/tests/test_packaging.py 2009-10-16 15:00:55 +0000
+++ lib/lp/registry/browser/tests/test_packaging.py 2009-10-24 20:01:11 +0000
@@ -56,7 +56,7 @@
56 user_browser = self.user_browser56 user_browser = self.user_browser
57 user_browser.open('http://launchpad.dev/ubuntu/+source/alsa-utils')57 user_browser.open('http://launchpad.dev/ubuntu/+source/alsa-utils')
58 form = user_browser.getForm("delete_warty_alsa-utils_trunk")58 form = user_browser.getForm("delete_warty_alsa-utils_trunk")
59 form.getControl("Delete Link").click()59 form.getControl(name="field.actions.delete_packaging").click()
60 # Check that the change was committed.60 # Check that the change was committed.
61 login('no-priv@canonical.com')61 login('no-priv@canonical.com')
62 self.assertFalse(packaging_util.packagingEntryExists(62 self.assertFalse(packaging_util.packagingEntryExists(
6363
=== renamed file 'lib/lp/registry/stories/distribution/xx-distributionsourcepackage-packaging-concurrent-deletion.txt' => 'lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.txt'
--- lib/lp/registry/stories/distribution/xx-distributionsourcepackage-packaging-concurrent-deletion.txt 2009-08-27 20:08:53 +0000
+++ lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.txt 2009-10-24 20:01:11 +0000
@@ -1,4 +1,5 @@
1=== Concurrent Deletion of Packaging ===1Concurrent Deletion of Packaging
2================================
23
3When two browsers are used to concurrently delete the same packaging4When two browsers are used to concurrently delete the same packaging
4association, only one of them can succeed. The other one does not oops5association, only one of them can succeed. The other one does not oops
@@ -16,7 +17,7 @@
16deletion succeeds and the usual informational message is displayed.17deletion succeeds and the usual informational message is displayed.
1718
18 >>> form = first_browser.getForm("delete_warty_alsa-utils_trunk")19 >>> form = first_browser.getForm("delete_warty_alsa-utils_trunk")
19 >>> form.getControl("Delete Link").click()20 >>> form.getControl(name="field.actions.delete_packaging").click()
20 >>> content = first_browser.contents21 >>> content = first_browser.contents
21 >>> for tag in find_tags_by_class(content, 'error'):22 >>> for tag in find_tags_by_class(content, 'error'):
22 ... print extract_text(tag)23 ... print extract_text(tag)
@@ -37,7 +38,7 @@
37-- David Allouche 2007-12-0738-- David Allouche 2007-12-07
3839
39 >>> form = second_browser.getForm("delete_warty_alsa-utils_trunk")40 >>> form = second_browser.getForm("delete_warty_alsa-utils_trunk")
40 >>> form.getControl("Delete Link").click()41 >>> form.getControl(name="field.actions.delete_packaging").click()
41 >>> content = second_browser.contents42 >>> content = second_browser.contents
42 >>> for tag in find_tags_by_class(content, 'informational'):43 >>> for tag in find_tags_by_class(content, 'informational'):
43 ... print extract_text(tag)44 ... print extract_text(tag)
4445
=== renamed file 'lib/lp/registry/stories/distribution/xx-distributionsourcepackage-packaging.txt' => 'lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.txt'
--- lib/lp/registry/stories/distribution/xx-distributionsourcepackage-packaging.txt 2009-10-21 01:55:17 +0000
+++ lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.txt 2009-10-24 20:01:11 +0000
@@ -1,4 +1,5 @@
1== Distribution Packaging ==1Distribution Packaging
2======================
23
3The packaging records for a source package in a given distribution are4The packaging records for a source package in a given distribution are
4displayed on the page of the distribution source package.5displayed on the page of the distribution source package.
@@ -18,15 +19,16 @@
18 1.0.9a-4 release (main) ... weeks ago19 1.0.9a-4 release (main) ... weeks ago
1920
2021
21=== Delete Link Button ===22Delete Link Button
23------------------
2224
23A button is displayed to authenticated users to delete existing25A button is displayed to authenticated users to delete existing
24packaging links.26packaging links.
2527
26 >>> user_browser.open('http://launchpad.dev/ubuntu/+source/alsa-utils')28 >>> user_browser.open('http://launchpad.dev/ubuntu/+source/alsa-utils')
27 >>> form = user_browser.getForm("delete_warty_alsa-utils_trunk")29 >>> form = user_browser.getForm("delete_warty_alsa-utils_trunk")
28 >>> print form.getControl("Delete Link")30 >>> print form.getControl(name="field.actions.delete_packaging")
29 <SubmitControl name='field.actions.delete_packaging' type='submit'>31 <ImageControl name='field.actions.delete_packaging' type='image'>
3032
31This button is not displayed to anonymous users.33This button is not displayed to anonymous users.
3234
@@ -39,7 +41,7 @@
39Clicking this button deletes the corresponding packaging association.41Clicking this button deletes the corresponding packaging association.
4042
41 >>> form = user_browser.getForm("delete_warty_alsa-utils_trunk")43 >>> form = user_browser.getForm("delete_warty_alsa-utils_trunk")
42 >>> form.getControl("Delete Link").click()44 >>> form.getControl(name="field.actions.delete_packaging").click()
43 >>> content = user_browser.contents45 >>> content = user_browser.contents
44 >>> for tag in find_tags_by_class(content, 'error'):46 >>> for tag in find_tags_by_class(content, 'error'):
45 ... print extract_text(tag)47 ... print extract_text(tag)
4648
=== modified file 'lib/lp/registry/templates/distributionsourcepackage-index.pt'
--- lib/lp/registry/templates/distributionsourcepackage-index.pt 2009-09-25 17:00:20 +0000
+++ lib/lp/registry/templates/distributionsourcepackage-index.pt 2009-10-24 20:01:11 +0000
@@ -149,10 +149,6 @@
149 <tal:has_packaging condition="row/packaging">149 <tal:has_packaging condition="row/packaging">
150 <img tal:replace="structure row/packaging/productseries/image:icon"/>150 <img tal:replace="structure row/packaging/productseries/image:icon"/>
151 <a tal:replace="structure row/packaging/productseries/fmt:link"/>151 <a tal:replace="structure row/packaging/productseries/fmt:link"/>
152 <a tal:attributes="
153 href row/series_package/menu:overview/edit_packaging/url">
154 <img src="/@@/edit"/>
155 </a>
156 <form style="display: inline"152 <form style="display: inline"
157 method="POST"153 method="POST"
158 tal:condition="row/hidden_packaging_field"154 tal:condition="row/hidden_packaging_field"
@@ -161,6 +157,11 @@
161 <tal:hidden replace="structure row/hidden_packaging_field" />157 <tal:hidden replace="structure row/hidden_packaging_field" />
162 <tal:action replace="structure view/renderDeletePackagingAction" />158 <tal:action replace="structure view/renderDeletePackagingAction" />
163 </form>159 </form>
160 <a tal:attributes="
161 href row/series_package/menu:overview/edit_packaging/url;
162 title string:Edit upsteam link">
163 <img src="/@@/edit"/>
164 </a>
164 </tal:has_packaging>165 </tal:has_packaging>
165 </div><!--float right-->166 </div><!--float right-->
166 </td>167 </td>
@@ -215,7 +216,7 @@
215 </tal:rows>216 </tal:rows>
216 </table>217 </table>
217 <script218 <script
218 tal:content="string:LP.client.cache['archive_context_url'] = '${archive/fmt:url}';"/>219 tal:content="string:LP.client.cache['archive_context_url'] = '${archive/fmt:url}';"></script>
219 <metal:js use-macro="archive/@@+macros/expandable-table-js"/>220 <metal:js use-macro="archive/@@+macros/expandable-table-js"/>
220221
221 <p style="float:right; padding-top:1em">222 <p style="float:right; padding-top:1em">
222223
=== modified file 'lib/lp/registry/templates/productseries-index.pt'
--- lib/lp/registry/templates/productseries-index.pt 2009-10-20 16:13:57 +0000
+++ lib/lp/registry/templates/productseries-index.pt 2009-10-24 20:01:11 +0000
@@ -24,8 +24,7 @@
24 </tal:heading>24 </tal:heading>
2525
26 <div metal:fill-slot="main"26 <div metal:fill-slot="main"
27 tal:define="overview_menu context/menu:overview;27 tal:define="overview_menu context/menu:overview">
28 sourcepackages context/sourcepackages">
2928
30 <div class="top-portlet">29 <div class="top-portlet">
31 <div id="description"30 <div id="description"
@@ -144,41 +143,7 @@
144 tal:content="structure context/@@+portlet-latestbugs"143 tal:content="structure context/@@+portlet-latestbugs"
145 tal:condition="context/@@+get-involved/official_malone" />144 tal:condition="context/@@+get-involved/official_malone" />
146145
147 <div class="portlet">146 <div tal:content="structure context/@@+portlet-packages" />
148 <h2>Distribution packaging</h2>
149
150 <p id="distribution-packaging-explanation">
151 <tal:distro condition="sourcepackages">
152 This series is packaged in the following distribution series:
153 </tal:distro>
154 <tal:no-distro condition="not: sourcepackages">
155 This series is not packaged in any distribution series.
156 </tal:no-distro>
157 </p>
158
159 <ul id="distribution-packaging"
160 tal:condition="sourcepackages">
161 <li
162 tal:repeat="package sourcepackages">
163 <a class="sprite package-source"
164 tal:attributes="href package/fmt:url"><tal:distro
165 tal:content="package/distroseries/distribution/displayname">
166 Ubuntu</tal:distro>
167 <tal:series tal:replace="package/distroseries/displayname">
168 Warty</tal:series>
169 <tal:package tal:content="package/name">firefox</tal:package></a>
170 </li>
171 </ul>
172
173 <ul class="horizontal">
174 <li>
175 <a tal:replace="structure overview_menu/ubuntupkg/fmt:icon-link" />
176 </li>
177 <li>
178 <a tal:replace="structure overview_menu/add_package/fmt:icon-link" />
179 </li>
180 </ul>
181 </div>
182 </div>147 </div>
183148
184 <div class="yui-u">149 <div class="yui-u">
185150
=== modified file 'lib/lp/registry/templates/productseries-portlet-packages.pt'
--- lib/lp/registry/templates/productseries-portlet-packages.pt 2009-07-17 17:59:07 +0000
+++ lib/lp/registry/templates/productseries-portlet-packages.pt 2009-10-24 20:01:11 +0000
@@ -1,25 +1,46 @@
1<tal:root1<div class="portlet" id="portlet-packages"
2 xmlns:tal="http://xml.zope.org/namespaces/tal"2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 omit-tag="">5 tal:define="sourcepackages context/sourcepackages">
66 <h2>
7<div class="portlet" id="portlet-packages"7 <span class="see-all">
8 tal:condition="context/sourcepackages">8 <a tal:attributes="href context/product/menu:overview/packages/fmt:url">
9 <h2>Distribution packaging</h2>9 All packages
1010 </a>
11 <div class="portletBody portletContent ">11 </span>
12 This series is packaged in the following places:12 Distribution packaging
13 <ul>13 </h2>
14 <li tal:repeat="package context/sourcepackages">14
15 <span tal:replace="package/distroseries/distribution/name"15 <p id="distribution-packaging-explanation">
16 />16 <tal:distro condition="sourcepackages">
17 <span tal:replace="package/distroseries/name"17 This series is packaged in the following distribution series:
18 />18 </tal:distro>
19 <a tal:content="package/name"19 <tal:no-distro condition="not: sourcepackages">
20 tal:attributes="href package/fmt:url">apache</a>20 This series is not packaged in any distribution series.
21 </li>21 </tal:no-distro>
22 </ul>22 </p>
23 </div>23
24 <ul id="distribution-packaging"
25 tal:condition="sourcepackages">
26 <li
27 tal:repeat="package sourcepackages">
28 <a class="sprite package-source"
29 tal:attributes="href package/fmt:url"><tal:distro
30 tal:content="package/distroseries/distribution/displayname">
31 Ubuntu</tal:distro>
32 <tal:series tal:replace="package/distroseries/displayname">
33 Warty</tal:series>
34 <tal:package tal:content="package/name">firefox</tal:package></a>
35 </li>
36 </ul>
37
38 <ul class="horizontal">
39 <li>
40 <a tal:replace="structure context/menu:overview/ubuntupkg/fmt:icon-link" />
41 </li>
42 <li>
43 <a tal:replace="structure context/menu:overview/add_package/fmt:icon-link" />
44 </li>
45 </ul>
24</div>46</div>
25</tal:root>
2647
=== modified file 'lib/lp/registry/templates/sourcepackage-index.pt'
--- lib/lp/registry/templates/sourcepackage-index.pt 2009-09-25 17:00:20 +0000
+++ lib/lp/registry/templates/sourcepackage-index.pt 2009-10-24 20:01:11 +0000
@@ -114,9 +114,7 @@
114 </div>114 </div>
115115
116 <div class="yui-u">116 <div class="yui-u">
117 <div tal:replace="structure context/@@+portlet-upstream" />117 <div class="portlet" tal:condition="current">
118
119 <div tal:condition="current">
120 <h2>Binary packages</h2>118 <h2>Binary packages</h2>
121119
122 <div id="binaries" tal:define="binaries view/binaries">120 <div id="binaries" tal:define="binaries view/binaries">
123121
=== removed file 'lib/lp/registry/templates/sourcepackage-portlet-upstream.pt'
--- lib/lp/registry/templates/sourcepackage-portlet-upstream.pt 2009-07-17 17:59:07 +0000
+++ lib/lp/registry/templates/sourcepackage-portlet-upstream.pt 1970-01-01 00:00:00 +0000
@@ -1,17 +0,0 @@
1<tal:root
2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 omit-tag="">
6
7<div class="portlet" id="portlet-upstream">
8 <h2>Upstream release series</h2>
9 <div class="portletBody portletContent">
10
11 <a tal:condition="context/productseries"
12 tal:content="context/productseries/title"
13 tal:attributes="href context/productseries/fmt:url">apache head</a>
14 <i tal:condition="not: context/productseries">None linked.</i>
15 </div>
16</div>
17</tal:root>