Merge lp:~allenap/launchpad/selected-differences-vocab-bug-817408 into lp:launchpad

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 13579
Proposed branch: lp:~allenap/launchpad/selected-differences-vocab-bug-817408
Merge into: lp:launchpad
Diff against target: 348 lines (+228/-20)
4 files modified
lib/lp/registry/browser/distroseries.py (+1/-10)
lib/lp/registry/tests/test_distroseries_vocabularies.py (+138/-7)
lib/lp/registry/vocabularies.py (+77/-3)
lib/lp/registry/vocabularies.zcml (+12/-0)
To merge this branch: bzr merge lp:~allenap/launchpad/selected-differences-vocab-bug-817408
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+70048@code.launchpad.net

Commit message

[r=benji][bug=817408] Use a new DistroSeriesDifferences vocabulary in place of the hand-crafted one in IDifferencesFormSchema.

Description of the change

This adds a new vocabulary called "DistroSeriesDifferences" which represents all DistroSeriesDifference records for a given derived series. This is then used in place of the hand-crafted vocabulary in IDifferencesFormSchema.

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

This looks good. Definitely an improvement.

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/distroseries.py'
2--- lib/lp/registry/browser/distroseries.py 2011-07-29 20:05:40 +0000
3+++ lib/lp/registry/browser/distroseries.py 2011-08-01 16:44:42 +0000
4@@ -792,7 +792,7 @@
5
6 selected_differences = List(
7 title=_('Selected differences'),
8- value_type=Choice(vocabulary=SimpleVocabulary([])),
9+ value_type=Choice(vocabulary="DistroSeriesDifferences"),
10 description=_("Select the differences for syncing."),
11 required=True)
12
13@@ -859,15 +859,6 @@
14 self.form_fields = (
15 self.setupPackageFilterRadio() +
16 self.form_fields)
17- check_permission('launchpad.Edit', self.context)
18- terms = [
19- SimpleTerm(diff, diff.id)
20- for diff in self.cached_differences.batch]
21- diffs_vocabulary = SimpleVocabulary(terms)
22- # XXX: GavinPanella 2011-07-13 bug=809985: Good thing the app servers
23- # are running single threaded...
24- choice = self.form_fields['selected_differences'].field.value_type
25- choice.vocabulary = diffs_vocabulary
26
27 def _sync_sources(self, action, data):
28 """Synchronise packages from the parent series to this one."""
29
30=== modified file 'lib/lp/registry/tests/test_distroseries_vocabularies.py'
31--- lib/lp/registry/tests/test_distroseries_vocabularies.py 2011-07-08 14:09:11 +0000
32+++ lib/lp/registry/tests/test_distroseries_vocabularies.py 2011-08-01 16:44:42 +0000
33@@ -11,21 +11,34 @@
34 )
35
36 from pytz import utc
37-from testtools.matchers import Equals
38+from testtools.matchers import (
39+ Equals,
40+ Not,
41+ )
42 from zope.component import getUtility
43-from zope.schema.interfaces import IVocabularyFactory
44+from zope.schema.interfaces import (
45+ ITokenizedTerm,
46+ IVocabularyFactory,
47+ )
48 from zope.security.proxy import removeSecurityProxy
49
50 from canonical.database.sqlbase import flush_database_caches
51 from canonical.launchpad.webapp.vocabulary import IHugeVocabulary
52 from canonical.testing.layers import DatabaseFunctionalLayer
53 from lp.registry.interfaces.distroseries import IDistroSeriesSet
54-from lp.registry.vocabularies import DistroSeriesDerivationVocabulary
55+from lp.registry.vocabularies import (
56+ DistroSeriesDerivationVocabulary,
57+ DistroSeriesDifferencesVocabulary,
58+ )
59 from lp.testing import (
60 StormStatementRecorder,
61 TestCaseWithFactory,
62 )
63-from lp.testing.matchers import HasQueryCount
64+from lp.testing.matchers import (
65+ Contains,
66+ HasQueryCount,
67+ Provides,
68+ )
69
70
71 class TestDistroSeriesDerivationVocabulary(TestCaseWithFactory):
72@@ -47,9 +60,8 @@
73 getUtility(IVocabularyFactory, name="DistroSeriesDerivation"),
74 DistroSeriesDerivationVocabulary)
75
76- def test_interfaces(self):
77- # DistroSeriesDerivationVocabulary instances provide
78- # IVocabulary and IVocabularyTokenized.
79+ def test_interface(self):
80+ # DistroSeriesDerivationVocabulary instances provide IHugeVocabulary.
81 distroseries = self.factory.makeDistroSeries()
82 vocabulary = DistroSeriesDerivationVocabulary(distroseries)
83 self.assertProvides(vocabulary, IHugeVocabulary)
84@@ -248,3 +260,122 @@
85 self.assertContentEqual(
86 expected_distroseries,
87 observed_distroseries)
88+
89+
90+class TestDistroSeriesDifferencesVocabulary(TestCaseWithFactory):
91+ """Tests for `DistroSeriesDifferencesVocabulary`."""
92+
93+ layer = DatabaseFunctionalLayer
94+
95+ def test_registration(self):
96+ # DistroSeriesDifferencesVocabulary is registered as a named utility
97+ # for IVocabularyFactory.
98+ self.assertEqual(
99+ getUtility(IVocabularyFactory, name="DistroSeriesDifferences"),
100+ DistroSeriesDifferencesVocabulary)
101+
102+ def test_interface(self):
103+ # DistroSeriesDifferencesVocabulary instances provide IHugeVocabulary.
104+ distroseries = self.factory.makeDistroSeries()
105+ vocabulary = DistroSeriesDifferencesVocabulary(distroseries)
106+ self.assertProvides(vocabulary, IHugeVocabulary)
107+
108+ def test_non_derived_distroseries(self):
109+ # The vocabulary is empty for a non-derived series.
110+ distroseries = self.factory.makeDistroSeries()
111+ vocabulary = DistroSeriesDifferencesVocabulary(distroseries)
112+ self.assertContentEqual([], vocabulary)
113+
114+ def test_derived_distroseries(self):
115+ # The vocabulary contains all DSDs for a derived series.
116+ distroseries = self.factory.makeDistroSeries()
117+ dsds = [
118+ self.factory.makeDistroSeriesDifference(
119+ derived_series=distroseries),
120+ self.factory.makeDistroSeriesDifference(
121+ derived_series=distroseries),
122+ ]
123+ vocabulary = DistroSeriesDifferencesVocabulary(distroseries)
124+ self.assertContentEqual(
125+ dsds, (term.value for term in vocabulary))
126+
127+ def test_derived_distroseries_not_other_distroseries(self):
128+ # The vocabulary contains all DSDs for a derived series and not for
129+ # another series.
130+ distroseries1 = self.factory.makeDistroSeries()
131+ distroseries2 = self.factory.makeDistroSeries()
132+ dsds = [
133+ self.factory.makeDistroSeriesDifference(
134+ derived_series=distroseries1),
135+ self.factory.makeDistroSeriesDifference(
136+ derived_series=distroseries1),
137+ self.factory.makeDistroSeriesDifference(
138+ derived_series=distroseries2),
139+ self.factory.makeDistroSeriesDifference(
140+ derived_series=distroseries2),
141+ ]
142+ vocabulary = DistroSeriesDifferencesVocabulary(distroseries1)
143+ self.assertContentEqual(
144+ (dsd for dsd in dsds if dsd.derived_series == distroseries1),
145+ (term.value for term in vocabulary))
146+
147+ def test_contains_difference(self):
148+ # The vocabulary can be tested for membership.
149+ difference = self.factory.makeDistroSeriesDifference()
150+ vocabulary = DistroSeriesDifferencesVocabulary(
151+ difference.derived_series)
152+ self.assertThat(vocabulary, Contains(difference))
153+
154+ def test_does_not_contain_difference(self):
155+ # The vocabulary can be tested for non-membership.
156+ difference = self.factory.makeDistroSeriesDifference()
157+ vocabulary = DistroSeriesDifferencesVocabulary(
158+ self.factory.makeDistroSeries())
159+ self.assertThat(vocabulary, Not(Contains(difference)))
160+
161+ def test_does_not_contain_something_else(self):
162+ # The vocabulary can be tested for non-membership of something that's
163+ # not a DistroSeriesDifference.
164+ distroseries = self.factory.makeDistroSeries()
165+ vocabulary = DistroSeriesDifferencesVocabulary(distroseries)
166+ self.assertThat(vocabulary, Not(Contains("foobar")))
167+
168+ def test_size(self):
169+ # The vocabulary can report its size.
170+ difference = self.factory.makeDistroSeriesDifference()
171+ vocabulary = DistroSeriesDifferencesVocabulary(
172+ difference.derived_series)
173+ self.assertEqual(1, len(vocabulary))
174+
175+ def test_getTerm(self):
176+ # A term can be obtained from a given value.
177+ difference = self.factory.makeDistroSeriesDifference()
178+ vocabulary = DistroSeriesDifferencesVocabulary(
179+ difference.derived_series)
180+ term = vocabulary.getTerm(difference)
181+ self.assertThat(term, Provides(ITokenizedTerm))
182+ self.assertEqual(difference, term.value)
183+ self.assertEqual(str(difference.id), term.token)
184+
185+ def test_getTermByToken(self):
186+ # A term can be obtained from a given token.
187+ difference = self.factory.makeDistroSeriesDifference()
188+ vocabulary = DistroSeriesDifferencesVocabulary(
189+ difference.derived_series)
190+ term = vocabulary.getTermByToken(str(difference.id))
191+ self.assertEqual(difference, term.value)
192+
193+ def test_getTermByToken_not_found(self):
194+ # LookupError is raised when the token cannot be found.
195+ distroseries = self.factory.makeDistroSeries()
196+ difference = self.factory.makeDistroSeriesDifference()
197+ vocabulary = DistroSeriesDifferencesVocabulary(distroseries)
198+ self.assertRaises(
199+ LookupError, vocabulary.getTermByToken, str(difference.id))
200+
201+ def test_getTermByToken_invalid(self):
202+ # LookupError is raised when the token is not valid (i.e. a string
203+ # containing only digits).
204+ distroseries = self.factory.makeDistroSeries()
205+ vocabulary = DistroSeriesDifferencesVocabulary(distroseries)
206+ self.assertRaises(LookupError, vocabulary.getTermByToken, "foobar")
207
208=== modified file 'lib/lp/registry/vocabularies.py'
209--- lib/lp/registry/vocabularies.py 2011-07-26 19:09:48 +0000
210+++ lib/lp/registry/vocabularies.py 2011-08-01 16:44:42 +0000
211@@ -33,6 +33,7 @@
212 'DistributionSourcePackageVocabulary',
213 'DistributionVocabulary',
214 'DistroSeriesDerivationVocabulary',
215+ 'DistroSeriesDifferencesVocabulary',
216 'DistroSeriesVocabulary',
217 'FeaturedProjectVocabulary',
218 'FilteredDistroSeriesVocabulary',
219@@ -138,6 +139,9 @@
220 IDistributionSourcePackage,
221 )
222 from lp.registry.interfaces.distroseries import IDistroSeries
223+from lp.registry.interfaces.distroseriesdifference import (
224+ IDistroSeriesDifference,
225+ )
226 from lp.registry.interfaces.mailinglist import (
227 IMailingListSet,
228 MailingListStatus,
229@@ -167,6 +171,7 @@
230 from lp.registry.interfaces.sourcepackage import ISourcePackage
231 from lp.registry.model.distribution import Distribution
232 from lp.registry.model.distroseries import DistroSeries
233+from lp.registry.model.distroseriesdifference import DistroSeriesDifference
234 from lp.registry.model.distroseriesparent import DistroSeriesParent
235 from lp.registry.model.featuredproject import FeaturedProject
236 from lp.registry.model.karma import KarmaCategory
237@@ -194,9 +199,7 @@
238 from lp.soyuz.model.binarypackagename import BinaryPackageName
239 from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
240 from lp.soyuz.model.distroarchseries import DistroArchSeries
241-from lp.soyuz.model.publishing import (
242- SourcePackagePublishingHistory,
243- )
244+from lp.soyuz.model.publishing import SourcePackagePublishingHistory
245 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
246
247
248@@ -1866,6 +1869,77 @@
249 return self.find_terms(where)
250
251
252+class DistroSeriesDifferencesVocabulary:
253+ """A vocabulary source for differences relating to a series.
254+
255+ Specifically, all `DistroSeriesDifference`s relating to a derived series.
256+ """
257+
258+ implements(IHugeVocabulary)
259+
260+ displayname = "Choose a difference"
261+ step_title = 'Search'
262+
263+ def __init__(self, context):
264+ """Create a new vocabulary for the context.
265+
266+ :type context: `IDistroSeries`.
267+ """
268+ assert IDistroSeries.providedBy(context)
269+ self.distroseries = context
270+
271+ def __len__(self):
272+ """See `IIterableVocabulary`."""
273+ return self.searchForDifferences().count()
274+
275+ def __iter__(self):
276+ """See `IIterableVocabulary`."""
277+ for difference in self.searchForDifferences():
278+ yield self.toTerm(difference)
279+
280+ def __contains__(self, value):
281+ """See `IVocabulary`."""
282+ return (
283+ IDistroSeriesDifference.providedBy(value) and
284+ value.derived_series == self.distroseries)
285+
286+ def getTerm(self, value):
287+ """See `IVocabulary`."""
288+ if value not in self:
289+ raise LookupError(value)
290+ return self.toTerm(value)
291+
292+ def getTermByToken(self, token):
293+ """See `IVocabularyTokenized`."""
294+ if not token.isdigit():
295+ raise LookupError(token)
296+ difference = IStore(DistroSeriesDifference).get(
297+ DistroSeriesDifference, int(token))
298+ if difference is None:
299+ raise LookupError(token)
300+ elif difference.derived_series != self.distroseries:
301+ raise LookupError(token)
302+ else:
303+ return self.toTerm(difference)
304+
305+ @staticmethod
306+ def toTerm(dsd):
307+ """Return the term for a `DistroSeriesDifference`."""
308+ return SimpleTerm(dsd, dsd.id)
309+
310+ def searchForTerms(self, query=None):
311+ """See `IHugeVocabulary`."""
312+ results = self.searchForDifferences()
313+ return CountableIterator(results.count(), results, self.toTerm)
314+
315+ def searchForDifferences(self):
316+ """The set of `DistroSeriesDifference`s related to the context.
317+
318+ :return: `IResultSet` yielding `IDistroSeriesDifference`.
319+ """
320+ return DistroSeriesDifference.getForDistroSeries(self.distroseries)
321+
322+
323 class PillarVocabularyBase(NamedSQLObjectHugeVocabulary):
324 """Active `IPillar` objects vocabulary."""
325 displayname = 'Needs to be overridden'
326
327=== modified file 'lib/lp/registry/vocabularies.zcml'
328--- lib/lp/registry/vocabularies.zcml 2011-07-13 01:49:53 +0000
329+++ lib/lp/registry/vocabularies.zcml 2011-08-01 16:44:42 +0000
330@@ -42,6 +42,18 @@
331
332
333 <securedutility
334+ name="DistroSeriesDifferences"
335+ component=".vocabularies.DistroSeriesDifferencesVocabulary"
336+ provides="zope.schema.interfaces.IVocabularyFactory">
337+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
338+ </securedutility>
339+
340+ <class class=".vocabularies.DistroSeriesDifferencesVocabulary">
341+ <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
342+ </class>
343+
344+
345+ <securedutility
346 name="FeaturedProject"
347 component="lp.registry.vocabularies.FeaturedProjectVocabulary"
348 provides="zope.schema.interfaces.IVocabularyFactory"