Merge lp:~cjwatson/launchpad/productseries-ubuntupkg-dsp-vocab into lp:launchpad
- productseries-ubuntupkg-dsp-vocab
- Merge into devel
Proposed by
Colin Watson
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 18195 | ||||||||
Proposed branch: | lp:~cjwatson/launchpad/productseries-ubuntupkg-dsp-vocab | ||||||||
Merge into: | lp:launchpad | ||||||||
Prerequisite: | lp:~cjwatson/launchpad/distribution-filebug-dsp-vocab | ||||||||
Diff against target: |
526 lines (+164/-86) 7 files modified
lib/lp/app/widgets/popup.py (+85/-1) lib/lp/bugs/browser/bugtracker.py (+2/-2) lib/lp/bugs/browser/widgets/bugtask.py (+11/-74) lib/lp/registry/browser/productseries.py (+33/-4) lib/lp/registry/browser/tests/test_packaging.py (+20/-3) lib/lp/registry/tests/test_distributionsourcepackage_vocabulary.py (+11/-0) lib/lp/registry/vocabularies.py (+2/-2) |
||||||||
To merge this branch: | bzr merge lp:~cjwatson/launchpad/productseries-ubuntupkg-dsp-vocab | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+305367@code.launchpad.net |
Commit message
Convert ProductSeries:
Description of the change
Convert ProductSeries:
I had to move most of BugTaskSourcePa
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
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/app/widgets/popup.py' | |||
2 | --- lib/lp/app/widgets/popup.py 2016-07-21 20:22:00 +0000 | |||
3 | +++ lib/lp/app/widgets/popup.py 2016-09-09 17:05:20 +0000 | |||
4 | @@ -4,21 +4,38 @@ | |||
5 | 4 | """Single selection widget using a popup to select one item from many.""" | 4 | """Single selection widget using a popup to select one item from many.""" |
6 | 5 | 5 | ||
7 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
8 | 7 | __all__ = [ | ||
9 | 8 | "BugTrackerPickerWidget", | ||
10 | 9 | "DistributionSourcePackagePickerWidget", | ||
11 | 10 | "PersonPickerWidget", | ||
12 | 11 | "SearchForUpstreamPopupWidget", | ||
13 | 12 | "SourcePackageNameWidgetBase", | ||
14 | 13 | "UbuntuSourcePackageNameWidget", | ||
15 | 14 | "VocabularyPickerWidget", | ||
16 | 15 | ] | ||
17 | 7 | 16 | ||
18 | 8 | from lazr.restful.utils import safe_hasattr | 17 | from lazr.restful.utils import safe_hasattr |
19 | 9 | import simplejson | 18 | import simplejson |
20 | 10 | from z3c.ptcompat import ViewPageTemplateFile | 19 | from z3c.ptcompat import ViewPageTemplateFile |
21 | 20 | from zope.component import getUtility | ||
22 | 21 | from zope.formlib.interfaces import ConversionError | ||
23 | 11 | from zope.formlib.itemswidgets import ( | 22 | from zope.formlib.itemswidgets import ( |
24 | 12 | ItemsWidgetBase, | 23 | ItemsWidgetBase, |
25 | 13 | SingleDataHelper, | 24 | SingleDataHelper, |
26 | 14 | ) | 25 | ) |
28 | 15 | from zope.schema.interfaces import IChoice | 26 | from zope.schema.interfaces import ( |
29 | 27 | IChoice, | ||
30 | 28 | InvalidValue, | ||
31 | 29 | ) | ||
32 | 16 | 30 | ||
33 | 17 | from lp.app.browser.stringformatter import FormattersAPI | 31 | from lp.app.browser.stringformatter import FormattersAPI |
34 | 18 | from lp.app.browser.vocabulary import ( | 32 | from lp.app.browser.vocabulary import ( |
35 | 19 | get_person_picker_entry_metadata, | 33 | get_person_picker_entry_metadata, |
36 | 20 | vocabulary_filters, | 34 | vocabulary_filters, |
37 | 21 | ) | 35 | ) |
38 | 36 | from lp.app.errors import NotFoundError | ||
39 | 37 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | ||
40 | 38 | from lp.services.features import getFeatureFlag | ||
41 | 22 | from lp.services.propertycache import cachedproperty | 39 | from lp.services.propertycache import cachedproperty |
42 | 23 | from lp.services.webapp import canonical_url | 40 | from lp.services.webapp import canonical_url |
43 | 24 | from lp.services.webapp.escaping import structured | 41 | from lp.services.webapp.escaping import structured |
44 | @@ -288,3 +305,70 @@ | |||
45 | 288 | return self._prefix + 'distribution' | 305 | return self._prefix + 'distribution' |
46 | 289 | 306 | ||
47 | 290 | distribution_name = '' | 307 | distribution_name = '' |
48 | 308 | |||
49 | 309 | |||
50 | 310 | class SourcePackageNameWidgetBase(DistributionSourcePackagePickerWidget): | ||
51 | 311 | """A base widget for choosing a SourcePackageName. | ||
52 | 312 | |||
53 | 313 | It accepts both binary and source package names. Widgets inheriting | ||
54 | 314 | from this must implement `getDistribution`. | ||
55 | 315 | |||
56 | 316 | Most views should use LaunchpadTargetWidget or | ||
57 | 317 | DistributionSourcePackagePickerWidget instead, but this is useful in | ||
58 | 318 | cases where the distribution is fixed in some other way. | ||
59 | 319 | """ | ||
60 | 320 | |||
61 | 321 | # Pages that use this widget don't display the distribution. | ||
62 | 322 | distribution_id = '' | ||
63 | 323 | |||
64 | 324 | def __init__(self, field, vocabulary, request): | ||
65 | 325 | super(SourcePackageNameWidgetBase, self).__init__( | ||
66 | 326 | field, vocabulary, request) | ||
67 | 327 | self.cached_values = {} | ||
68 | 328 | |||
69 | 329 | def getDistribution(self): | ||
70 | 330 | """Get the distribution used for package validation. | ||
71 | 331 | |||
72 | 332 | The package name has to be published in the returned distribution. | ||
73 | 333 | """ | ||
74 | 334 | raise NotImplementedError | ||
75 | 335 | |||
76 | 336 | def _toFieldValue(self, input): | ||
77 | 337 | if not input: | ||
78 | 338 | return self.context.missing_value | ||
79 | 339 | |||
80 | 340 | distribution = self.getDistribution() | ||
81 | 341 | cached_value = self.cached_values.get(input) | ||
82 | 342 | if cached_value: | ||
83 | 343 | return cached_value | ||
84 | 344 | if bool(getFeatureFlag('disclosure.dsp_picker.enabled')): | ||
85 | 345 | try: | ||
86 | 346 | self.context.vocabulary.setDistribution(distribution) | ||
87 | 347 | return self.context.vocabulary.getTermByToken(input).value | ||
88 | 348 | except LookupError: | ||
89 | 349 | raise ConversionError( | ||
90 | 350 | "Launchpad doesn't know of any source package named" | ||
91 | 351 | " '%s' in %s." % (input, distribution.displayname)) | ||
92 | 352 | # Else the untrusted SPN vocab was used so it needs secondary | ||
93 | 353 | # verification. | ||
94 | 354 | try: | ||
95 | 355 | source = distribution.guessPublishedSourcePackageName(input) | ||
96 | 356 | except NotFoundError: | ||
97 | 357 | try: | ||
98 | 358 | source = self.convertTokensToValues([input])[0] | ||
99 | 359 | except InvalidValue: | ||
100 | 360 | raise ConversionError( | ||
101 | 361 | "Launchpad doesn't know of any source package named" | ||
102 | 362 | " '%s' in %s." % (input, distribution.displayname)) | ||
103 | 363 | self.cached_values[input] = source | ||
104 | 364 | return source | ||
105 | 365 | |||
106 | 366 | |||
107 | 367 | class UbuntuSourcePackageNameWidget(SourcePackageNameWidgetBase): | ||
108 | 368 | """A widget to select Ubuntu packages.""" | ||
109 | 369 | |||
110 | 370 | distribution_name = 'ubuntu' | ||
111 | 371 | |||
112 | 372 | def getDistribution(self): | ||
113 | 373 | """See `SourcePackageNameWidgetBase`""" | ||
114 | 374 | return getUtility(ILaunchpadCelebrities).ubuntu | ||
115 | 291 | 375 | ||
116 | === modified file 'lib/lp/bugs/browser/bugtracker.py' | |||
117 | --- lib/lp/bugs/browser/bugtracker.py 2015-09-28 17:38:45 +0000 | |||
118 | +++ lib/lp/bugs/browser/bugtracker.py 2016-09-09 17:05:20 +0000 | |||
119 | @@ -1,4 +1,4 @@ | |||
121 | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
122 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
123 | 3 | 3 | ||
124 | 4 | """Bug tracker views.""" | 4 | """Bug tracker views.""" |
125 | @@ -40,8 +40,8 @@ | |||
126 | 40 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | 40 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
127 | 41 | from lp.app.validators import LaunchpadValidationError | 41 | from lp.app.validators import LaunchpadValidationError |
128 | 42 | from lp.app.widgets.itemswidgets import LaunchpadRadioWidget | 42 | from lp.app.widgets.itemswidgets import LaunchpadRadioWidget |
129 | 43 | from lp.app.widgets.popup import UbuntuSourcePackageNameWidget | ||
130 | 43 | from lp.app.widgets.textwidgets import DelimitedListWidget | 44 | from lp.app.widgets.textwidgets import DelimitedListWidget |
131 | 44 | from lp.bugs.browser.widgets.bugtask import UbuntuSourcePackageNameWidget | ||
132 | 45 | from lp.bugs.interfaces.bugtracker import ( | 45 | from lp.bugs.interfaces.bugtracker import ( |
133 | 46 | BugTrackerType, | 46 | BugTrackerType, |
134 | 47 | IBugTracker, | 47 | IBugTracker, |
135 | 48 | 48 | ||
136 | === modified file 'lib/lp/bugs/browser/widgets/bugtask.py' | |||
137 | --- lib/lp/bugs/browser/widgets/bugtask.py 2016-09-09 17:05:19 +0000 | |||
138 | +++ lib/lp/bugs/browser/widgets/bugtask.py 2016-09-09 17:05:20 +0000 | |||
139 | @@ -15,13 +15,11 @@ | |||
140 | 15 | "DBItemDisplayWidget", | 15 | "DBItemDisplayWidget", |
141 | 16 | "FileBugSourcePackageNameWidget", | 16 | "FileBugSourcePackageNameWidget", |
142 | 17 | "NewLineToSpacesWidget", | 17 | "NewLineToSpacesWidget", |
143 | 18 | "UbuntuSourcePackageNameWidget", | ||
144 | 19 | ] | 18 | ] |
145 | 20 | 19 | ||
146 | 21 | from z3c.ptcompat import ViewPageTemplateFile | 20 | from z3c.ptcompat import ViewPageTemplateFile |
147 | 22 | from zope.component import getUtility | 21 | from zope.component import getUtility |
148 | 23 | from zope.formlib.interfaces import ( | 22 | from zope.formlib.interfaces import ( |
149 | 24 | ConversionError, | ||
150 | 25 | IDisplayWidget, | 23 | IDisplayWidget, |
151 | 26 | IInputWidget, | 24 | IInputWidget, |
152 | 27 | InputErrors, | 25 | InputErrors, |
153 | @@ -39,24 +37,17 @@ | |||
154 | 39 | implementer, | 37 | implementer, |
155 | 40 | Interface, | 38 | Interface, |
156 | 41 | ) | 39 | ) |
161 | 42 | from zope.schema.interfaces import ( | 40 | from zope.schema.interfaces import ValidationError |
158 | 43 | InvalidValue, | ||
159 | 44 | ValidationError, | ||
160 | 45 | ) | ||
162 | 46 | from zope.schema.vocabulary import getVocabularyRegistry | 41 | from zope.schema.vocabulary import getVocabularyRegistry |
163 | 47 | 42 | ||
164 | 48 | from lp import _ | 43 | from lp import _ |
165 | 49 | from lp.app.browser.tales import TeamFormatterAPI | 44 | from lp.app.browser.tales import TeamFormatterAPI |
171 | 50 | from lp.app.errors import ( | 45 | from lp.app.errors import UnexpectedFormData |
167 | 51 | NotFoundError, | ||
168 | 52 | UnexpectedFormData, | ||
169 | 53 | ) | ||
170 | 54 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | ||
172 | 55 | from lp.app.widgets.helpers import get_widget_template | 46 | from lp.app.widgets.helpers import get_widget_template |
173 | 56 | from lp.app.widgets.launchpadtarget import LaunchpadTargetWidget | 47 | from lp.app.widgets.launchpadtarget import LaunchpadTargetWidget |
174 | 57 | from lp.app.widgets.popup import ( | 48 | from lp.app.widgets.popup import ( |
175 | 58 | DistributionSourcePackagePickerWidget, | ||
176 | 59 | PersonPickerWidget, | 49 | PersonPickerWidget, |
177 | 50 | SourcePackageNameWidgetBase, | ||
178 | 60 | ) | 51 | ) |
179 | 61 | from lp.app.widgets.textwidgets import ( | 52 | from lp.app.widgets.textwidgets import ( |
180 | 62 | StrippedTextWidget, | 53 | StrippedTextWidget, |
181 | @@ -483,27 +474,14 @@ | |||
182 | 483 | return vocabulary | 474 | return vocabulary |
183 | 484 | 475 | ||
184 | 485 | 476 | ||
186 | 486 | class BugTaskSourcePackageNameWidget(DistributionSourcePackagePickerWidget): | 477 | class BugTaskSourcePackageNameWidget(SourcePackageNameWidgetBase): |
187 | 487 | """A widget for associating a bugtask with a SourcePackageName. | 478 | """A widget for associating a bugtask with a SourcePackageName. |
188 | 488 | 479 | ||
189 | 489 | It accepts both binary and source package names. | 480 | It accepts both binary and source package names. |
190 | 490 | """ | 481 | """ |
191 | 491 | 482 | ||
192 | 492 | # Pages that use this widget don't display the distribution, but this | ||
193 | 493 | # can only be used by bugtasks on the distribution in question so the | ||
194 | 494 | # vocabulary will be able to work it out for itself. | ||
195 | 495 | distribution_id = '' | ||
196 | 496 | |||
197 | 497 | def __init__(self, field, vocabulary, request): | ||
198 | 498 | super(BugTaskSourcePackageNameWidget, self).__init__( | ||
199 | 499 | field, vocabulary, request) | ||
200 | 500 | self.cached_values = {} | ||
201 | 501 | |||
202 | 502 | def getDistribution(self): | 483 | def getDistribution(self): |
207 | 503 | """Get the distribution used for package validation. | 484 | """See `SourcePackageNameWidgetBase`.""" |
204 | 504 | |||
205 | 505 | The package name has to be published in the returned distribution. | ||
206 | 506 | """ | ||
208 | 507 | field = self.context | 485 | field = self.context |
209 | 508 | distribution = field.context.distribution | 486 | distribution = field.context.distribution |
210 | 509 | if distribution is None and field.context.distroseries is not None: | 487 | if distribution is None and field.context.distroseries is not None: |
211 | @@ -513,39 +491,8 @@ | |||
212 | 513 | " bugtasks on distributions or on distribution series.") | 491 | " bugtasks on distributions or on distribution series.") |
213 | 514 | return distribution | 492 | return distribution |
214 | 515 | 493 | ||
248 | 516 | def _toFieldValue(self, input): | 494 | |
249 | 517 | if not input: | 495 | class BugTaskAlsoAffectsSourcePackageNameWidget(SourcePackageNameWidgetBase): |
217 | 518 | return self.context.missing_value | ||
218 | 519 | |||
219 | 520 | distribution = self.getDistribution() | ||
220 | 521 | cached_value = self.cached_values.get(input) | ||
221 | 522 | if cached_value: | ||
222 | 523 | return cached_value | ||
223 | 524 | if bool(getFeatureFlag('disclosure.dsp_picker.enabled')): | ||
224 | 525 | try: | ||
225 | 526 | self.context.vocabulary.setDistribution(distribution) | ||
226 | 527 | return self.context.vocabulary.getTermByToken(input).value | ||
227 | 528 | except LookupError: | ||
228 | 529 | raise ConversionError( | ||
229 | 530 | "Launchpad doesn't know of any source package named" | ||
230 | 531 | " '%s' in %s." % (input, distribution.displayname)) | ||
231 | 532 | # Else the untrusted SPN vocab was used so it needs secondary | ||
232 | 533 | # verification. | ||
233 | 534 | try: | ||
234 | 535 | source = distribution.guessPublishedSourcePackageName(input) | ||
235 | 536 | except NotFoundError: | ||
236 | 537 | try: | ||
237 | 538 | source = self.convertTokensToValues([input])[0] | ||
238 | 539 | except InvalidValue: | ||
239 | 540 | raise ConversionError( | ||
240 | 541 | "Launchpad doesn't know of any source package named" | ||
241 | 542 | " '%s' in %s." % (input, distribution.displayname)) | ||
242 | 543 | self.cached_values[input] = source | ||
243 | 544 | return source | ||
244 | 545 | |||
245 | 546 | |||
246 | 547 | class BugTaskAlsoAffectsSourcePackageNameWidget( | ||
247 | 548 | BugTaskSourcePackageNameWidget): | ||
250 | 549 | """Package widget for +distrotask. | 496 | """Package widget for +distrotask. |
251 | 550 | 497 | ||
252 | 551 | This widget works the same as `BugTaskSourcePackageNameWidget`, except | 498 | This widget works the same as `BugTaskSourcePackageNameWidget`, except |
253 | @@ -555,7 +502,7 @@ | |||
254 | 555 | distribution_id = 'field.distribution' | 502 | distribution_id = 'field.distribution' |
255 | 556 | 503 | ||
256 | 557 | def getDistribution(self): | 504 | def getDistribution(self): |
258 | 558 | """See `BugTaskSourcePackageNameWidget`.""" | 505 | """See `SourcePackageNameWidgetBase`.""" |
259 | 559 | distribution_name = self.request.form.get('field.distribution') | 506 | distribution_name = self.request.form.get('field.distribution') |
260 | 560 | if distribution_name is None: | 507 | if distribution_name is None: |
261 | 561 | raise UnexpectedFormData( | 508 | raise UnexpectedFormData( |
262 | @@ -568,7 +515,7 @@ | |||
263 | 568 | return distribution | 515 | return distribution |
264 | 569 | 516 | ||
265 | 570 | 517 | ||
267 | 571 | class FileBugSourcePackageNameWidget(BugTaskSourcePackageNameWidget): | 518 | class FileBugSourcePackageNameWidget(SourcePackageNameWidgetBase): |
268 | 572 | """Package widget for +filebug. | 519 | """Package widget for +filebug. |
269 | 573 | 520 | ||
270 | 574 | This widget works the same as `BugTaskSourcePackageNameWidget`, except | 521 | This widget works the same as `BugTaskSourcePackageNameWidget`, except |
271 | @@ -577,7 +524,7 @@ | |||
272 | 577 | """ | 524 | """ |
273 | 578 | 525 | ||
274 | 579 | def getDistribution(self): | 526 | def getDistribution(self): |
276 | 580 | """See `BugTaskSourcePackageNameWidget`.""" | 527 | """See `SourcePackageNameWidgetBase`.""" |
277 | 581 | field = self.context | 528 | field = self.context |
278 | 582 | pillar = field.context.pillar | 529 | pillar = field.context.pillar |
279 | 583 | assert IDistribution.providedBy(pillar), ( | 530 | assert IDistribution.providedBy(pillar), ( |
280 | @@ -586,7 +533,7 @@ | |||
281 | 586 | return pillar | 533 | return pillar |
282 | 587 | 534 | ||
283 | 588 | def _toFieldValue(self, input): | 535 | def _toFieldValue(self, input): |
285 | 589 | """See `BugTaskSourcePackageNameWidget`.""" | 536 | """See `SourcePackageNameWidgetBase`.""" |
286 | 590 | source = super(FileBugSourcePackageNameWidget, self)._toFieldValue( | 537 | source = super(FileBugSourcePackageNameWidget, self)._toFieldValue( |
287 | 591 | input) | 538 | input) |
288 | 592 | if (source is not None and | 539 | if (source is not None and |
289 | @@ -604,16 +551,6 @@ | |||
290 | 604 | return source | 551 | return source |
291 | 605 | 552 | ||
292 | 606 | 553 | ||
293 | 607 | class UbuntuSourcePackageNameWidget(BugTaskSourcePackageNameWidget): | ||
294 | 608 | """A widget to select Ubuntu packages.""" | ||
295 | 609 | |||
296 | 610 | distribution_name = 'ubuntu' | ||
297 | 611 | |||
298 | 612 | def getDistribution(self): | ||
299 | 613 | """See `BugTaskSourcePackageNameWidget`""" | ||
300 | 614 | return getUtility(ILaunchpadCelebrities).ubuntu | ||
301 | 615 | |||
302 | 616 | |||
303 | 617 | @implementer(IDisplayWidget) | 554 | @implementer(IDisplayWidget) |
304 | 618 | class AssigneeDisplayWidget(BrowserWidget): | 555 | class AssigneeDisplayWidget(BrowserWidget): |
305 | 619 | """A widget for displaying an assignee.""" | 556 | """A widget for displaying an assignee.""" |
306 | 620 | 557 | ||
307 | === modified file 'lib/lp/registry/browser/productseries.py' | |||
308 | --- lib/lp/registry/browser/productseries.py 2015-09-29 00:05:21 +0000 | |||
309 | +++ lib/lp/registry/browser/productseries.py 2016-09-09 17:05:20 +0000 | |||
310 | @@ -1,4 +1,4 @@ | |||
312 | 1 | # Copyright 2009-2015 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
313 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
314 | 3 | 3 | ||
315 | 4 | """View classes for `IProductSeries`.""" | 4 | """View classes for `IProductSeries`.""" |
316 | @@ -28,6 +28,7 @@ | |||
317 | 28 | 28 | ||
318 | 29 | from operator import attrgetter | 29 | from operator import attrgetter |
319 | 30 | 30 | ||
320 | 31 | from lazr.restful.interface import copy_field | ||
321 | 31 | from z3c.ptcompat import ViewPageTemplateFile | 32 | from z3c.ptcompat import ViewPageTemplateFile |
322 | 32 | from zope.component import getUtility | 33 | from zope.component import getUtility |
323 | 33 | from zope.formlib import form | 34 | from zope.formlib import form |
324 | @@ -57,6 +58,7 @@ | |||
325 | 57 | from lp.app.enums import ServiceUsage | 58 | from lp.app.enums import ServiceUsage |
326 | 58 | from lp.app.errors import NotFoundError | 59 | from lp.app.errors import NotFoundError |
327 | 59 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | 60 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
328 | 61 | from lp.app.widgets.popup import UbuntuSourcePackageNameWidget | ||
329 | 60 | from lp.app.widgets.textwidgets import StrippedTextWidget | 62 | from lp.app.widgets.textwidgets import StrippedTextWidget |
330 | 61 | from lp.blueprints.browser.specificationtarget import ( | 63 | from lp.blueprints.browser.specificationtarget import ( |
331 | 62 | HasSpecificationsMenuMixin, | 64 | HasSpecificationsMenuMixin, |
332 | @@ -86,6 +88,9 @@ | |||
333 | 86 | from lp.registry.browser.product import ProductSetBranchView | 88 | from lp.registry.browser.product import ProductSetBranchView |
334 | 87 | from lp.registry.enums import VCSType | 89 | from lp.registry.enums import VCSType |
335 | 88 | from lp.registry.errors import CannotPackageProprietaryProduct | 90 | from lp.registry.errors import CannotPackageProprietaryProduct |
336 | 91 | from lp.registry.interfaces.distributionsourcepackage import ( | ||
337 | 92 | IDistributionSourcePackage, | ||
338 | 93 | ) | ||
339 | 89 | from lp.registry.interfaces.packaging import ( | 94 | from lp.registry.interfaces.packaging import ( |
340 | 90 | IPackaging, | 95 | IPackaging, |
341 | 91 | IPackagingUtil, | 96 | IPackagingUtil, |
342 | @@ -93,6 +98,7 @@ | |||
343 | 93 | from lp.registry.interfaces.productseries import IProductSeries | 98 | from lp.registry.interfaces.productseries import IProductSeries |
344 | 94 | from lp.registry.interfaces.series import SeriesStatus | 99 | from lp.registry.interfaces.series import SeriesStatus |
345 | 95 | from lp.services.config import config | 100 | from lp.services.config import config |
346 | 101 | from lp.services.features import getFeatureFlag | ||
347 | 96 | from lp.services.propertycache import cachedproperty | 102 | from lp.services.propertycache import cachedproperty |
348 | 97 | from lp.services.webapp import ( | 103 | from lp.services.webapp import ( |
349 | 98 | ApplicationMenu, | 104 | ApplicationMenu, |
350 | @@ -482,10 +488,25 @@ | |||
351 | 482 | return list(self.context.releases[:12]) | 488 | return list(self.context.releases[:12]) |
352 | 483 | 489 | ||
353 | 484 | 490 | ||
354 | 491 | class IPackagingForm(IPackaging): | ||
355 | 492 | |||
356 | 493 | sourcepackagename = copy_field( | ||
357 | 494 | IPackaging['sourcepackagename'], | ||
358 | 495 | vocabularyName='DistributionSourcePackage') | ||
359 | 496 | |||
360 | 497 | |||
361 | 485 | class ProductSeriesUbuntuPackagingView(LaunchpadFormView): | 498 | class ProductSeriesUbuntuPackagingView(LaunchpadFormView): |
362 | 486 | 499 | ||
364 | 487 | schema = IPackaging | 500 | @property |
365 | 501 | def schema(self): | ||
366 | 502 | """See `LaunchpadFormView`.""" | ||
367 | 503 | if bool(getFeatureFlag('disclosure.dsp_picker.enabled')): | ||
368 | 504 | return IPackagingForm | ||
369 | 505 | else: | ||
370 | 506 | return IPackaging | ||
371 | 507 | |||
372 | 488 | field_names = ['sourcepackagename', 'distroseries'] | 508 | field_names = ['sourcepackagename', 'distroseries'] |
373 | 509 | custom_widget('sourcepackagename', UbuntuSourcePackageNameWidget) | ||
374 | 489 | page_title = 'Ubuntu source packaging' | 510 | page_title = 'Ubuntu source packaging' |
375 | 490 | label = page_title | 511 | label = page_title |
376 | 491 | 512 | ||
377 | @@ -497,7 +518,11 @@ | |||
378 | 497 | self._ubuntu_series = self._ubuntu.currentseries | 518 | self._ubuntu_series = self._ubuntu.currentseries |
379 | 498 | try: | 519 | try: |
380 | 499 | package = self.context.getPackage(self._ubuntu_series) | 520 | package = self.context.getPackage(self._ubuntu_series) |
382 | 500 | self.default_sourcepackagename = package.sourcepackagename | 521 | if bool(getFeatureFlag('disclosure.dsp_picker.enabled')): |
383 | 522 | self.default_sourcepackagename = self._ubuntu.getSourcePackage( | ||
384 | 523 | package.sourcepackagename) | ||
385 | 524 | else: | ||
386 | 525 | self.default_sourcepackagename = package.sourcepackagename | ||
387 | 501 | except NotFoundError: | 526 | except NotFoundError: |
388 | 502 | # The package has never been set. | 527 | # The package has never been set. |
389 | 503 | self.default_sourcepackagename = None | 528 | self.default_sourcepackagename = None |
390 | @@ -555,6 +580,8 @@ | |||
391 | 555 | def validate(self, data): | 580 | def validate(self, data): |
392 | 556 | productseries = self.context | 581 | productseries = self.context |
393 | 557 | sourcepackagename = data.get('sourcepackagename', None) | 582 | sourcepackagename = data.get('sourcepackagename', None) |
394 | 583 | if IDistributionSourcePackage.providedBy(sourcepackagename): | ||
395 | 584 | sourcepackagename = sourcepackagename.sourcepackagename | ||
396 | 558 | distroseries = self._getSubmittedSeries(data) | 585 | distroseries = self._getSubmittedSeries(data) |
397 | 559 | 586 | ||
398 | 560 | packaging_util = getUtility(IPackagingUtil) | 587 | packaging_util = getUtility(IPackagingUtil) |
399 | @@ -595,6 +622,8 @@ | |||
400 | 595 | # ubuntu series. if none exists, one will be created | 622 | # ubuntu series. if none exists, one will be created |
401 | 596 | distroseries = self._getSubmittedSeries(data) | 623 | distroseries = self._getSubmittedSeries(data) |
402 | 597 | sourcepackagename = data['sourcepackagename'] | 624 | sourcepackagename = data['sourcepackagename'] |
403 | 625 | if IDistributionSourcePackage.providedBy(sourcepackagename): | ||
404 | 626 | sourcepackagename = sourcepackagename.sourcepackagename | ||
405 | 598 | if getUtility(IPackagingUtil).packagingEntryExists( | 627 | if getUtility(IPackagingUtil).packagingEntryExists( |
406 | 599 | sourcepackagename, distroseries, productseries=self.context): | 628 | sourcepackagename, distroseries, productseries=self.context): |
407 | 600 | # There is no change. | 629 | # There is no change. |
408 | @@ -602,7 +631,7 @@ | |||
409 | 602 | try: | 631 | try: |
410 | 603 | self.context.setPackaging( | 632 | self.context.setPackaging( |
411 | 604 | distroseries, sourcepackagename, self.user) | 633 | distroseries, sourcepackagename, self.user) |
413 | 605 | except CannotPackageProprietaryProduct, e: | 634 | except CannotPackageProprietaryProduct as e: |
414 | 606 | self.request.response.addErrorNotification(str(e)) | 635 | self.request.response.addErrorNotification(str(e)) |
415 | 607 | 636 | ||
416 | 608 | 637 | ||
417 | 609 | 638 | ||
418 | === modified file 'lib/lp/registry/browser/tests/test_packaging.py' | |||
419 | --- lib/lp/registry/browser/tests/test_packaging.py 2016-06-17 15:46:57 +0000 | |||
420 | +++ lib/lp/registry/browser/tests/test_packaging.py 2016-09-09 17:05:20 +0000 | |||
421 | @@ -5,6 +5,10 @@ | |||
422 | 5 | 5 | ||
423 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
424 | 7 | 7 | ||
425 | 8 | from testscenarios import ( | ||
426 | 9 | load_tests_apply_scenarios, | ||
427 | 10 | WithScenarios, | ||
428 | 11 | ) | ||
429 | 8 | from zope.component import getUtility | 12 | from zope.component import getUtility |
430 | 9 | 13 | ||
431 | 10 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | 14 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
432 | @@ -15,6 +19,7 @@ | |||
433 | 15 | ) | 19 | ) |
434 | 16 | from lp.registry.interfaces.product import IProductSet | 20 | from lp.registry.interfaces.product import IProductSet |
435 | 17 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet | 21 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
436 | 22 | from lp.services.features.testing import FeatureFixture | ||
437 | 18 | from lp.testing import ( | 23 | from lp.testing import ( |
438 | 19 | login, | 24 | login, |
439 | 20 | logout, | 25 | logout, |
440 | @@ -28,13 +33,22 @@ | |||
441 | 28 | from lp.testing.views import create_initialized_view | 33 | from lp.testing.views import create_initialized_view |
442 | 29 | 34 | ||
443 | 30 | 35 | ||
446 | 31 | class TestProductSeriesUbuntuPackagingView(TestCaseWithFactory): | 36 | class TestProductSeriesUbuntuPackagingView(WithScenarios, TestCaseWithFactory): |
447 | 32 | """Browser tests for deletion of Packaging objects.""" | 37 | """Browser tests for adding Packaging objects.""" |
448 | 33 | 38 | ||
449 | 34 | layer = DatabaseFunctionalLayer | 39 | layer = DatabaseFunctionalLayer |
450 | 35 | 40 | ||
451 | 41 | scenarios = [ | ||
452 | 42 | ("spn_picker", {"features": {}}), | ||
453 | 43 | ("dsp_picker", { | ||
454 | 44 | "features": {u"disclosure.dsp_picker.enabled": u"on"}, | ||
455 | 45 | }), | ||
456 | 46 | ] | ||
457 | 47 | |||
458 | 36 | def setUp(self): | 48 | def setUp(self): |
459 | 37 | super(TestProductSeriesUbuntuPackagingView, self).setUp() | 49 | super(TestProductSeriesUbuntuPackagingView, self).setUp() |
460 | 50 | if self.features: | ||
461 | 51 | self.useFixture(FeatureFixture(self.features)) | ||
462 | 38 | self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu | 52 | self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu |
463 | 39 | self.hoary = self.ubuntu.getSeries('hoary') | 53 | self.hoary = self.ubuntu.getSeries('hoary') |
464 | 40 | self.sourcepackagename = self.factory.makeSourcePackageName('hot') | 54 | self.sourcepackagename = self.factory.makeSourcePackageName('hot') |
465 | @@ -88,7 +102,7 @@ | |||
466 | 88 | 'hot</a> package in Hoary is already linked to another series.'] | 102 | 'hot</a> package in Hoary is already linked to another series.'] |
467 | 89 | self.assertEqual(view_errors, view.errors) | 103 | self.assertEqual(view_errors, view.errors) |
468 | 90 | 104 | ||
470 | 91 | def test_sourcepackgename_required(self): | 105 | def test_sourcepackagename_required(self): |
471 | 92 | # A source package name must be provided. | 106 | # A source package name must be provided. |
472 | 93 | form = { | 107 | form = { |
473 | 94 | 'field.distroseries': 'hoary', | 108 | 'field.distroseries': 'hoary', |
474 | @@ -188,3 +202,6 @@ | |||
475 | 188 | productseries=productseries, | 202 | productseries=productseries, |
476 | 189 | sourcepackagename=package_name, | 203 | sourcepackagename=package_name, |
477 | 190 | distroseries=distroseries)) | 204 | distroseries=distroseries)) |
478 | 205 | |||
479 | 206 | |||
480 | 207 | load_tests = load_tests_apply_scenarios | ||
481 | 191 | 208 | ||
482 | === modified file 'lib/lp/registry/tests/test_distributionsourcepackage_vocabulary.py' | |||
483 | --- lib/lp/registry/tests/test_distributionsourcepackage_vocabulary.py 2016-09-09 17:05:19 +0000 | |||
484 | +++ lib/lp/registry/tests/test_distributionsourcepackage_vocabulary.py 2016-09-09 17:05:20 +0000 | |||
485 | @@ -175,6 +175,17 @@ | |||
486 | 175 | self.assertEqual(dsp.name, term.title) | 175 | self.assertEqual(dsp.name, term.title) |
487 | 176 | self.assertEqual(dsp, term.value) | 176 | self.assertEqual(dsp, term.value) |
488 | 177 | 177 | ||
489 | 178 | def test_toTerm_dsp_no_distribution(self): | ||
490 | 179 | # The vocabulary can convert a DSP to a term even if it does not yet | ||
491 | 180 | # have a distribution. | ||
492 | 181 | dsp = self.factory.makeDistributionSourcePackage( | ||
493 | 182 | sourcepackagename='foo', with_db=False) | ||
494 | 183 | vocabulary = DistributionSourcePackageVocabulary(None) | ||
495 | 184 | term = vocabulary.toTerm(dsp) | ||
496 | 185 | self.assertEqual(dsp.name, term.token) | ||
497 | 186 | self.assertEqual(dsp.name, term.title) | ||
498 | 187 | self.assertEqual(dsp, term.value) | ||
499 | 188 | |||
500 | 178 | def test_getTermByToken_error(self): | 189 | def test_getTermByToken_error(self): |
501 | 179 | # An error is raised if the token does not match a official DSP. | 190 | # An error is raised if the token does not match a official DSP. |
502 | 180 | distro = self.factory.makeDistribution() | 191 | distro = self.factory.makeDistribution() |
503 | 181 | 192 | ||
504 | === modified file 'lib/lp/registry/vocabularies.py' | |||
505 | --- lib/lp/registry/vocabularies.py 2016-09-09 17:05:19 +0000 | |||
506 | +++ lib/lp/registry/vocabularies.py 2016-09-09 17:05:20 +0000 | |||
507 | @@ -2073,7 +2073,6 @@ | |||
508 | 2073 | 2073 | ||
509 | 2074 | def toTerm(self, spn_or_dsp): | 2074 | def toTerm(self, spn_or_dsp): |
510 | 2075 | """See `IVocabulary`.""" | 2075 | """See `IVocabulary`.""" |
511 | 2076 | self._assertHasDistribution() | ||
512 | 2077 | dsp = None | 2076 | dsp = None |
513 | 2078 | binary_names = None | 2077 | binary_names = None |
514 | 2079 | if isinstance(spn_or_dsp, tuple): | 2078 | if isinstance(spn_or_dsp, tuple): |
515 | @@ -2088,9 +2087,10 @@ | |||
516 | 2088 | if IDistributionSourcePackage.providedBy(spn_or_dsp): | 2087 | if IDistributionSourcePackage.providedBy(spn_or_dsp): |
517 | 2089 | dsp = spn_or_dsp | 2088 | dsp = spn_or_dsp |
518 | 2090 | elif spn_or_dsp is not None: | 2089 | elif spn_or_dsp is not None: |
519 | 2090 | self._assertHasDistribution() | ||
520 | 2091 | dsp = self.distribution.getSourcePackage(spn_or_dsp) | 2091 | dsp = self.distribution.getSourcePackage(spn_or_dsp) |
521 | 2092 | if dsp is not None: | 2092 | if dsp is not None: |
523 | 2093 | if dsp == self.dsp or dsp.is_official: | 2093 | if dsp == self.dsp or dsp.is_official or self.distribution is None: |
524 | 2094 | if binary_names: | 2094 | if binary_names: |
525 | 2095 | # Search already did the hard work of looking up binary | 2095 | # Search already did the hard work of looking up binary |
526 | 2096 | # names. | 2096 | # names. |