Merge lp:~wgrant/launchpad/sensible-validate_target into lp:launchpad
- sensible-validate_target
- Merge into devel
Proposed by
William Grant
Status: | Merged |
---|---|
Approved by: | Curtis Hovey |
Approved revision: | no longer in the source branch. |
Merged at revision: | 13514 |
Proposed branch: | lp:~wgrant/launchpad/sensible-validate_target |
Merge into: | lp:launchpad |
Diff against target: |
763 lines (+284/-248) 8 files modified
lib/canonical/launchpad/doc/validation.txt (+0/-105) lib/canonical/launchpad/interfaces/validation.py (+0/-98) lib/lp/bugs/browser/bugalsoaffects.py (+20/-22) lib/lp/bugs/browser/bugtask.py (+16/-13) lib/lp/bugs/browser/tests/test_bugtask.py (+2/-1) lib/lp/bugs/model/bugtask.py (+65/-7) lib/lp/bugs/model/tests/test_bugtask.py (+177/-0) lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt (+4/-2) |
To merge this branch: | bzr merge lp:~wgrant/launchpad/sensible-validate_target |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey (community) | code | Approve | |
Review via email:
|
Commit message
Replace validate_distrotask and valid_upstreamtask with a more general and sensible validate_target.
Description of the change
This branch continues my ruthless campaign to move BugTask.target handling completely into the model. It takes validate_distrotask and valid_upstreamtask, combining them into a single validate_target which can be easily called by transitionToTarget. It now lives in lp.bugs, and has extensive unittests rather than doctests.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/doc/validation.txt' |
2 | --- lib/canonical/launchpad/doc/validation.txt 2011-02-28 20:46:41 +0000 |
3 | +++ lib/canonical/launchpad/doc/validation.txt 2011-07-25 23:06:42 +0000 |
4 | @@ -1,110 +1,5 @@ |
5 | = Validation = |
6 | |
7 | -The validation interface contains different kinds of validation functions. |
8 | - |
9 | -== validate_distrotask() == |
10 | - |
11 | -The validate_distrotask() function is used to guarantee that distribution |
12 | -bugtasks are unique per bug. |
13 | - |
14 | - >>> from canonical.launchpad.interfaces.validation import validate_distrotask |
15 | - >>> from lp.bugs.interfaces.bug import IBugSet |
16 | - >>> from lp.registry.interfaces.distribution import IDistributionSet |
17 | - >>> from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
18 | - >>> bug_two = getUtility(IBugSet).get(2) |
19 | - >>> debian = getUtility(IDistributionSet).getByName('debian') |
20 | - >>> mozilla_firefox = getUtility( |
21 | - ... ISourcePackageNameSet).queryByName('mozilla-firefox') |
22 | - >>> alsa_utils = getUtility( |
23 | - ... ISourcePackageNameSet).queryByName('alsa-utils') |
24 | - |
25 | -We aren't allowed to have two distrotasks with the same sourcepackage. |
26 | - |
27 | - >>> validate_distrotask(bug_two, debian, mozilla_firefox) |
28 | - Traceback (most recent call last): |
29 | - ... |
30 | - LaunchpadValidationError: ... |
31 | - |
32 | -But it's legal to have two bugtasks with different sourcepackages. |
33 | - |
34 | - >>> validate_distrotask(bug_two, debian, alsa_utils) |
35 | - |
36 | -If the bug already has a distribution task with no source package, it's |
37 | -not possible to add a another one. |
38 | - |
39 | - >>> from lp.bugs.interfaces.bug import CreateBugParams |
40 | - >>> from lp.registry.interfaces.distribution import IDistributionSet |
41 | - |
42 | - >>> login('no-priv@canonical.com') |
43 | - >>> no_priv = getUtility(ILaunchBag).user |
44 | - >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu') |
45 | - >>> ubuntu_bug_no_sourcepackage = ubuntu.createBug( |
46 | - ... CreateBugParams(no_priv, 'Test Bug', 'This is a test')) |
47 | - >>> validate_distrotask(ubuntu_bug_no_sourcepackage, ubuntu, None) |
48 | - Traceback (most recent call last): |
49 | - ... |
50 | - LaunchpadValidationError: ... |
51 | - |
52 | -It's also not possible to have a bugtask for a sourcepackage that has |
53 | -not been published in the target distribution. |
54 | - |
55 | - >>> foobar = getUtility( |
56 | - ... ISourcePackageNameSet).queryByName('foobar') |
57 | - >>> validate_distrotask(ubuntu_bug_no_sourcepackage, ubuntu, foobar) |
58 | - Traceback (most recent call last): |
59 | - ... |
60 | - LaunchpadValidationError: u'Package foobar not published in Ubuntu' |
61 | - |
62 | -It's not allowed even if the sourcepackage has been published in a |
63 | -PPA. |
64 | - |
65 | - >>> from lp.soyuz.enums import PackagePublishingStatus |
66 | - >>> from lp.soyuz.tests.ppa import publishToPPA |
67 | - >>> publishToPPA( |
68 | - ... person_name='cprov', |
69 | - ... sourcepackage_name='foobar', |
70 | - ... sourcepackage_version='1.0', |
71 | - ... distribution_name='ubuntu', |
72 | - ... distroseries_name='hoary', |
73 | - ... publishing_status=PackagePublishingStatus.PUBLISHED) |
74 | - |
75 | - >>> validate_distrotask(ubuntu_bug_no_sourcepackage, ubuntu, foobar) |
76 | - Traceback (most recent call last): |
77 | - ... |
78 | - LaunchpadValidationError: u'Package foobar not published in Ubuntu' |
79 | - |
80 | -Adding a task with a source package is still possible. |
81 | - |
82 | - >>> validate_distrotask(ubuntu_bug_no_sourcepackage, ubuntu, alsa_utils) |
83 | - |
84 | - |
85 | -== validate_new_distrotask() == |
86 | - |
87 | -The validate_new_distrotask() function does the same checks as |
88 | -validate_distrotask(), but it does some extra checks as well. |
89 | - |
90 | -We can't add a task on just the distribution, because it doesn't |
91 | -make sense to have tasks open on just the distribution and also packages |
92 | -in that distribution on the same bug. |
93 | - |
94 | - >>> from canonical.launchpad.interfaces.validation import ( |
95 | - ... validate_new_distrotask) |
96 | - >>> validate_new_distrotask(bug_two, debian, None) |
97 | - Traceback (most recent call last): |
98 | - ... |
99 | - LaunchpadValidationError: ... |
100 | - |
101 | -If the bug already has a distribution with no package, we can't add |
102 | -another distribution task with a source package (the task with no |
103 | -package should be edited instead). |
104 | - |
105 | - >>> validate_new_distrotask( |
106 | - ... ubuntu_bug_no_sourcepackage, ubuntu, alsa_utils) |
107 | - Traceback (most recent call last): |
108 | - ... |
109 | - LaunchpadValidationError: ... |
110 | - |
111 | - |
112 | == LaunchpadValidationError == |
113 | |
114 | LaunchpadValidationError is the standard exception used for custom |
115 | |
116 | === modified file 'lib/canonical/launchpad/interfaces/validation.py' |
117 | --- lib/canonical/launchpad/interfaces/validation.py 2011-06-14 20:35:20 +0000 |
118 | +++ lib/canonical/launchpad/interfaces/validation.py 2011-07-25 23:06:42 +0000 |
119 | @@ -12,9 +12,6 @@ |
120 | 'valid_cve_sequence', |
121 | 'validate_new_team_email', |
122 | 'validate_new_person_email', |
123 | - 'validate_distrotask', |
124 | - 'validate_new_distrotask', |
125 | - 'valid_upstreamtask', |
126 | 'valid_password', |
127 | 'validate_date_interval', |
128 | ] |
129 | @@ -25,8 +22,6 @@ |
130 | from zope.app.form.interfaces import WidgetsError |
131 | from zope.component import getUtility |
132 | |
133 | -from lazr.restful.error import expose |
134 | - |
135 | from canonical.launchpad import _ |
136 | from canonical.launchpad.interfaces.emailaddress import ( |
137 | IEmailAddressSet, |
138 | @@ -150,99 +145,6 @@ |
139 | return True |
140 | |
141 | |
142 | -def validate_new_distrotask(bug, distribution, sourcepackagename=None): |
143 | - """Validate a distribution bugtask to be added. |
144 | - |
145 | - Make sure that the isn't already a distribution task without a |
146 | - source package, or that such task is added only when the bug doesn't |
147 | - already have any tasks for the distribution. |
148 | - |
149 | - The same checks as `validate_distrotask` does are also done. |
150 | - """ |
151 | - from canonical.launchpad.helpers import shortlist |
152 | - |
153 | - if sourcepackagename: |
154 | - # Ensure that there isn't already a generic task open on the |
155 | - # distribution for this bug, because if there were, that task |
156 | - # should be reassigned to the sourcepackage, rather than a new |
157 | - # task opened. |
158 | - if bug.getBugTask(distribution) is not None: |
159 | - raise LaunchpadValidationError(_( |
160 | - 'This bug is already open on ${distribution} with no ' |
161 | - 'package specified. You should fill in a package ' |
162 | - 'name for the existing bug.', |
163 | - mapping={'distribution': distribution.displayname})) |
164 | - else: |
165 | - # Prevent having a task on only the distribution if there's at |
166 | - # least one task already on the distribution, whether or not |
167 | - # that task also has a source package. |
168 | - distribution_tasks_for_bug = [ |
169 | - bugtask for bugtask |
170 | - in shortlist(bug.bugtasks, longest_expected=50) |
171 | - if bugtask.distribution == distribution] |
172 | - |
173 | - if len(distribution_tasks_for_bug) > 0: |
174 | - raise LaunchpadValidationError(_( |
175 | - 'This bug is already on ${distribution}. Please ' |
176 | - 'specify an affected package in which the bug ' |
177 | - 'has not yet been reported.', |
178 | - mapping={'distribution': distribution.displayname})) |
179 | - validate_distrotask(bug, distribution, sourcepackagename) |
180 | - |
181 | - |
182 | -def validate_distrotask(bug, distribution, sourcepackagename=None): |
183 | - """Check if a distribution bugtask already exists for a given bug. |
184 | - |
185 | - If validation fails, a LaunchpadValidationError will be raised. |
186 | - """ |
187 | - if sourcepackagename is not None and len(distribution.series) > 0: |
188 | - # If the distribution has at least one series, check that the |
189 | - # source package has been published in the distribution. |
190 | - try: |
191 | - distribution.guessPublishedSourcePackageName( |
192 | - sourcepackagename.name) |
193 | - except NotFoundError, e: |
194 | - raise LaunchpadValidationError(e) |
195 | - new_source_package = distribution.getSourcePackage(sourcepackagename) |
196 | - if sourcepackagename is not None and ( |
197 | - bug.getBugTask(new_source_package) is not None): |
198 | - # Ensure this distribution/sourcepackage task is unique. |
199 | - raise LaunchpadValidationError(_( |
200 | - 'This bug has already been reported on ${source} ' |
201 | - '(${distribution}).', |
202 | - mapping={'source': sourcepackagename.name, |
203 | - 'distribution': distribution.name})) |
204 | - elif (sourcepackagename is None and |
205 | - bug.getBugTask(distribution) is not None): |
206 | - # Don't allow two distribution tasks with no source package. |
207 | - raise LaunchpadValidationError(_( |
208 | - 'This bug has already been reported on ${distribution}.', |
209 | - mapping={'distribution': distribution.name})) |
210 | - else: |
211 | - # The bugtask is valid. |
212 | - pass |
213 | - |
214 | - |
215 | -def valid_upstreamtask(bug, bug_target): |
216 | - """Check if a bugtask already exists for a given bug/target. |
217 | - |
218 | - If it exists, WidgetsError will be raised. |
219 | - """ |
220 | - # Local import to avoid circular imports. |
221 | - from lp.bugs.interfaces.bugtask import BugTaskSearchParams |
222 | - errors = [] |
223 | - user = getUtility(ILaunchBag).user |
224 | - params = BugTaskSearchParams(user, bug=bug) |
225 | - if not bug_target.searchTasks(params).is_empty(): |
226 | - errors.append( |
227 | - LaunchpadValidationError(_( |
228 | - 'A fix for this bug has already been requested for ${target}', |
229 | - mapping={'target': bug_target.displayname}))) |
230 | - |
231 | - if len(errors) > 0: |
232 | - raise expose(WidgetsError(errors), 400) |
233 | - |
234 | - |
235 | def valid_password(password): |
236 | """Return True if the argument is a valid password. |
237 | |
238 | |
239 | === modified file 'lib/lp/bugs/browser/bugalsoaffects.py' |
240 | --- lib/lp/bugs/browser/bugalsoaffects.py 2011-07-13 02:17:29 +0000 |
241 | +++ lib/lp/bugs/browser/bugalsoaffects.py 2011-07-25 23:06:42 +0000 |
242 | @@ -16,10 +16,7 @@ |
243 | from lazr.lifecycle.event import ObjectCreatedEvent |
244 | from z3c.ptcompat import ViewPageTemplateFile |
245 | from zope.app.form.browser import DropdownWidget |
246 | -from zope.app.form.interfaces import ( |
247 | - MissingInputError, |
248 | - WidgetsError, |
249 | - ) |
250 | +from zope.app.form.interfaces import MissingInputError |
251 | from zope.component import getUtility |
252 | from zope.event import notify |
253 | from zope.formlib import form |
254 | @@ -34,10 +31,6 @@ |
255 | MultiStepView, |
256 | StepView, |
257 | ) |
258 | -from canonical.launchpad.interfaces.validation import ( |
259 | - valid_upstreamtask, |
260 | - validate_new_distrotask, |
261 | - ) |
262 | from canonical.launchpad.webapp import canonical_url |
263 | from canonical.launchpad.webapp.interfaces import ILaunchBag |
264 | from canonical.launchpad.webapp.menu import structured |
265 | @@ -62,6 +55,7 @@ |
266 | BugTaskStatus, |
267 | IAddBugTaskForm, |
268 | IAddBugTaskWithProductCreationForm, |
269 | + IllegalTarget, |
270 | valid_remote_bug_url, |
271 | ) |
272 | from lp.bugs.interfaces.bugtracker import ( |
273 | @@ -73,6 +67,10 @@ |
274 | NoBugTrackerFound, |
275 | UnrecognizedBugTrackerURL, |
276 | ) |
277 | +from lp.bugs.model.bugtask import ( |
278 | + validate_new_target, |
279 | + validate_target, |
280 | + ) |
281 | from lp.registry.interfaces.distributionsourcepackage import ( |
282 | IDistributionSourcePackage, |
283 | ) |
284 | @@ -157,8 +155,8 @@ |
285 | upstream = bugtask.target.upstream_product |
286 | if upstream is not None: |
287 | try: |
288 | - valid_upstreamtask(bugtask.bug, upstream) |
289 | - except WidgetsError: |
290 | + validate_target(bugtask.bug, upstream) |
291 | + except IllegalTarget: |
292 | # There is already a task for the upstream. |
293 | pass |
294 | else: |
295 | @@ -173,10 +171,9 @@ |
296 | def validateStep(self, data): |
297 | if data.get('product'): |
298 | try: |
299 | - valid_upstreamtask(self.context.bug, data.get('product')) |
300 | - except WidgetsError, errors: |
301 | - for error in errors: |
302 | - self.setFieldError('product', error.snippet()) |
303 | + validate_target(self.context.bug, data.get('product')) |
304 | + except IllegalTarget as e: |
305 | + self.setFieldError('product', e[0]) |
306 | return |
307 | |
308 | entered_product = self.request.form.get(self.widgets['product'].name) |
309 | @@ -431,10 +428,12 @@ |
310 | self.setFieldError('sourcepackagename', error) |
311 | else: |
312 | try: |
313 | - validate_new_distrotask( |
314 | - self.context.bug, distribution, sourcepackagename) |
315 | - except LaunchpadValidationError, error: |
316 | - self.setFieldError('sourcepackagename', error.snippet()) |
317 | + target = distribution |
318 | + if sourcepackagename: |
319 | + target = target.getSourcePackage(sourcepackagename) |
320 | + validate_new_target(self.context.bug, target) |
321 | + except IllegalTarget as e: |
322 | + self.setFieldError('sourcepackagename', e[0]) |
323 | |
324 | super(DistroBugTaskCreationStep, self).validateStep(data) |
325 | |
326 | @@ -812,10 +811,9 @@ |
327 | self._validate(action, data) |
328 | project = data.get('existing_product') |
329 | try: |
330 | - valid_upstreamtask(self.context.bug, project) |
331 | - except WidgetsError, errors: |
332 | - for error in errors: |
333 | - self.setFieldError('existing_product', error.snippet()) |
334 | + validate_target(self.context.bug, project) |
335 | + except IllegalTarget as e: |
336 | + self.setFieldError('existing_product', e[0]) |
337 | |
338 | @action('Use Existing Project', name='use_existing_product', |
339 | validator=validate_existing_product) |
340 | |
341 | === modified file 'lib/lp/bugs/browser/bugtask.py' |
342 | --- lib/lp/bugs/browser/bugtask.py 2011-07-21 06:27:29 +0000 |
343 | +++ lib/lp/bugs/browser/bugtask.py 2011-07-25 23:06:42 +0000 |
344 | @@ -132,10 +132,6 @@ |
345 | FeedsMixin, |
346 | ) |
347 | from canonical.launchpad.interfaces.launchpad import IHasExternalBugTracker |
348 | -from canonical.launchpad.interfaces.validation import ( |
349 | - valid_upstreamtask, |
350 | - validate_distrotask, |
351 | - ) |
352 | from canonical.launchpad.mailnotification import get_unified_diff |
353 | from canonical.launchpad.searchbuilder import ( |
354 | all, |
355 | @@ -238,6 +234,7 @@ |
356 | IBugTaskSet, |
357 | ICreateQuestionFromBugTaskForm, |
358 | IFrontPageBugTaskSearch, |
359 | + IllegalTarget, |
360 | INominationsReviewTableBatchNavigator, |
361 | IPersonBugTaskSearch, |
362 | IRemoveQuestionFromBugTaskForm, |
363 | @@ -249,6 +246,7 @@ |
364 | from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus |
365 | from lp.bugs.interfaces.cve import ICveSet |
366 | from lp.bugs.interfaces.malone import IMaloneApplication |
367 | +from lp.bugs.model.bugtask import validate_target |
368 | from lp.registry.interfaces.distribution import ( |
369 | IDistribution, |
370 | IDistributionSet, |
371 | @@ -1372,15 +1370,20 @@ |
372 | distro = bugtask.distroseries.distribution |
373 | else: |
374 | distro = bugtask.distribution |
375 | - sourcename = bugtask.sourcepackagename |
376 | old_product = bugtask.product |
377 | |
378 | - if distro is not None and sourcename != data.get('sourcepackagename'): |
379 | + new_spn = data.get('sourcepackagename') |
380 | + if distro is not None and bugtask.sourcepackagename != new_spn: |
381 | try: |
382 | - validate_distrotask( |
383 | - bugtask.bug, distro, data.get('sourcepackagename')) |
384 | - except LaunchpadValidationError, error: |
385 | - self.setFieldError('sourcepackagename', str(error)) |
386 | + target = distro |
387 | + if new_spn is not None: |
388 | + target = distro.getSourcePackage(new_spn) |
389 | + validate_target(bugtask.bug, target) |
390 | + except IllegalTarget as e: |
391 | + # The field validator may have already set an error. |
392 | + # Don't clobber it. |
393 | + if not self.getFieldError('sourcepackagename'): |
394 | + self.setFieldError('sourcepackagename', e[0]) |
395 | |
396 | new_product = data.get('product') |
397 | if (old_product is None or old_product == new_product or |
398 | @@ -1394,9 +1397,9 @@ |
399 | self.setFieldError('product', 'Enter a project name') |
400 | else: |
401 | try: |
402 | - valid_upstreamtask(bugtask.bug, new_product) |
403 | - except WidgetsError, errors: |
404 | - self.setFieldError('product', errors.args[0]) |
405 | + validate_target(bugtask.bug, new_product) |
406 | + except IllegalTarget as e: |
407 | + self.setFieldError('product', e[0]) |
408 | |
409 | def updateContextFromData(self, data, context=None): |
410 | """Updates the context object using the submitted form data. |
411 | |
412 | === modified file 'lib/lp/bugs/browser/tests/test_bugtask.py' |
413 | --- lib/lp/bugs/browser/tests/test_bugtask.py 2011-07-18 11:32:51 +0000 |
414 | +++ lib/lp/bugs/browser/tests/test_bugtask.py 2011-07-25 23:06:42 +0000 |
415 | @@ -705,7 +705,8 @@ |
416 | bug_task_2, name='+editstatus', form=form, principal=user) |
417 | self.assertEqual(1, len(view.errors)) |
418 | self.assertEqual( |
419 | - 'This bug has already been reported on mouse (ubuntu).', |
420 | + 'A fix for this bug has already been requested for mouse in ' |
421 | + 'Ubuntu', |
422 | view.errors[0]) |
423 | |
424 | def setUpRetargetMilestone(self): |
425 | |
426 | === modified file 'lib/lp/bugs/model/bugtask.py' |
427 | --- lib/lp/bugs/model/bugtask.py 2011-07-22 04:50:51 +0000 |
428 | +++ lib/lp/bugs/model/bugtask.py 2011-07-25 23:06:42 +0000 |
429 | @@ -18,6 +18,8 @@ |
430 | 'get_bug_privacy_filter', |
431 | 'get_related_bugtasks_search_params', |
432 | 'search_value_to_where_condition', |
433 | + 'validate_new_target', |
434 | + 'validate_target', |
435 | ] |
436 | |
437 | |
438 | @@ -85,10 +87,6 @@ |
439 | ) |
440 | from canonical.launchpad.helpers import shortlist |
441 | from canonical.launchpad.interfaces.lpstorm import IStore |
442 | -from canonical.launchpad.interfaces.validation import ( |
443 | - valid_upstreamtask, |
444 | - validate_new_distrotask, |
445 | - ) |
446 | from canonical.launchpad.searchbuilder import ( |
447 | all, |
448 | any, |
449 | @@ -439,6 +437,65 @@ |
450 | return validate_target_attribute(self, attr, value) |
451 | |
452 | |
453 | +def validate_target(bug, target): |
454 | + """Validate a bugtask target against a bug's existing tasks. |
455 | + |
456 | + Checks that no conflicting tasks already exist. |
457 | + """ |
458 | + if bug.getBugTask(target): |
459 | + raise IllegalTarget( |
460 | + "A fix for this bug has already been requested for %s" |
461 | + % target.displayname) |
462 | + |
463 | + if IDistributionSourcePackage.providedBy(target): |
464 | + # If the distribution has at least one series, check that the |
465 | + # source package has been published in the distribution. |
466 | + if (target.sourcepackagename is not None and |
467 | + len(target.distribution.series) > 0): |
468 | + try: |
469 | + target.distribution.guessPublishedSourcePackageName( |
470 | + target.sourcepackagename.name) |
471 | + except NotFoundError, e: |
472 | + raise IllegalTarget(e[0]) |
473 | + |
474 | + |
475 | +def validate_new_target(bug, target): |
476 | + """Validate a bugtask target to be added. |
477 | + |
478 | + Make sure that the isn't already a distribution task without a |
479 | + source package, or that such task is added only when the bug doesn't |
480 | + already have any tasks for the distribution. |
481 | + |
482 | + The same checks as `validate_target` does are also done. |
483 | + """ |
484 | + if IDistribution.providedBy(target): |
485 | + # Prevent having a task on only the distribution if there's at |
486 | + # least one task already on the distribution, whether or not |
487 | + # that task also has a source package. |
488 | + distribution_tasks_for_bug = [ |
489 | + bugtask for bugtask |
490 | + in shortlist(bug.bugtasks, longest_expected=50) |
491 | + if bugtask.distribution == target] |
492 | + |
493 | + if len(distribution_tasks_for_bug) > 0: |
494 | + raise IllegalTarget( |
495 | + "This bug is already on %s. Please specify an " |
496 | + "affected package in which the bug has not yet " |
497 | + "been reported." % target.displayname) |
498 | + elif IDistributionSourcePackage.providedBy(target): |
499 | + # Ensure that there isn't already a generic task open on the |
500 | + # distribution for this bug, because if there were, that task |
501 | + # should be reassigned to the sourcepackage, rather than a new |
502 | + # task opened. |
503 | + if bug.getBugTask(target.distribution) is not None: |
504 | + raise IllegalTarget( |
505 | + "This bug is already open on %s with no package " |
506 | + "specified. You should fill in a package name for " |
507 | + "the existing bug." % target.distribution.displayname) |
508 | + |
509 | + validate_target(bug, target) |
510 | + |
511 | + |
512 | class BugTask(SQLBase): |
513 | """See `IBugTask`.""" |
514 | implements(IBugTask) |
515 | @@ -2653,7 +2710,8 @@ |
516 | elif distribution is not None: |
517 | # Make sure there's no bug task already filed against |
518 | # this source package in this distribution. |
519 | - validate_new_distrotask(bug, distribution, sourcepackagename) |
520 | + validate_new_target( |
521 | + bug, distribution.getSourcePackage(sourcepackagename)) |
522 | stop_checking = True |
523 | |
524 | if target is None and not stop_checking: |
525 | @@ -2670,13 +2728,13 @@ |
526 | target = distroseries |
527 | elif distribution is not None and not stop_checking: |
528 | # Bug filed against a distribution. |
529 | - validate_new_distrotask(bug, distribution) |
530 | + validate_new_target(bug, distribution) |
531 | stop_checking = True |
532 | |
533 | if target is not None and not stop_checking: |
534 | # Make sure there's no task for this bug already filed |
535 | # against the target. |
536 | - valid_upstreamtask(bug, target) |
537 | + validate_target(bug, target) |
538 | |
539 | if not bug.private and bug.security_related: |
540 | if product and product.security_contact: |
541 | |
542 | === modified file 'lib/lp/bugs/model/tests/test_bugtask.py' |
543 | --- lib/lp/bugs/model/tests/test_bugtask.py 2011-07-22 04:50:51 +0000 |
544 | +++ lib/lp/bugs/model/tests/test_bugtask.py 2011-07-25 23:06:42 +0000 |
545 | @@ -42,6 +42,8 @@ |
546 | bug_target_to_key, |
547 | build_tag_search_clause, |
548 | IllegalTarget, |
549 | + validate_new_target, |
550 | + validate_target, |
551 | ) |
552 | from lp.bugs.tests.bug import ( |
553 | create_old_bug, |
554 | @@ -57,6 +59,7 @@ |
555 | ) |
556 | from lp.registry.interfaces.product import IProductSet |
557 | from lp.registry.interfaces.projectgroup import IProjectGroupSet |
558 | +from lp.soyuz.interfaces.archive import ArchivePurpose |
559 | from lp.testing import ( |
560 | ANONYMOUS, |
561 | EventRecorder, |
562 | @@ -1831,3 +1834,177 @@ |
563 | def test_no_target_for_bad_keys(self): |
564 | self.assertRaises( |
565 | AssertionError, bug_target_from_key, None, None, None, None, None) |
566 | + |
567 | + |
568 | +class TestValidateTarget(TestCaseWithFactory): |
569 | + |
570 | + layer = DatabaseFunctionalLayer |
571 | + |
572 | + def test_new_product_is_allowed(self): |
573 | + # A new product not on the bug is OK. |
574 | + p1 = self.factory.makeProduct() |
575 | + task = self.factory.makeBugTask(target=p1) |
576 | + p2 = self.factory.makeProduct() |
577 | + validate_target(task.bug, p2) |
578 | + |
579 | + def test_same_product_is_forbidden(self): |
580 | + # A product with an existing task is not. |
581 | + p = self.factory.makeProduct() |
582 | + task = self.factory.makeBugTask(target=p) |
583 | + self.assertRaisesWithContent( |
584 | + IllegalTarget, |
585 | + "A fix for this bug has already been requested for %s" |
586 | + % p.displayname, |
587 | + validate_target, task.bug, p) |
588 | + |
589 | + def test_new_distribution_is_allowed(self): |
590 | + # A new distribution not on the bug is OK. |
591 | + d1 = self.factory.makeDistribution() |
592 | + task = self.factory.makeBugTask(target=d1) |
593 | + d2 = self.factory.makeDistribution() |
594 | + validate_target(task.bug, d2) |
595 | + |
596 | + def test_new_productseries_is_allowed(self): |
597 | + # A new productseries not on the bug is OK. |
598 | + ds1 = self.factory.makeProductSeries() |
599 | + task = self.factory.makeBugTask(target=ds1) |
600 | + ds2 = self.factory.makeProductSeries() |
601 | + validate_target(task.bug, ds2) |
602 | + |
603 | + def test_new_distroseries_is_allowed(self): |
604 | + # A new distroseries not on the bug is OK. |
605 | + ds1 = self.factory.makeDistroSeries() |
606 | + task = self.factory.makeBugTask(target=ds1) |
607 | + ds2 = self.factory.makeDistroSeries() |
608 | + validate_target(task.bug, ds2) |
609 | + |
610 | + def test_new_sourcepackage_is_allowed(self): |
611 | + # A new sourcepackage not on the bug is OK. |
612 | + sp1 = self.factory.makeSourcePackage() |
613 | + task = self.factory.makeBugTask(target=sp1) |
614 | + sp2 = self.factory.makeSourcePackage() |
615 | + validate_target(task.bug, sp2) |
616 | + |
617 | + def test_multiple_packageless_distribution_tasks_are_forbidden(self): |
618 | + # A distribution with an existing task is not. |
619 | + d = self.factory.makeDistribution() |
620 | + task = self.factory.makeBugTask(target=d) |
621 | + self.assertRaisesWithContent( |
622 | + IllegalTarget, |
623 | + "A fix for this bug has already been requested for %s" |
624 | + % d.displayname, |
625 | + validate_target, task.bug, d) |
626 | + |
627 | + def test_distributionsourcepackage_task_is_allowed(self): |
628 | + # A DistributionSourcePackage task can coexist with a task for |
629 | + # its Distribution. |
630 | + d = self.factory.makeDistribution() |
631 | + task = self.factory.makeBugTask(target=d) |
632 | + dsp = self.factory.makeDistributionSourcePackage(distribution=d) |
633 | + validate_target(task.bug, dsp) |
634 | + |
635 | + def test_different_distributionsourcepackage_tasks_are_allowed(self): |
636 | + # A DistributionSourcePackage task can also coexist with a task |
637 | + # for another one. |
638 | + dsp1 = self.factory.makeDistributionSourcePackage() |
639 | + task = self.factory.makeBugTask(target=dsp1) |
640 | + dsp2 = self.factory.makeDistributionSourcePackage( |
641 | + distribution=dsp1.distribution) |
642 | + validate_target(task.bug, dsp2) |
643 | + |
644 | + def test_same_distributionsourcepackage_task_is_forbidden(self): |
645 | + # But a DistributionSourcePackage task cannot coexist with a |
646 | + # task for itself. |
647 | + dsp = self.factory.makeDistributionSourcePackage() |
648 | + task = self.factory.makeBugTask(target=dsp) |
649 | + self.assertRaisesWithContent( |
650 | + IllegalTarget, |
651 | + "A fix for this bug has already been requested for %s in %s" |
652 | + % (dsp.sourcepackagename.name, dsp.distribution.displayname), |
653 | + validate_target, task.bug, dsp) |
654 | + |
655 | + def test_dsp_without_publications_disallowed(self): |
656 | + # If a distribution has series, a DistributionSourcePackage task |
657 | + # can only be created if the package is published in a distro |
658 | + # archive. |
659 | + series = self.factory.makeDistroSeries() |
660 | + dsp = self.factory.makeDistributionSourcePackage( |
661 | + distribution=series.distribution) |
662 | + task = self.factory.makeBugTask() |
663 | + self.assertRaisesWithContent( |
664 | + IllegalTarget, |
665 | + "Package %s not published in %s" |
666 | + % (dsp.sourcepackagename.name, dsp.distribution.displayname), |
667 | + validate_target, task.bug, dsp) |
668 | + |
669 | + def test_dsp_with_publications_allowed(self): |
670 | + # If a distribution has series, a DistributionSourcePackage task |
671 | + # can only be created if the package is published in a distro |
672 | + # archive. |
673 | + series = self.factory.makeDistroSeries() |
674 | + dsp = self.factory.makeDistributionSourcePackage( |
675 | + distribution=series.distribution) |
676 | + task = self.factory.makeBugTask() |
677 | + self.factory.makeSourcePackagePublishingHistory( |
678 | + distroseries=series, sourcepackagename=dsp.sourcepackagename, |
679 | + archive=series.main_archive) |
680 | + validate_target(task.bug, dsp) |
681 | + |
682 | + def test_dsp_with_only_ppa_publications_disallowed(self): |
683 | + # If a distribution has series, a DistributionSourcePackage task |
684 | + # can only be created if the package is published in a distro |
685 | + # archive. PPA publications don't count. |
686 | + series = self.factory.makeDistroSeries() |
687 | + dsp = self.factory.makeDistributionSourcePackage( |
688 | + distribution=series.distribution) |
689 | + task = self.factory.makeBugTask() |
690 | + self.factory.makeSourcePackagePublishingHistory( |
691 | + distroseries=series, sourcepackagename=dsp.sourcepackagename, |
692 | + archive=self.factory.makeArchive(purpose=ArchivePurpose.PPA)) |
693 | + self.assertRaisesWithContent( |
694 | + IllegalTarget, |
695 | + "Package %s not published in %s" |
696 | + % (dsp.sourcepackagename.name, dsp.distribution.displayname), |
697 | + validate_target, task.bug, dsp) |
698 | + |
699 | + |
700 | +class TestValidateNewTarget(TestCaseWithFactory): |
701 | + |
702 | + layer = DatabaseFunctionalLayer |
703 | + |
704 | + def test_products_are_ok(self): |
705 | + p1 = self.factory.makeProduct() |
706 | + task = self.factory.makeBugTask(target=p1) |
707 | + p2 = self.factory.makeProduct() |
708 | + validate_new_target(task.bug, p2) |
709 | + |
710 | + def test_calls_validate_target(self): |
711 | + p = self.factory.makeProduct() |
712 | + task = self.factory.makeBugTask(target=p) |
713 | + self.assertRaisesWithContent( |
714 | + IllegalTarget, |
715 | + "A fix for this bug has already been requested for %s" |
716 | + % p.displayname, |
717 | + validate_new_target, task.bug, p) |
718 | + |
719 | + def test_package_task_with_distribution_task_forbidden(self): |
720 | + d = self.factory.makeDistribution() |
721 | + dsp = self.factory.makeDistributionSourcePackage(distribution=d) |
722 | + task = self.factory.makeBugTask(target=d) |
723 | + self.assertRaisesWithContent( |
724 | + IllegalTarget, |
725 | + "This bug is already open on %s with no package specified. " |
726 | + "You should fill in a package name for the existing bug." |
727 | + % d.displayname, |
728 | + validate_new_target, task.bug, dsp) |
729 | + |
730 | + def test_distribution_task_with_package_task_forbidden(self): |
731 | + d = self.factory.makeDistribution() |
732 | + dsp = self.factory.makeDistributionSourcePackage(distribution=d) |
733 | + task = self.factory.makeBugTask(target=dsp) |
734 | + self.assertRaisesWithContent( |
735 | + IllegalTarget, |
736 | + "This bug is already on %s. Please specify an affected " |
737 | + "package in which the bug has not yet been reported." |
738 | + % d.displayname, |
739 | + validate_new_target, task.bug, d) |
740 | |
741 | === modified file 'lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt' |
742 | --- lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt 2011-07-06 19:37:54 +0000 |
743 | +++ lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt 2011-07-25 23:06:42 +0000 |
744 | @@ -76,7 +76,8 @@ |
745 | >>> browser.getControl("Save Changes").click() |
746 | >>> print get_feedback_messages(browser.contents) |
747 | [...There is 1 error in the data you entered... |
748 | - u'This bug has already been reported on evolution (ubuntu).'] |
749 | + u'A fix for this bug has already been requested for evolution in |
750 | + Ubuntu'] |
751 | |
752 | Now let's add a Debian task to bug 1. Since Debian doesn't use Launchpad, |
753 | we add a bug watch as well. |
754 | @@ -131,7 +132,8 @@ |
755 | 'http://bugs.../ubuntu/+source/mozilla-firefox/+bug/1/+editstatus' |
756 | >>> print get_feedback_messages(browser.contents) |
757 | [...There is 1 error in the data you entered... |
758 | - u'This bug has already been reported on alsa-utils (ubuntu).'] |
759 | + u'A fix for this bug has already been requested for alsa-utils in |
760 | + Ubuntu'] |
761 | |
762 | >>> browser.getControl('Package').value = 'pmount' |
763 | >>> browser.getControl('Save Changes').click() |
This branch looks good to land. I have a comment that relates to my own recent work. Maybe your thoughts will help us to make a method faster.
> === modified file 'lib/lp/ bugs/model/ bugtask. py' bugs/model/ bugtask. py 2011-07-22 04:50:51 +0000 bugs/model/ bugtask. py 2011-07-25 23:06:42 +0000 target_ attribute( self, attr, value) target( bug, target): target) : urcePackage. providedBy( target) : sourcepackagena me is not None and distribution. series) > 0): distribution. guessPublishedS ourcePackageNam e( sourcepackagena me.name)
> --- lib/lp/
> +++ lib/lp/
...
> @@ -439,6 +437,65 @@
> return validate_
>
>
> +def validate_
> + """Validate a bugtask target against a bug's existing tasks.
> +
> + Checks that no conflicting tasks already exist.
> + """
> + if bug.getBugTask(
> + raise IllegalTarget(
> + "A fix for this bug has already been requested for %s"
> + % target.displayname)
> +
> + if IDistributionSo
> + # If the distribution has at least one series, check that the
> + # source package has been published in the distribution.
> + if (target.
> + len(target.
> + try:
> + target.
> + target.
I believe this is the method that contributes to bugs /bugs.launchpad .net/launchpad/ +bug/618366 and /bugs.launchpad .net/launchpad/ +bug/721643
https:/
https:/
I tried to avoid using it. Do you know why guessPublishedS ourcePackageNam e()
requires a string and does the extra work to get the spn. We already have
an spn half the time. I also want to avoid calling this method if the method
that provided the spn can be trusted to have provide one that was published
in the distro.
Ah, but this method is smart enough to verify the branch for the package.
I like that.
The binary name is in part the fault of the existing vocabs. I hope
Steven's work will make this path in the method obsolete, or at least very
rare.