Merge lp:~edwin-grubbs/launchpad/bug-538469-invalid-upstream-link into lp:launchpad

Proposed by Edwin Grubbs
Status: Merged
Merged at revision: not available
Proposed branch: lp:~edwin-grubbs/launchpad/bug-538469-invalid-upstream-link
Merge into: lp:launchpad
Diff against target: 288 lines (+121/-16)
9 files modified
lib/lp/registry/browser/product.py (+2/-1)
lib/lp/registry/browser/productseries.py (+1/-1)
lib/lp/registry/browser/tests/product-portlet-packages-view.txt (+14/-4)
lib/lp/registry/doc/distribution.txt (+12/-0)
lib/lp/registry/interfaces/distribution.py (+7/-2)
lib/lp/registry/model/distribution.py (+23/-7)
lib/lp/registry/model/productseries.py (+6/-0)
lib/lp/registry/tests/test_distroseries.py (+1/-1)
lib/lp/registry/tests/test_productseries.py (+55/-0)
To merge this branch: bzr merge lp:~edwin-grubbs/launchpad/bug-538469-invalid-upstream-link
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+21947@code.launchpad.net

Description of the change

Summary
-------

When the Packages in Ubuntu portlet on the project index page suggests a
source package, it should only include source packages that have a
publishing history for the current Ubuntu series. This matches the
behavior of $project/+ubuntupkg, which will give you an error if you
try to link to a distroseries without a publishing history for the
given source package.

Implementation details
----------------------

Added assertion to setPackaging() as a failsafe in case another
method of adding a Packaging entry is created.
    lib/lp/registry/model/productseries.py

Added publishing_distroseries parameter to
IDistribution.searchSourcePackage() that filters out source package
names that do not have a publishing history for the given distroseries.
    lib/lp/registry/browser/product.py
    lib/lp/registry/browser/tests/product-portlet-packages-view.txt
    lib/lp/registry/doc/distribution.txt
    lib/lp/registry/interfaces/distribution.txt
    lib/lp/registry/model/distribution.py
    lib/lp/registry/tests/test_productseries.py

Fixed grammar error:
    lib/lp/registry/browser/productseries.py

Fixed docstring:
    lib/lp/registry/tests/test_distroseries.py

Tests
-----

./bin/test -vv -t 'registry.*(product-portlet-packages-view.txt|doc/distribution.txt|test_distroseries|test_productseries)'

Demo and Q/A
------------

* Open https://launchpad.dev/evolution/+packages
  * Remove all the packages.
* Open https://launchpad.dev/evolution
  * The Packages in Ubuntu should still suggest the
    evolution source package, since it has a publishing
    history for the current series, which is hoary.
* Open https://launchpad.dev/ubuntu/grumpy/+admin
  * Change the status to Pre-release Freeze. This status
    has priority over the Development or Current statuses
    when determining the IDistribution.currentseries.
* Open https://launchpad.dev/evolution
  * The evolution source package should no longer be
    suggested.

Lint
----
Linting changed files:
  lib/lp/registry/browser/product.py
  lib/lp/registry/browser/productseries.py
  lib/lp/registry/browser/tests/product-portlet-packages-view.txt
  lib/lp/registry/doc/distribution.txt
  lib/lp/registry/model/distribution.py
  lib/lp/registry/model/productseries.py
  lib/lp/registry/tests/test_distroseries.py
  lib/lp/registry/tests/test_productseries.py

== Pylint notices ==

lib/lp/registry/browser/product.py
    59: [F0401] Unable to import 'lazr.delegates' (No module named delegates)

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Edwin this branch looks very good. Thanks.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/product.py'
2--- lib/lp/registry/browser/product.py 2010-03-09 00:46:37 +0000
3+++ lib/lp/registry/browser/product.py 2010-03-23 15:45:38 +0000
4@@ -983,7 +983,8 @@
5 super(ProductPackagesPortletView, self).setUpFields()
6 ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
7 source_packages = ubuntu.searchSourcePackages(
8- self.context.name, has_packaging=False)
9+ self.context.name, has_packaging=False,
10+ publishing_distroseries=ubuntu.currentseries)
11 # Based upon the matches, create a new vocabulary with
12 # term descriptions that include a link to the source package.
13 self.suggestions = []
14
15=== modified file 'lib/lp/registry/browser/productseries.py'
16--- lib/lp/registry/browser/productseries.py 2010-03-09 22:06:12 +0000
17+++ lib/lp/registry/browser/productseries.py 2010-03-23 15:45:38 +0000
18@@ -442,7 +442,7 @@
19 if sourcepackagename is None:
20 message = "You must choose the source package name."
21 self.setFieldError('sourcepackagename', message)
22- # Do not allow users it create links to unpublished Ubuntu packages.
23+ # Do not allow users to create links to unpublished Ubuntu packages.
24 elif distroseries.distribution.full_functionality:
25 source_package = distroseries.getSourcePackage(sourcepackagename)
26 if source_package.currentrelease is None:
27
28=== modified file 'lib/lp/registry/browser/tests/product-portlet-packages-view.txt'
29--- lib/lp/registry/browser/tests/product-portlet-packages-view.txt 2010-03-01 21:48:15 +0000
30+++ lib/lp/registry/browser/tests/product-portlet-packages-view.txt 2010-03-23 15:45:38 +0000
31@@ -80,11 +80,9 @@
32 >>> print view.suggestions
33 []
34
35-An Ubuntu distribution package that is not in the current series
36-will be suggested.
37+A source package that does NOT have a publishing history
38+for the current Ubuntu series will NOT be suggested.
39
40- >>> distro_package = factory.makeDistributionSourcePackage(
41- ... sourcepackagename=spn, distribution=ubuntu)
42 >>> warty = ubuntu.getSeries('warty')
43 >>> warty == ubuntu.currentseries
44 False
45@@ -96,6 +94,18 @@
46 ... principal=product.owner)
47 >>> for dsp in view.suggestions:
48 ... print dsp.name
49+
50+A source package that does have a publishing history for the current
51+Ubuntu series will be suggested.
52+
53+ >>> spph = factory.makeSourcePackagePublishingHistory(
54+ ... sourcepackagename=spn, distroseries=ubuntu.currentseries)
55+ >>> (ubuntu, product, spn) = updateCache()
56+ >>> view = create_initialized_view(
57+ ... product, name="+portlet-packages",
58+ ... principal=product.owner)
59+ >>> for dsp in view.suggestions:
60+ ... print dsp.name
61 bingo
62
63 And the user is presented with a form to select the distribution
64
65=== modified file 'lib/lp/registry/doc/distribution.txt'
66--- lib/lp/registry/doc/distribution.txt 2010-03-09 16:26:04 +0000
67+++ lib/lp/registry/doc/distribution.txt 2010-03-23 15:45:38 +0000
68@@ -219,6 +219,18 @@
69 DistributionSourcePackage: foobar
70 DistributionSourcePackage: commercialpackage
71
72+searchSourcePackages() also has a publishing_distroseries parameter that
73+it just passes on to searchSourcePackageCaches(), and it restricts the
74+results based on whether the source package has an entry in the
75+SourcePackagePublishingHistory table for the given distroseries.
76+
77+ >>> packages = ubuntu.searchSourcePackages(
78+ ... 'a', publishing_distroseries=ubuntu.currentseries)
79+ >>> for dsp in packages:
80+ ... print "%s: %s" % (dsp.__class__.__name__, dsp.name)
81+ DistributionSourcePackage: netapplet
82+ DistributionSourcePackage: alsa-utils
83+
84
85 === Searching for binary packages ===
86
87
88=== modified file 'lib/lp/registry/interfaces/distribution.py'
89--- lib/lp/registry/interfaces/distribution.py 2010-03-09 16:26:04 +0000
90+++ lib/lp/registry/interfaces/distribution.py 2010-03-23 15:45:38 +0000
91@@ -406,14 +406,16 @@
92 # _schema_circular_imports.py.
93 @operation_returns_collection_of(Interface)
94 @export_read_operation()
95- def searchSourcePackages(text, has_packaging=None):
96+ def searchSourcePackages(
97+ text, has_packaging=None, publishing_distroseries=None):
98 """Search for source packages that correspond to the given text.
99
100 This method just decorates the result of searchSourcePackageCaches()
101 to return DistributionSourcePackages.
102 """
103
104- def searchSourcePackageCaches(text, has_packaging=None):
105+ def searchSourcePackageCaches(
106+ text, has_packaging=None, publishing_distroseries=None):
107 """Search for source packages that correspond to the given text.
108
109 :param text: The text that will be matched.
110@@ -421,6 +423,9 @@
111 packages with no packaging (i.e. no link to the upstream
112 project). False will do the reverse filtering, and None
113 will do no filtering on this field.
114+ :param publishing_distroseries: If it is not None, then
115+ it will filter out source packages that do not have a
116+ publishing history for the given distroseries.
117 :return: A result set containing
118 (DistributionSourcePackageCache, SourcePackageName, rank) tuples
119 ordered by rank.
120
121=== modified file 'lib/lp/registry/model/distribution.py'
122--- lib/lp/registry/model/distribution.py 2010-03-09 16:26:04 +0000
123+++ lib/lp/registry/model/distribution.py 2010-03-23 15:45:38 +0000
124@@ -928,7 +928,7 @@
125 cache.changelog = ' '.join(sorted(sprchangelog))
126
127 def searchSourcePackageCaches(
128- self, text, has_packaging=None):
129+ self, text, has_packaging=None, publishing_distroseries=None):
130 """See `IDistribution`."""
131 # The query below tries exact matching on the source package
132 # name as well; this is because source package names are
133@@ -949,6 +949,18 @@
134 )
135 ]
136
137+ publishing_condition = ''
138+ if publishing_distroseries is not None:
139+ publishing_condition = """
140+ AND EXISTS (
141+ SELECT 1
142+ FROM SourcePackageRelease spr
143+ JOIN SourcePackagePublishingHistory spph
144+ ON spph.sourcepackagerelease = spr.id
145+ WHERE spr.sourcepackagename = SourcePackageName.id
146+ AND spph.distroseries = %d
147+ )""" % (
148+ publishing_distroseries.id)
149
150 packaging_query = """
151 SELECT 1
152@@ -965,24 +977,28 @@
153 # Storm expressions, a 'tuple index out-of-range' error was always
154 # raised.
155 condition = """
156- distribution = %s AND
157- archive IN %s AND
158- (fti @@ ftq(%s) OR
159+ DistributionSourcePackageCache.distribution = %s AND
160+ DistributionSourcePackageCache.archive IN %s AND
161+ (DistributionSourcePackageCache.fti @@ ftq(%s) OR
162 DistributionSourcePackageCache.name ILIKE '%%' || %s || '%%')
163 %s
164+ %s
165 """ % (quote(self), quote(self.all_distro_archive_ids),
166- quote(text), quote_like(text), has_packaging_condition)
167+ quote(text), quote_like(text), has_packaging_condition,
168+ publishing_condition)
169 dsp_caches_with_ranks = store.using(*origin).find(
170 find_spec, condition
171 ).order_by('rank DESC')
172
173 return dsp_caches_with_ranks
174
175- def searchSourcePackages(self, text, has_packaging=None):
176+ def searchSourcePackages(
177+ self, text, has_packaging=None, publishing_distroseries=None):
178 """See `IDistribution`."""
179
180 dsp_caches_with_ranks = self.searchSourcePackageCaches(
181- text, has_packaging=has_packaging)
182+ text, has_packaging=has_packaging,
183+ publishing_distroseries=publishing_distroseries)
184
185 # Create a function that will decorate the resulting
186 # DistributionSourcePackageCaches, converting
187
188=== modified file 'lib/lp/registry/model/productseries.py'
189--- lib/lp/registry/model/productseries.py 2010-03-17 09:13:05 +0000
190+++ lib/lp/registry/model/productseries.py 2010-03-23 15:45:38 +0000
191@@ -378,6 +378,12 @@
192
193 def setPackaging(self, distroseries, sourcepackagename, owner):
194 """See IProductSeries."""
195+ if distroseries.distribution.full_functionality:
196+ source_package = distroseries.getSourcePackage(sourcepackagename)
197+ if source_package.currentrelease is None:
198+ raise AssertionError(
199+ "The source package is not published in %s." %
200+ distroseries.displayname)
201 for pkg in self.packagings:
202 if pkg.distroseries == distroseries:
203 # we have found a matching Packaging record
204
205=== modified file 'lib/lp/registry/tests/test_distroseries.py'
206--- lib/lp/registry/tests/test_distroseries.py 2010-02-25 16:27:34 +0000
207+++ lib/lp/registry/tests/test_distroseries.py 2010-03-23 15:45:38 +0000
208@@ -1,7 +1,7 @@
209 # Copyright 2009 Canonical Ltd. This software is licensed under the
210 # GNU Affero General Public License version 3 (see the file LICENSE).
211
212-"""Tests for Distribution."""
213+"""Tests for distroseries."""
214
215 __metaclass__ = type
216
217
218=== modified file 'lib/lp/registry/tests/test_productseries.py'
219--- lib/lp/registry/tests/test_productseries.py 2010-03-18 13:27:47 +0000
220+++ lib/lp/registry/tests/test_productseries.py 2010-03-23 15:45:38 +0000
221@@ -9,13 +9,68 @@
222
223 from zope.component import getUtility
224
225+from canonical.launchpad.ftests import login
226+from canonical.testing import LaunchpadFunctionalLayer
227 from canonical.testing import ZopelessDatabaseLayer
228+
229 from lp.testing import TestCaseWithFactory
230+from lp.registry.interfaces.distribution import IDistributionSet
231+from lp.registry.interfaces.distroseries import IDistroSeriesSet
232 from lp.registry.interfaces.productseries import IProductSeriesSet
233 from lp.translations.interfaces.translations import (
234 TranslationsBranchImportMode)
235
236
237+class TestProductSeriesSetPackaging(TestCaseWithFactory):
238+ """Test for ProductSeries.setPackaging()."""
239+
240+ layer = LaunchpadFunctionalLayer
241+
242+ def setUp(self):
243+ TestCaseWithFactory.setUp(self)
244+ login('admin@canonical.com')
245+
246+ self.sourcepackagename = self.factory.makeSourcePackageName()
247+
248+ # Set up productseries.
249+ self.person = self.factory.makePerson()
250+ self.product = self.factory.makeProduct(owner=self.person)
251+ self.dev_focus = self.product.development_focus
252+ self.product_series = self.factory.makeProductSeries(self.product)
253+
254+ # Set up distroseries.
255+ self.distroseries_set = getUtility(IDistroSeriesSet)
256+ self.distribution_set = getUtility(IDistributionSet)
257+ self.ubuntu = self.distribution_set.getByName("ubuntu")
258+ self.debian = self.distribution_set.getByName("debian")
259+ self.ubuntu_series = self.factory.makeDistroSeries(self.ubuntu)
260+ self.debian_series = self.factory.makeDistroSeries(self.debian)
261+
262+ def test_setPackaging_without_publishing_history(self):
263+ # Fully functional (ubuntu) distributions are prevented from
264+ # having a packaging entry for a distroseries that does not
265+ # have a source package publishing history.
266+ self.assertRaises(
267+ AssertionError,
268+ self.product_series.setPackaging,
269+ self.ubuntu_series, self.sourcepackagename, self.person)
270+
271+ def test_setPackaging_with_publishing_history(self):
272+ # Add the source package publishing history to the distroseries
273+ # so that the packaging can be added successfully.
274+ self.spph = self.factory.makeSourcePackagePublishingHistory(
275+ sourcepackagename=self.sourcepackagename,
276+ distroseries=self.ubuntu_series)
277+ self.product_series.setPackaging(
278+ self.ubuntu_series, self.sourcepackagename, self.person)
279+
280+ def test_setPackaging_not_ubuntu(self):
281+ # A non-fully-functional distribution does not need a source
282+ # package publishing history before adding the packaging entry.
283+ self.product_series.setPackaging(
284+ self.debian_series, self.sourcepackagename, self.person)
285+
286+
287 class TestProductSeriesDrivers(TestCaseWithFactory):
288 """Test the 'drivers' attribute of a ProductSeries."""
289