Merge lp:~sinzui/launchpad/delete-structural-target into lp:launchpad/db-devel
- delete-structural-target
- Merge into db-devel
Proposed by
Curtis Hovey
Status: | Merged |
---|---|
Approved by: | Eleanor Berger |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~sinzui/launchpad/delete-structural-target |
Merge into: | lp:launchpad/db-devel |
Diff against target: |
399 lines 10 files modified
database/schema/security.cfg (+1/-1) lib/canonical/launchpad/icing/style-3-0.css (+3/-0) lib/canonical/launchpad/templates/launchpad-login.pt (+5/-5) lib/lp/registry/browser/__init__.py (+47/-9) lib/lp/registry/browser/productseries.py (+7/-2) lib/lp/registry/browser/tests/milestone-views.txt (+6/-0) lib/lp/registry/browser/tests/productseries-views.txt (+40/-10) lib/lp/registry/doc/milestone.txt (+10/-0) lib/lp/registry/model/milestone.py (+4/-1) lib/lp/registry/templates/productseries-delete.pt (+12/-6) |
To merge this branch: | bzr merge lp:~sinzui/launchpad/delete-structural-target |
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eleanor Berger (community) | code | Approve | |
Review via email: mp+12877@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote : | # |
Revision history for this message
Eleanor Berger (intellectronica) wrote : | # |
We discussed changing obj.destroySelf to Store.of(
Also, we agreed that keeping the deletion work in the view (rather than in the model) is a bit unfortunate (since we won't be able to repeat the same interaction when working with the model directly or via the API) but for now that's the pragmatic solution.
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'database/schema/security.cfg' | |||
2 | --- database/schema/security.cfg 2009-10-02 14:02:25 +0000 | |||
3 | +++ database/schema/security.cfg 2009-10-05 16:45:23 +0000 | |||
4 | @@ -891,7 +891,7 @@ | |||
5 | 891 | public.bugpackageinfestation = SELECT, INSERT, UPDATE | 891 | public.bugpackageinfestation = SELECT, INSERT, UPDATE |
6 | 892 | public.bugproductinfestation = SELECT, INSERT, UPDATE | 892 | public.bugproductinfestation = SELECT, INSERT, UPDATE |
7 | 893 | public.bugsubscription = SELECT, INSERT, UPDATE, DELETE | 893 | public.bugsubscription = SELECT, INSERT, UPDATE, DELETE |
9 | 894 | public.bugtask = SELECT, INSERT, UPDATE | 894 | public.bugtask = SELECT, INSERT, UPDATE, DELETE |
10 | 895 | public.bugtracker = SELECT, INSERT, UPDATE, DELETE | 895 | public.bugtracker = SELECT, INSERT, UPDATE, DELETE |
11 | 896 | public.bugtrackeralias = SELECT, INSERT, UPDATE, DELETE | 896 | public.bugtrackeralias = SELECT, INSERT, UPDATE, DELETE |
12 | 897 | public.bugwatch = SELECT, INSERT, UPDATE, DELETE | 897 | public.bugwatch = SELECT, INSERT, UPDATE, DELETE |
13 | 898 | 898 | ||
14 | === modified file 'lib/canonical/launchpad/icing/style-3-0.css' | |||
15 | --- lib/canonical/launchpad/icing/style-3-0.css 2009-10-02 13:56:25 +0000 | |||
16 | +++ lib/canonical/launchpad/icing/style-3-0.css 2009-10-05 16:45:23 +0000 | |||
17 | @@ -446,6 +446,9 @@ | |||
18 | 446 | padding: 0 1.5em 0 0; | 446 | padding: 0 1.5em 0 0; |
19 | 447 | } | 447 | } |
20 | 448 | .subordinate { | 448 | .subordinate { |
21 | 449 | margin-left: 2em; | ||
22 | 450 | } | ||
23 | 451 | ol.subordinate { | ||
24 | 449 | margin-left: 4em; | 452 | margin-left: 4em; |
25 | 450 | } | 453 | } |
26 | 451 | .two-column-list li, | 454 | .two-column-list li, |
27 | 452 | 455 | ||
28 | === modified file 'lib/canonical/launchpad/templates/launchpad-login.pt' | |||
29 | --- lib/canonical/launchpad/templates/launchpad-login.pt 2009-07-17 17:59:07 +0000 | |||
30 | +++ lib/canonical/launchpad/templates/launchpad-login.pt 2009-10-05 16:45:22 +0000 | |||
31 | @@ -169,11 +169,11 @@ | |||
32 | 169 | <p> | 169 | <p> |
33 | 170 | Creating your Launchpad account is easy. Here's what to do:</p> | 170 | Creating your Launchpad account is easy. Here's what to do:</p> |
34 | 171 | 171 | ||
40 | 172 | <ol class="subordinate"> | 172 | <ol class="subordinate"> |
41 | 173 | <li>Make sure cookies are enabled in your browser.</li> | 173 | <li>Make sure cookies are enabled in your browser.</li> |
42 | 174 | <li>Enter your e-mail address.</li> | 174 | <li>Enter your e-mail address.</li> |
43 | 175 | <li>Follow the URL in the confirmation e-mail that Launchpad sends and you're done!</li> | 175 | <li>Follow the URL in the confirmation e-mail that Launchpad sends and you're done!</li> |
44 | 176 | </ol> | 176 | </ol> |
45 | 177 | 177 | ||
46 | 178 | 178 | ||
47 | 179 | 179 | ||
48 | 180 | 180 | ||
49 | === modified file 'lib/lp/registry/browser/__init__.py' | |||
50 | --- lib/lp/registry/browser/__init__.py 2009-09-22 16:21:12 +0000 | |||
51 | +++ lib/lp/registry/browser/__init__.py 2009-10-05 16:45:23 +0000 | |||
52 | @@ -18,6 +18,8 @@ | |||
53 | 18 | 18 | ||
54 | 19 | from zope.component import getUtility | 19 | from zope.component import getUtility |
55 | 20 | 20 | ||
56 | 21 | from storm.store import Store | ||
57 | 22 | |||
58 | 21 | from lp.bugs.interfaces.bugtask import BugTaskSearchParams, IBugTaskSet | 23 | from lp.bugs.interfaces.bugtask import BugTaskSearchParams, IBugTaskSet |
59 | 22 | from lp.registry.interfaces.productseries import IProductSeries | 24 | from lp.registry.interfaces.productseries import IProductSeries |
60 | 23 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities | 25 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
61 | @@ -136,15 +138,22 @@ | |||
62 | 136 | """The context's URL.""" | 138 | """The context's URL.""" |
63 | 137 | return canonical_url(self.context) | 139 | return canonical_url(self.context) |
64 | 138 | 140 | ||
68 | 139 | def _getBugtasks(self, milestone): | 141 | def _getBugtasks(self, target): |
69 | 140 | """Return the list `IBugTask`s targeted to the milestone.""" | 142 | """Return the list `IBugTask`s associated with the target.""" |
70 | 141 | params = BugTaskSearchParams(milestone=milestone, user=None) | 143 | if IProductSeries.providedBy(target): |
71 | 144 | params = BugTaskSearchParams(user=None) | ||
72 | 145 | params.setProductSeries(target) | ||
73 | 146 | else: | ||
74 | 147 | params = BugTaskSearchParams(milestone=target, user=None) | ||
75 | 142 | bugtasks = getUtility(IBugTaskSet).search(params) | 148 | bugtasks = getUtility(IBugTaskSet).search(params) |
76 | 143 | return list(bugtasks) | 149 | return list(bugtasks) |
77 | 144 | 150 | ||
81 | 145 | def _getSpecifications(self, milestone): | 151 | def _getSpecifications(self, target): |
82 | 146 | """Return the list `ISpecification`s targeted to the milestone.""" | 152 | """Return the list `ISpecification`s associated to the target.""" |
83 | 147 | return list(milestone.specifications) | 153 | if IProductSeries.providedBy(target): |
84 | 154 | return list(target.all_specifications) | ||
85 | 155 | else: | ||
86 | 156 | return list(target.specifications) | ||
87 | 148 | 157 | ||
88 | 149 | def _getProductRelease(self, milestone): | 158 | def _getProductRelease(self, milestone): |
89 | 150 | """The `IProductRelease` associated with the milestone.""" | 159 | """The `IProductRelease` associated with the milestone.""" |
90 | @@ -158,10 +167,37 @@ | |||
91 | 158 | else: | 167 | else: |
92 | 159 | return [] | 168 | return [] |
93 | 160 | 169 | ||
94 | 170 | def _unsubscribe_structure(self, structure): | ||
95 | 171 | """Removed the subscriptions from structure.""" | ||
96 | 172 | for subscription in structure.getSubscriptions(): | ||
97 | 173 | # The owner of the subscription or an admin are the only users | ||
98 | 174 | # that can destroy a subscription, but this rule cannot prevent | ||
99 | 175 | # the owner from removing the structure. | ||
100 | 176 | Store.of(subscription).remove(subscription) | ||
101 | 177 | |||
102 | 178 | def _remove_series_bugs_and_specifications(self, series): | ||
103 | 179 | """Untarget the associated bugs and subscriptions.""" | ||
104 | 180 | for spec in self._getSpecifications(series): | ||
105 | 181 | spec.proposeGoal(None, self.user) | ||
106 | 182 | for bugtask in self._getBugtasks(series): | ||
107 | 183 | # Bugtasks cannot be deleted directly. In this case, the bugtask | ||
108 | 184 | # is already reported on the product, so the series bugtask has | ||
109 | 185 | # no purpose without a series. | ||
110 | 186 | Store.of(bugtask).remove(bugtask) | ||
111 | 187 | |||
112 | 161 | def _deleteProductSeries(self, series): | 188 | def _deleteProductSeries(self, series): |
116 | 162 | """Remove the series and delete/unlink related objects.""" | 189 | """Remove the series and delete/unlink related objects. |
117 | 163 | # Delete all milestones, releases, and files. | 190 | |
118 | 164 | # Any associated bugtasks and specifications are untargeted. | 191 | All subordinate milestones, releases, and files will be deleted. |
119 | 192 | Milestone bugs and blueprints will be untargeted. | ||
120 | 193 | Series bugs and blueprints will be untargeted. | ||
121 | 194 | Series and milestone structural subscriptions are unsubscribed. | ||
122 | 195 | Series branches are unlinked. | ||
123 | 196 | """ | ||
124 | 197 | self._unsubscribe_structure(series) | ||
125 | 198 | self._remove_series_bugs_and_specifications(series) | ||
126 | 199 | series.branch = None | ||
127 | 200 | |||
128 | 165 | for milestone in series.all_milestones: | 201 | for milestone in series.all_milestones: |
129 | 166 | self._deleteMilestone(milestone) | 202 | self._deleteMilestone(milestone) |
130 | 167 | # Series are not deleted because some objects like translations are | 203 | # Series are not deleted because some objects like translations are |
131 | @@ -174,6 +210,7 @@ | |||
132 | 174 | 210 | ||
133 | 175 | def _deleteMilestone(self, milestone): | 211 | def _deleteMilestone(self, milestone): |
134 | 176 | """Delete a milestone and unlink related objects.""" | 212 | """Delete a milestone and unlink related objects.""" |
135 | 213 | self._unsubscribe_structure(milestone) | ||
136 | 177 | for bugtask in self._getBugtasks(milestone): | 214 | for bugtask in self._getBugtasks(milestone): |
137 | 178 | bugtask.milestone = None | 215 | bugtask.milestone = None |
138 | 179 | for spec in self._getSpecifications(milestone): | 216 | for spec in self._getSpecifications(milestone): |
139 | @@ -191,6 +228,7 @@ | |||
140 | 191 | 228 | ||
141 | 192 | class RegistryEditFormView(LaunchpadEditFormView): | 229 | class RegistryEditFormView(LaunchpadEditFormView): |
142 | 193 | """A base class that provides consistent edit form behaviour.""" | 230 | """A base class that provides consistent edit form behaviour.""" |
143 | 231 | |||
144 | 194 | @property | 232 | @property |
145 | 195 | def page_title(self): | 233 | def page_title(self): |
146 | 196 | """The page title.""" | 234 | """The page title.""" |
147 | 197 | 235 | ||
148 | === modified file 'lib/lp/registry/browser/productseries.py' | |||
149 | --- lib/lp/registry/browser/productseries.py 2009-09-23 14:58:12 +0000 | |||
150 | +++ lib/lp/registry/browser/productseries.py 2009-10-05 16:45:23 +0000 | |||
151 | @@ -508,7 +508,7 @@ | |||
152 | 508 | @cachedproperty | 508 | @cachedproperty |
153 | 509 | def bugtasks(self): | 509 | def bugtasks(self): |
154 | 510 | """A list of all `IBugTask`s targeted to this series.""" | 510 | """A list of all `IBugTask`s targeted to this series.""" |
156 | 511 | all_bugtasks = [] | 511 | all_bugtasks = self._getBugtasks(self.context) |
157 | 512 | for milestone in self.milestones: | 512 | for milestone in self.milestones: |
158 | 513 | all_bugtasks.extend(self._getBugtasks(milestone)) | 513 | all_bugtasks.extend(self._getBugtasks(milestone)) |
159 | 514 | return all_bugtasks | 514 | return all_bugtasks |
160 | @@ -516,7 +516,7 @@ | |||
161 | 516 | @cachedproperty | 516 | @cachedproperty |
162 | 517 | def specifications(self): | 517 | def specifications(self): |
163 | 518 | """A list of all `ISpecification`s targeted to this series.""" | 518 | """A list of all `ISpecification`s targeted to this series.""" |
165 | 519 | all_specifications = [] | 519 | all_specifications = self._getSpecifications(self.context) |
166 | 520 | for milestone in self.milestones: | 520 | for milestone in self.milestones: |
167 | 521 | all_specifications.extend(self._getSpecifications(milestone)) | 521 | all_specifications.extend(self._getSpecifications(milestone)) |
168 | 522 | return all_specifications | 522 | return all_specifications |
169 | @@ -526,6 +526,11 @@ | |||
170 | 526 | """Does the series have any targeted bugtasks or specifications.""" | 526 | """Does the series have any targeted bugtasks or specifications.""" |
171 | 527 | return len(self.bugtasks) > 0 or len(self.specifications) > 0 | 527 | return len(self.bugtasks) > 0 or len(self.specifications) > 0 |
172 | 528 | 528 | ||
173 | 529 | @property | ||
174 | 530 | def has_linked_branch(self): | ||
175 | 531 | """Is the series linked to a branch.""" | ||
176 | 532 | return self.context.branch is not None | ||
177 | 533 | |||
178 | 529 | @cachedproperty | 534 | @cachedproperty |
179 | 530 | def product_release_files(self): | 535 | def product_release_files(self): |
180 | 531 | """A list of all `IProductReleaseFile`s that belong to this series.""" | 536 | """A list of all `IProductReleaseFile`s that belong to this series.""" |
181 | 532 | 537 | ||
182 | === modified file 'lib/lp/registry/browser/tests/milestone-views.txt' | |||
183 | --- lib/lp/registry/browser/tests/milestone-views.txt 2009-09-22 16:21:12 +0000 | |||
184 | +++ lib/lp/registry/browser/tests/milestone-views.txt 2009-10-05 16:45:22 +0000 | |||
185 | @@ -654,6 +654,9 @@ | |||
186 | 654 | >>> bug = factory.makeBug(product=firefox) | 654 | >>> bug = factory.makeBug(product=firefox) |
187 | 655 | >>> bugtask = bug.bugtasks[0] | 655 | >>> bugtask = bug.bugtasks[0] |
188 | 656 | >>> bugtask.milestone = milestone | 656 | >>> bugtask.milestone = milestone |
189 | 657 | >>> subscription = milestone.addSubscription(owner, owner) | ||
190 | 658 | >>> [subscription for subscription in owner.structural_subscriptions] | ||
191 | 659 | [<StructuralSubscription ...>] | ||
192 | 657 | 660 | ||
193 | 658 | >>> view = create_initialized_view(milestone, '+delete') | 661 | >>> view = create_initialized_view(milestone, '+delete') |
194 | 659 | >>> [bugtask.milestone.name for bugtask in view.bugtasks] | 662 | >>> [bugtask.milestone.name for bugtask in view.bugtasks] |
195 | @@ -685,6 +688,9 @@ | |||
196 | 685 | >>> print bugtask.milestone | 688 | >>> print bugtask.milestone |
197 | 686 | None | 689 | None |
198 | 687 | 690 | ||
199 | 691 | >>> [subscription for subscription in owner.structural_subscriptions] | ||
200 | 692 | [] | ||
201 | 693 | |||
202 | 688 | No Privileges Person cannot access this view because he is neither the | 694 | No Privileges Person cannot access this view because he is neither the |
203 | 689 | project owner or series driver.. | 695 | project owner or series driver.. |
204 | 690 | 696 | ||
205 | 691 | 697 | ||
206 | === modified file 'lib/lp/registry/browser/tests/productseries-views.txt' | |||
207 | --- lib/lp/registry/browser/tests/productseries-views.txt 2009-09-12 06:11:08 +0000 | |||
208 | +++ lib/lp/registry/browser/tests/productseries-views.txt 2009-10-05 16:45:23 +0000 | |||
209 | @@ -229,6 +229,8 @@ | |||
210 | 229 | [] | 229 | [] |
211 | 230 | >>> view.product_release_files | 230 | >>> view.product_release_files |
212 | 231 | [] | 231 | [] |
213 | 232 | >>> view.has_linked_branch | ||
214 | 233 | False | ||
215 | 232 | 234 | ||
216 | 233 | Most series that are deleted do not have any related objects, but a small | 235 | Most series that are deleted do not have any related objects, but a small |
217 | 234 | portion do. | 236 | portion do. |
218 | @@ -244,18 +246,38 @@ | |||
219 | 244 | >>> bugtask = bug.bugtasks[0] | 246 | >>> bugtask = bug.bugtasks[0] |
220 | 245 | >>> bugtask.milestone = milestone_two | 247 | >>> bugtask.milestone = milestone_two |
221 | 246 | 248 | ||
222 | 249 | >>> owner = product.owner | ||
223 | 250 | >>> series_specification = factory.makeSpecification(product=product) | ||
224 | 251 | >>> series_specification.proposeGoal(productseries, owner) | ||
225 | 252 | >>> series_bugtask = factory.makeBugTask(bug=bug, target=productseries) | ||
226 | 253 | >>> subscription = productseries.addSubscription(owner, owner) | ||
227 | 254 | >>> productseries.branch = factory.makeBranch() | ||
228 | 255 | |||
229 | 247 | >>> view = create_view(productseries, name='+delete') | 256 | >>> view = create_view(productseries, name='+delete') |
230 | 248 | >>> [milestone.name for milestone in view.milestones] | 257 | >>> [milestone.name for milestone in view.milestones] |
231 | 249 | [u'0.2', u'0.1'] | 258 | [u'0.2', u'0.1'] |
232 | 250 | >>> view.has_bugtasks_and_specifications | 259 | >>> view.has_bugtasks_and_specifications |
233 | 251 | True | 260 | True |
241 | 252 | >>> [bugtask.milestone.name for bugtask in view.bugtasks] | 261 | >>> for bugtask in view.bugtasks: |
242 | 253 | [u'0.2'] | 262 | ... if bugtask.milestone is not None: |
243 | 254 | >>> [spec.milestone.name for spec in view.specifications] | 263 | ... print bugtask.milestone.name |
244 | 255 | [u'0.1'] | 264 | ... else: |
245 | 256 | 265 | ... print bugtask.target.name | |
246 | 257 | # Listing and deleting product release files is done in the story | 266 | rabbit |
247 | 258 | # because they require the Librarian to be running. | 267 | 0.2 |
248 | 268 | >>> for spec in view.specifications: | ||
249 | 269 | ... if spec.milestone is not None: | ||
250 | 270 | ... print spec.milestone.name | ||
251 | 271 | ... else: | ||
252 | 272 | ... print spec.goal.name | ||
253 | 273 | rabbit | ||
254 | 274 | 0.1 | ||
255 | 275 | |||
256 | 276 | >>> view.has_linked_branch | ||
257 | 277 | True | ||
258 | 278 | |||
259 | 279 | # Listing and deleting product release files is done in | ||
260 | 280 | # product-release-views because they require the Librarian to be running. | ||
261 | 259 | 281 | ||
262 | 260 | Series that are the active focus of development cannot be deleted. The | 282 | Series that are the active focus of development cannot be deleted. The |
263 | 261 | view's can_delete property checks this rule. | 283 | view's can_delete property checks this rule. |
264 | @@ -291,7 +313,8 @@ | |||
265 | 291 | Calling the view's delete action on a series that can be deleted will | 313 | Calling the view's delete action on a series that can be deleted will |
266 | 292 | untarget the bugtasks and specifications that are targeted to the | 314 | untarget the bugtasks and specifications that are targeted to the |
267 | 293 | series' milestones. The milestones, releases, and release files are | 315 | series' milestones. The milestones, releases, and release files are |
269 | 294 | deleted. | 316 | deleted. Bugs and blueprints targeted to the series are unassigned. |
270 | 317 | Series structural subscriptions are removed. Branch links are removed. | ||
271 | 295 | 318 | ||
272 | 296 | >>> view = create_initialized_view(productseries, '+delete', form=form) | 319 | >>> view = create_initialized_view(productseries, '+delete', form=form) |
273 | 297 | >>> for notification in view.request.response.notifications: | 320 | >>> for notification in view.request.response.notifications: |
274 | @@ -308,11 +331,17 @@ | |||
275 | 308 | None | 331 | None |
276 | 309 | >>> print bugtask.milestone | 332 | >>> print bugtask.milestone |
277 | 310 | None | 333 | None |
278 | 334 | >>> bugtask.related_tasks | ||
279 | 335 | [] | ||
280 | 336 | >>> print series_specification.milestone | ||
281 | 337 | None | ||
282 | 338 | >>> [subscription for subscription in owner.structural_subscriptions] | ||
283 | 339 | [] | ||
284 | 311 | 340 | ||
285 | 312 | The series was not actually deleted because there are problematic objects | 341 | The series was not actually deleted because there are problematic objects |
286 | 313 | like translations. The series are assigned to the Obsolete Junk project. | 342 | like translations. The series are assigned to the Obsolete Junk project. |
287 | 314 | The series name is changed to 'product_name-series_name-date_created' to | 343 | The series name is changed to 'product_name-series_name-date_created' to |
289 | 315 | avoid conflicts. | 344 | avoid conflicts. The linked branch is removed. |
290 | 316 | 345 | ||
291 | 317 | >>> from zope.component import getUtility | 346 | >>> from zope.component import getUtility |
292 | 318 | >>> from canonical.launchpad.interfaces.launchpad import ( | 347 | >>> from canonical.launchpad.interfaces.launchpad import ( |
293 | @@ -323,7 +352,8 @@ | |||
294 | 323 | True | 352 | True |
295 | 324 | >>> print productseries.name | 353 | >>> print productseries.name |
296 | 325 | field-rabbit-20090501-193424 | 354 | field-rabbit-20090501-193424 |
298 | 326 | 355 | >>> print productseries.branch | |
299 | 356 | None | ||
300 | 327 | 357 | ||
301 | 328 | Linking packages | 358 | Linking packages |
302 | 329 | ---------------- | 359 | ---------------- |
303 | 330 | 360 | ||
304 | === modified file 'lib/lp/registry/doc/milestone.txt' | |||
305 | --- lib/lp/registry/doc/milestone.txt 2009-08-13 19:03:36 +0000 | |||
306 | +++ lib/lp/registry/doc/milestone.txt 2009-10-05 16:45:22 +0000 | |||
307 | @@ -496,3 +496,13 @@ | |||
308 | 496 | ... | 496 | ... |
309 | 497 | AssertionError: You cannot delete a milestone which has specifications | 497 | AssertionError: You cannot delete a milestone which has specifications |
310 | 498 | targeted to it. | 498 | targeted to it. |
311 | 499 | |||
312 | 500 | If a milestone has a structural subscription, it cannot be deleted. | ||
313 | 501 | |||
314 | 502 | >>> milestone = ff_onedotzero.newMilestone('1.0.14') | ||
315 | 503 | >>> subscription = milestone.addSubscription(owner, owner) | ||
316 | 504 | >>> milestone.destroySelf() | ||
317 | 505 | Traceback (most recent call last): | ||
318 | 506 | ... | ||
319 | 507 | AssertionError: You cannot delete a milestone which has structural | ||
320 | 508 | subscriptions. | ||
321 | 499 | 509 | ||
322 | === modified file 'lib/lp/registry/model/milestone.py' | |||
323 | --- lib/lp/registry/model/milestone.py 2009-08-24 04:03:27 +0000 | |||
324 | +++ lib/lp/registry/model/milestone.py 2009-10-05 16:45:23 +0000 | |||
325 | @@ -188,6 +188,9 @@ | |||
326 | 188 | """See `IMilestone`.""" | 188 | """See `IMilestone`.""" |
327 | 189 | params = BugTaskSearchParams(milestone=self, user=None) | 189 | params = BugTaskSearchParams(milestone=self, user=None) |
328 | 190 | bugtasks = getUtility(IBugTaskSet).search(params) | 190 | bugtasks = getUtility(IBugTaskSet).search(params) |
329 | 191 | assert len(self.getSubscriptions()) == 0, ( | ||
330 | 192 | "You cannot delete a milestone which has structural " | ||
331 | 193 | "subscriptions.") | ||
332 | 191 | assert bugtasks.count() == 0, ( | 194 | assert bugtasks.count() == 0, ( |
333 | 192 | "You cannot delete a milestone which has bugtasks targeted " | 195 | "You cannot delete a milestone which has bugtasks targeted " |
334 | 193 | "to it.") | 196 | "to it.") |
335 | @@ -238,6 +241,7 @@ | |||
336 | 238 | """See lp.registry.interfaces.milestone.IMilestoneSet.""" | 241 | """See lp.registry.interfaces.milestone.IMilestoneSet.""" |
337 | 239 | return Milestone.selectBy(active=True, orderBy='id') | 242 | return Milestone.selectBy(active=True, orderBy='id') |
338 | 240 | 243 | ||
339 | 244 | |||
340 | 241 | class ProjectMilestone(HasBugsBase): | 245 | class ProjectMilestone(HasBugsBase): |
341 | 242 | """A virtual milestone implementation for project. | 246 | """A virtual milestone implementation for project. |
342 | 243 | 247 | ||
343 | @@ -301,4 +305,3 @@ | |||
344 | 301 | def official_bug_tags(self): | 305 | def official_bug_tags(self): |
345 | 302 | """See `IHasBugs`.""" | 306 | """See `IHasBugs`.""" |
346 | 303 | return self.target.official_bug_tags | 307 | return self.target.official_bug_tags |
347 | 304 | |||
348 | 305 | 308 | ||
349 | === modified file 'lib/lp/registry/templates/productseries-delete.pt' | |||
350 | --- lib/lp/registry/templates/productseries-delete.pt 2009-08-11 21:31:51 +0000 | |||
351 | +++ lib/lp/registry/templates/productseries-delete.pt 2009-10-05 16:45:23 +0000 | |||
352 | @@ -30,10 +30,12 @@ | |||
353 | 30 | </tal:no-files> | 30 | </tal:no-files> |
354 | 31 | </p> | 31 | </p> |
355 | 32 | 32 | ||
357 | 33 | <ul id="milestones" tal:condition="view/milestones"> | 33 | <ul id="milestones" class="subordinate" |
358 | 34 | tal:condition="view/milestones"> | ||
359 | 34 | <li tal:repeat="milestone view/milestones"> | 35 | <li tal:repeat="milestone view/milestones"> |
360 | 35 | <strong> | 36 | <strong> |
362 | 36 | <a tal:attributes="href milestone/fmt:url"><tal:name | 37 | <a class="sprite milestone" |
363 | 38 | tal:attributes="href milestone/fmt:url"><tal:name | ||
364 | 37 | content="milestone/name">0.9</tal:name><tal:codename | 39 | content="milestone/name">0.9</tal:name><tal:codename |
365 | 38 | condition="milestone/code_name"> | 40 | condition="milestone/code_name"> |
366 | 39 | "<tal:name | 41 | "<tal:name |
367 | @@ -42,9 +44,8 @@ | |||
368 | 42 | </li> | 44 | </li> |
369 | 43 | </ul> | 45 | </ul> |
370 | 44 | 46 | ||
374 | 45 | 47 | <ul id="files" class="subordinate" | |
375 | 46 | 48 | tal:condition="view/product_release_files"> | |
373 | 47 | <ul id="files" tal:condition="view/product_release_files"> | ||
376 | 48 | <li tal:repeat="file view/product_release_files"> | 49 | <li tal:repeat="file view/product_release_files"> |
377 | 49 | <strong tal:content="file/libraryfile/filename">foo.tgz</strong> | 50 | <strong tal:content="file/libraryfile/filename">foo.tgz</strong> |
378 | 50 | </li> | 51 | </li> |
379 | @@ -54,7 +55,7 @@ | |||
380 | 54 | The following bugs and blueprints will be <em>untargeted</em>: | 55 | The following bugs and blueprints will be <em>untargeted</em>: |
381 | 55 | </p> | 56 | </p> |
382 | 56 | 57 | ||
384 | 57 | <ul id="bugtasks-and-blueprints" | 58 | <ul id="bugtasks-and-blueprints" class="subordinate" |
385 | 58 | tal:condition="view/has_bugtasks_and_specifications"> | 59 | tal:condition="view/has_bugtasks_and_specifications"> |
386 | 59 | <li tal:repeat="bugtask view/bugtasks" | 60 | <li tal:repeat="bugtask view/bugtasks" |
387 | 60 | tal:content="structure bugtask/bug/fmt:link">bug 1 | 61 | tal:content="structure bugtask/bug/fmt:link">bug 1 |
388 | @@ -64,6 +65,11 @@ | |||
389 | 64 | </li> | 65 | </li> |
390 | 65 | </ul> | 66 | </ul> |
391 | 66 | 67 | ||
392 | 68 | <p tal:condition="view/has_linked_branch"> | ||
393 | 69 | The associated branch will be <em>unlinked</em>: | ||
394 | 70 | <a tal:replace="structure view/context/branch/fmt:link" /> | ||
395 | 71 | </p> | ||
396 | 72 | |||
397 | 67 | <p> | 73 | <p> |
398 | 68 | Series deletion is permanent. | 74 | Series deletion is permanent. |
399 | 69 | </p> | 75 | </p> |
This is my branch to fix the series/milestone delete oopes and the bad
lost branch experience.
lp:~sinzui/launchpad/delete-structural-target /bugs.launchpad .net/bugs/ 416483 /bugs.launchpad .net/bugs/ 402725 /bugs.launchpad .net/bugs/ 400844 views|milestone -views"
-t "doc/milestone"
-t "xx-productseri es-delete" implementation: Deryck, beuno
Diff size: 399
Launchpad bug: https:/
https:/
https:/
Test command: ./bin/test -vv -t "productseries-
Pre-
Target release: 3.1.10
= Fix the series/milestone delete oopes =
Bug 416483 [deletion of series and milestone must remove structural
subscriptions]
OOPS-1318EA4 shows that structural subscriptions are not removed before
the destroySelf() method is called.
Bug 402725 [Delete series and milestones does not untarget series-targeted
blueprints and bugs]
As OOPS-1298B1901 shows, The delete action failed because there are still
blueprints targeted to the series. The blueprints targeted to milestones
were removed, but not the ones to the series. This situation may also
happen for bugs (OOPS-1321EB223)
Bug 400844 [Deleting the "trunk" series linked to branch messes up the divisiui/ trunk to "lp:obsolete-junk/divisiui- 20090625- 210501" . This is frightening, insulting, and gives the
Bazaar repository]
Deleting a series linked to a branch had the effect of renaming our
repository from ~commonsense/
trunk-
impression that the branch is going to be deleted soon. The branch should
have been unlinked before the deletion/move and the UI should explain
what it did and link to the branch.
== Rules ==
Bug 416483 [deletion of series and milestone must remove structural iewMixin. _unlinkSubscrip tion(series_ or_milestone)
subscriptions]
Create RegistryDeleteV
that will remove the structural subscriptions for series and milestones.
Bug 402725 [Delete series and milestones does not untarget series-targeted eteViewMixin. _untarget_ bugs_and_ specifications( series_ or_milestone)
blueprints and bugs]
Exract the loop to untarget spec and bugs from a milestone to also
handle series.
RegistryDel
of if getting the spec and bugs are very different, the 3 line loop can
be copied and rewritten for this case
Bug 400844 [Deleting the "trunk" series linked to branch messes up the
Bazaar repository]
Always set the series.branch to None.
ADDENDUM
The IProductSeriesB ugTask must be deleted because there is nothing to
reassign it to. This means that security.py needed a new rule, and that
means that this needs to be merged to db-devel. Delete is not exposed on
IBugtask, nor should it be because destroySelf() happens by 'need' rather
than by 'want'.
== QA ==
On Staging
* Create a structural subscription on a milestone and series.
* Target bugs to the series
* Target blueprints to the series
* Set the branch to the series.
* Choose delete.
* Verify the page explains what these objects will be untargeted/
unlinked.
* Choose delete
* Verify it did not oops
* Verify the branch...