Merge lp:~sinzui/launchpad/delete-structural-target into lp:launchpad/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
Reviewer Review Type Date Requested Status
Eleanor Berger (community) code Approve
Review via email: mp+12877@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (5.5 KiB)

This is my branch to fix the series/milestone delete oopes and the bad
lost branch experience.

    lp:~sinzui/launchpad/delete-structural-target
    Diff size: 399
    Launchpad bug: https://bugs.launchpad.net/bugs/416483
                   https://bugs.launchpad.net/bugs/402725
                   https://bugs.launchpad.net/bugs/400844
    Test command: ./bin/test -vv -t "productseries-views|milestone-views"
                                 -t "doc/milestone"
                                 -t "xx-productseries-delete"
    Pre-implementation: Deryck, beuno
    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
    Bazaar repository]
    Deleting a series linked to a branch had the effect of renaming our
    repository from ~commonsense/divisiui/trunk to "lp:obsolete-junk/divisiui-
    trunk-20090625-210501". This is frightening, insulting, and gives the
    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
    subscriptions]
    Create RegistryDeleteViewMixin._unlinkSubscription(series_or_milestone)
    that will remove the structural subscriptions for series and milestones.

Bug 402725 [Delete series and milestones does not untarget series-targeted
    blueprints and bugs]
    Exract the loop to untarget spec and bugs from a milestone to also
    handle series.
    RegistryDeleteViewMixin._untarget_bugs_and_specifications(series_or_milestone)
    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 IProductSeriesBugTask 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...

Read more...

Revision history for this message
Eleanor Berger (intellectronica) wrote :

We discussed changing obj.destroySelf to Store.of(obj).remove(obj) so that we don't add new sqlobject-oriented code.

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
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2009-10-02 14:02:25 +0000
+++ database/schema/security.cfg 2009-10-05 16:45:23 +0000
@@ -891,7 +891,7 @@
891public.bugpackageinfestation = SELECT, INSERT, UPDATE891public.bugpackageinfestation = SELECT, INSERT, UPDATE
892public.bugproductinfestation = SELECT, INSERT, UPDATE892public.bugproductinfestation = SELECT, INSERT, UPDATE
893public.bugsubscription = SELECT, INSERT, UPDATE, DELETE893public.bugsubscription = SELECT, INSERT, UPDATE, DELETE
894public.bugtask = SELECT, INSERT, UPDATE894public.bugtask = SELECT, INSERT, UPDATE, DELETE
895public.bugtracker = SELECT, INSERT, UPDATE, DELETE895public.bugtracker = SELECT, INSERT, UPDATE, DELETE
896public.bugtrackeralias = SELECT, INSERT, UPDATE, DELETE896public.bugtrackeralias = SELECT, INSERT, UPDATE, DELETE
897public.bugwatch = SELECT, INSERT, UPDATE, DELETE897public.bugwatch = SELECT, INSERT, UPDATE, DELETE
898898
=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
--- lib/canonical/launchpad/icing/style-3-0.css 2009-10-02 13:56:25 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css 2009-10-05 16:45:23 +0000
@@ -446,6 +446,9 @@
446 padding: 0 1.5em 0 0;446 padding: 0 1.5em 0 0;
447 }447 }
448.subordinate {448.subordinate {
449 margin-left: 2em;
450 }
451ol.subordinate {
449 margin-left: 4em;452 margin-left: 4em;
450 }453 }
451.two-column-list li,454.two-column-list li,
452455
=== modified file 'lib/canonical/launchpad/templates/launchpad-login.pt'
--- lib/canonical/launchpad/templates/launchpad-login.pt 2009-07-17 17:59:07 +0000
+++ lib/canonical/launchpad/templates/launchpad-login.pt 2009-10-05 16:45:22 +0000
@@ -169,11 +169,11 @@
169 <p>169 <p>
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>
171 171
172 <ol class="subordinate">172 <ol class="subordinate">
173 <li>Make sure cookies are enabled in your browser.</li>173 <li>Make sure cookies are enabled in your browser.</li>
174 <li>Enter your e-mail address.</li>174 <li>Enter your e-mail address.</li>
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>
176 </ol>176 </ol>
177 177
178178
179179
180180
=== modified file 'lib/lp/registry/browser/__init__.py'
--- lib/lp/registry/browser/__init__.py 2009-09-22 16:21:12 +0000
+++ lib/lp/registry/browser/__init__.py 2009-10-05 16:45:23 +0000
@@ -18,6 +18,8 @@
1818
19from zope.component import getUtility19from zope.component import getUtility
2020
21from storm.store import Store
22
21from lp.bugs.interfaces.bugtask import BugTaskSearchParams, IBugTaskSet23from lp.bugs.interfaces.bugtask import BugTaskSearchParams, IBugTaskSet
22from lp.registry.interfaces.productseries import IProductSeries24from lp.registry.interfaces.productseries import IProductSeries
23from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities25from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
@@ -136,15 +138,22 @@
136 """The context's URL."""138 """The context's URL."""
137 return canonical_url(self.context)139 return canonical_url(self.context)
138140
139 def _getBugtasks(self, milestone):141 def _getBugtasks(self, target):
140 """Return the list `IBugTask`s targeted to the milestone."""142 """Return the list `IBugTask`s associated with the target."""
141 params = BugTaskSearchParams(milestone=milestone, user=None)143 if IProductSeries.providedBy(target):
144 params = BugTaskSearchParams(user=None)
145 params.setProductSeries(target)
146 else:
147 params = BugTaskSearchParams(milestone=target, user=None)
142 bugtasks = getUtility(IBugTaskSet).search(params)148 bugtasks = getUtility(IBugTaskSet).search(params)
143 return list(bugtasks)149 return list(bugtasks)
144150
145 def _getSpecifications(self, milestone):151 def _getSpecifications(self, target):
146 """Return the list `ISpecification`s targeted to the milestone."""152 """Return the list `ISpecification`s associated to the target."""
147 return list(milestone.specifications)153 if IProductSeries.providedBy(target):
154 return list(target.all_specifications)
155 else:
156 return list(target.specifications)
148157
149 def _getProductRelease(self, milestone):158 def _getProductRelease(self, milestone):
150 """The `IProductRelease` associated with the milestone."""159 """The `IProductRelease` associated with the milestone."""
@@ -158,10 +167,37 @@
158 else:167 else:
159 return []168 return []
160169
170 def _unsubscribe_structure(self, structure):
171 """Removed the subscriptions from structure."""
172 for subscription in structure.getSubscriptions():
173 # The owner of the subscription or an admin are the only users
174 # that can destroy a subscription, but this rule cannot prevent
175 # the owner from removing the structure.
176 Store.of(subscription).remove(subscription)
177
178 def _remove_series_bugs_and_specifications(self, series):
179 """Untarget the associated bugs and subscriptions."""
180 for spec in self._getSpecifications(series):
181 spec.proposeGoal(None, self.user)
182 for bugtask in self._getBugtasks(series):
183 # Bugtasks cannot be deleted directly. In this case, the bugtask
184 # is already reported on the product, so the series bugtask has
185 # no purpose without a series.
186 Store.of(bugtask).remove(bugtask)
187
161 def _deleteProductSeries(self, series):188 def _deleteProductSeries(self, series):
162 """Remove the series and delete/unlink related objects."""189 """Remove the series and delete/unlink related objects.
163 # Delete all milestones, releases, and files.190
164 # Any associated bugtasks and specifications are untargeted.191 All subordinate milestones, releases, and files will be deleted.
192 Milestone bugs and blueprints will be untargeted.
193 Series bugs and blueprints will be untargeted.
194 Series and milestone structural subscriptions are unsubscribed.
195 Series branches are unlinked.
196 """
197 self._unsubscribe_structure(series)
198 self._remove_series_bugs_and_specifications(series)
199 series.branch = None
200
165 for milestone in series.all_milestones:201 for milestone in series.all_milestones:
166 self._deleteMilestone(milestone)202 self._deleteMilestone(milestone)
167 # Series are not deleted because some objects like translations are203 # Series are not deleted because some objects like translations are
@@ -174,6 +210,7 @@
174210
175 def _deleteMilestone(self, milestone):211 def _deleteMilestone(self, milestone):
176 """Delete a milestone and unlink related objects."""212 """Delete a milestone and unlink related objects."""
213 self._unsubscribe_structure(milestone)
177 for bugtask in self._getBugtasks(milestone):214 for bugtask in self._getBugtasks(milestone):
178 bugtask.milestone = None215 bugtask.milestone = None
179 for spec in self._getSpecifications(milestone):216 for spec in self._getSpecifications(milestone):
@@ -191,6 +228,7 @@
191228
192class RegistryEditFormView(LaunchpadEditFormView):229class RegistryEditFormView(LaunchpadEditFormView):
193 """A base class that provides consistent edit form behaviour."""230 """A base class that provides consistent edit form behaviour."""
231
194 @property232 @property
195 def page_title(self):233 def page_title(self):
196 """The page title."""234 """The page title."""
197235
=== modified file 'lib/lp/registry/browser/productseries.py'
--- lib/lp/registry/browser/productseries.py 2009-09-23 14:58:12 +0000
+++ lib/lp/registry/browser/productseries.py 2009-10-05 16:45:23 +0000
@@ -508,7 +508,7 @@
508 @cachedproperty508 @cachedproperty
509 def bugtasks(self):509 def bugtasks(self):
510 """A list of all `IBugTask`s targeted to this series."""510 """A list of all `IBugTask`s targeted to this series."""
511 all_bugtasks = []511 all_bugtasks = self._getBugtasks(self.context)
512 for milestone in self.milestones:512 for milestone in self.milestones:
513 all_bugtasks.extend(self._getBugtasks(milestone))513 all_bugtasks.extend(self._getBugtasks(milestone))
514 return all_bugtasks514 return all_bugtasks
@@ -516,7 +516,7 @@
516 @cachedproperty516 @cachedproperty
517 def specifications(self):517 def specifications(self):
518 """A list of all `ISpecification`s targeted to this series."""518 """A list of all `ISpecification`s targeted to this series."""
519 all_specifications = []519 all_specifications = self._getSpecifications(self.context)
520 for milestone in self.milestones:520 for milestone in self.milestones:
521 all_specifications.extend(self._getSpecifications(milestone))521 all_specifications.extend(self._getSpecifications(milestone))
522 return all_specifications522 return all_specifications
@@ -526,6 +526,11 @@
526 """Does the series have any targeted bugtasks or specifications."""526 """Does the series have any targeted bugtasks or specifications."""
527 return len(self.bugtasks) > 0 or len(self.specifications) > 0527 return len(self.bugtasks) > 0 or len(self.specifications) > 0
528528
529 @property
530 def has_linked_branch(self):
531 """Is the series linked to a branch."""
532 return self.context.branch is not None
533
529 @cachedproperty534 @cachedproperty
530 def product_release_files(self):535 def product_release_files(self):
531 """A list of all `IProductReleaseFile`s that belong to this series."""536 """A list of all `IProductReleaseFile`s that belong to this series."""
532537
=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
--- lib/lp/registry/browser/tests/milestone-views.txt 2009-09-22 16:21:12 +0000
+++ lib/lp/registry/browser/tests/milestone-views.txt 2009-10-05 16:45:22 +0000
@@ -654,6 +654,9 @@
654 >>> bug = factory.makeBug(product=firefox)654 >>> bug = factory.makeBug(product=firefox)
655 >>> bugtask = bug.bugtasks[0]655 >>> bugtask = bug.bugtasks[0]
656 >>> bugtask.milestone = milestone656 >>> bugtask.milestone = milestone
657 >>> subscription = milestone.addSubscription(owner, owner)
658 >>> [subscription for subscription in owner.structural_subscriptions]
659 [<StructuralSubscription ...>]
657660
658 >>> view = create_initialized_view(milestone, '+delete')661 >>> view = create_initialized_view(milestone, '+delete')
659 >>> [bugtask.milestone.name for bugtask in view.bugtasks]662 >>> [bugtask.milestone.name for bugtask in view.bugtasks]
@@ -685,6 +688,9 @@
685 >>> print bugtask.milestone688 >>> print bugtask.milestone
686 None689 None
687690
691 >>> [subscription for subscription in owner.structural_subscriptions]
692 []
693
688No Privileges Person cannot access this view because he is neither the694No Privileges Person cannot access this view because he is neither the
689project owner or series driver..695project owner or series driver..
690696
691697
=== modified file 'lib/lp/registry/browser/tests/productseries-views.txt'
--- lib/lp/registry/browser/tests/productseries-views.txt 2009-09-12 06:11:08 +0000
+++ lib/lp/registry/browser/tests/productseries-views.txt 2009-10-05 16:45:23 +0000
@@ -229,6 +229,8 @@
229 []229 []
230 >>> view.product_release_files230 >>> view.product_release_files
231 []231 []
232 >>> view.has_linked_branch
233 False
232234
233Most series that are deleted do not have any related objects, but a small235Most series that are deleted do not have any related objects, but a small
234portion do.236portion do.
@@ -244,18 +246,38 @@
244 >>> bugtask = bug.bugtasks[0]246 >>> bugtask = bug.bugtasks[0]
245 >>> bugtask.milestone = milestone_two247 >>> bugtask.milestone = milestone_two
246248
249 >>> owner = product.owner
250 >>> series_specification = factory.makeSpecification(product=product)
251 >>> series_specification.proposeGoal(productseries, owner)
252 >>> series_bugtask = factory.makeBugTask(bug=bug, target=productseries)
253 >>> subscription = productseries.addSubscription(owner, owner)
254 >>> productseries.branch = factory.makeBranch()
255
247 >>> view = create_view(productseries, name='+delete')256 >>> view = create_view(productseries, name='+delete')
248 >>> [milestone.name for milestone in view.milestones]257 >>> [milestone.name for milestone in view.milestones]
249 [u'0.2', u'0.1']258 [u'0.2', u'0.1']
250 >>> view.has_bugtasks_and_specifications259 >>> view.has_bugtasks_and_specifications
251 True260 True
252 >>> [bugtask.milestone.name for bugtask in view.bugtasks]261 >>> for bugtask in view.bugtasks:
253 [u'0.2']262 ... if bugtask.milestone is not None:
254 >>> [spec.milestone.name for spec in view.specifications]263 ... print bugtask.milestone.name
255 [u'0.1']264 ... else:
256265 ... print bugtask.target.name
257 # Listing and deleting product release files is done in the story266 rabbit
258 # because they require the Librarian to be running.267 0.2
268 >>> for spec in view.specifications:
269 ... if spec.milestone is not None:
270 ... print spec.milestone.name
271 ... else:
272 ... print spec.goal.name
273 rabbit
274 0.1
275
276 >>> view.has_linked_branch
277 True
278
279 # Listing and deleting product release files is done in
280 # product-release-views because they require the Librarian to be running.
259281
260Series that are the active focus of development cannot be deleted. The282Series that are the active focus of development cannot be deleted. The
261view's can_delete property checks this rule.283view's can_delete property checks this rule.
@@ -291,7 +313,8 @@
291Calling the view's delete action on a series that can be deleted will313Calling the view's delete action on a series that can be deleted will
292untarget the bugtasks and specifications that are targeted to the314untarget the bugtasks and specifications that are targeted to the
293series' milestones. The milestones, releases, and release files are315series' milestones. The milestones, releases, and release files are
294deleted.316deleted. Bugs and blueprints targeted to the series are unassigned.
317Series structural subscriptions are removed. Branch links are removed.
295318
296 >>> view = create_initialized_view(productseries, '+delete', form=form)319 >>> view = create_initialized_view(productseries, '+delete', form=form)
297 >>> for notification in view.request.response.notifications:320 >>> for notification in view.request.response.notifications:
@@ -308,11 +331,17 @@
308 None331 None
309 >>> print bugtask.milestone332 >>> print bugtask.milestone
310 None333 None
334 >>> bugtask.related_tasks
335 []
336 >>> print series_specification.milestone
337 None
338 >>> [subscription for subscription in owner.structural_subscriptions]
339 []
311340
312The series was not actually deleted because there are problematic objects341The series was not actually deleted because there are problematic objects
313like translations. The series are assigned to the Obsolete Junk project.342like translations. The series are assigned to the Obsolete Junk project.
314The series name is changed to 'product_name-series_name-date_created' to343The series name is changed to 'product_name-series_name-date_created' to
315avoid conflicts.344avoid conflicts. The linked branch is removed.
316345
317 >>> from zope.component import getUtility346 >>> from zope.component import getUtility
318 >>> from canonical.launchpad.interfaces.launchpad import (347 >>> from canonical.launchpad.interfaces.launchpad import (
@@ -323,7 +352,8 @@
323 True352 True
324 >>> print productseries.name353 >>> print productseries.name
325 field-rabbit-20090501-193424354 field-rabbit-20090501-193424
326355 >>> print productseries.branch
356 None
327357
328Linking packages358Linking packages
329----------------359----------------
330360
=== modified file 'lib/lp/registry/doc/milestone.txt'
--- lib/lp/registry/doc/milestone.txt 2009-08-13 19:03:36 +0000
+++ lib/lp/registry/doc/milestone.txt 2009-10-05 16:45:22 +0000
@@ -496,3 +496,13 @@
496 ...496 ...
497 AssertionError: You cannot delete a milestone which has specifications497 AssertionError: You cannot delete a milestone which has specifications
498 targeted to it.498 targeted to it.
499
500If a milestone has a structural subscription, it cannot be deleted.
501
502 >>> milestone = ff_onedotzero.newMilestone('1.0.14')
503 >>> subscription = milestone.addSubscription(owner, owner)
504 >>> milestone.destroySelf()
505 Traceback (most recent call last):
506 ...
507 AssertionError: You cannot delete a milestone which has structural
508 subscriptions.
499509
=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py 2009-08-24 04:03:27 +0000
+++ lib/lp/registry/model/milestone.py 2009-10-05 16:45:23 +0000
@@ -188,6 +188,9 @@
188 """See `IMilestone`."""188 """See `IMilestone`."""
189 params = BugTaskSearchParams(milestone=self, user=None)189 params = BugTaskSearchParams(milestone=self, user=None)
190 bugtasks = getUtility(IBugTaskSet).search(params)190 bugtasks = getUtility(IBugTaskSet).search(params)
191 assert len(self.getSubscriptions()) == 0, (
192 "You cannot delete a milestone which has structural "
193 "subscriptions.")
191 assert bugtasks.count() == 0, (194 assert bugtasks.count() == 0, (
192 "You cannot delete a milestone which has bugtasks targeted "195 "You cannot delete a milestone which has bugtasks targeted "
193 "to it.")196 "to it.")
@@ -238,6 +241,7 @@
238 """See lp.registry.interfaces.milestone.IMilestoneSet."""241 """See lp.registry.interfaces.milestone.IMilestoneSet."""
239 return Milestone.selectBy(active=True, orderBy='id')242 return Milestone.selectBy(active=True, orderBy='id')
240243
244
241class ProjectMilestone(HasBugsBase):245class ProjectMilestone(HasBugsBase):
242 """A virtual milestone implementation for project.246 """A virtual milestone implementation for project.
243247
@@ -301,4 +305,3 @@
301 def official_bug_tags(self):305 def official_bug_tags(self):
302 """See `IHasBugs`."""306 """See `IHasBugs`."""
303 return self.target.official_bug_tags307 return self.target.official_bug_tags
304
305308
=== modified file 'lib/lp/registry/templates/productseries-delete.pt'
--- lib/lp/registry/templates/productseries-delete.pt 2009-08-11 21:31:51 +0000
+++ lib/lp/registry/templates/productseries-delete.pt 2009-10-05 16:45:23 +0000
@@ -30,10 +30,12 @@
30 </tal:no-files>30 </tal:no-files>
31 </p>31 </p>
3232
33 <ul id="milestones" tal:condition="view/milestones">33 <ul id="milestones" class="subordinate"
34 tal:condition="view/milestones">
34 <li tal:repeat="milestone view/milestones">35 <li tal:repeat="milestone view/milestones">
35 <strong>36 <strong>
36 <a tal:attributes="href milestone/fmt:url"><tal:name37 <a class="sprite milestone"
38 tal:attributes="href milestone/fmt:url"><tal:name
37 content="milestone/name">0.9</tal:name><tal:codename39 content="milestone/name">0.9</tal:name><tal:codename
38 condition="milestone/code_name">40 condition="milestone/code_name">
39 "<tal:name41 "<tal:name
@@ -42,9 +44,8 @@
42 </li>44 </li>
43 </ul>45 </ul>
4446
4547 <ul id="files" class="subordinate"
4648 tal:condition="view/product_release_files">
47 <ul id="files" tal:condition="view/product_release_files">
48 <li tal:repeat="file view/product_release_files">49 <li tal:repeat="file view/product_release_files">
49 <strong tal:content="file/libraryfile/filename">foo.tgz</strong>50 <strong tal:content="file/libraryfile/filename">foo.tgz</strong>
50 </li>51 </li>
@@ -54,7 +55,7 @@
54 The following bugs and blueprints will be <em>untargeted</em>:55 The following bugs and blueprints will be <em>untargeted</em>:
55 </p>56 </p>
5657
57 <ul id="bugtasks-and-blueprints"58 <ul id="bugtasks-and-blueprints" class="subordinate"
58 tal:condition="view/has_bugtasks_and_specifications">59 tal:condition="view/has_bugtasks_and_specifications">
59 <li tal:repeat="bugtask view/bugtasks"60 <li tal:repeat="bugtask view/bugtasks"
60 tal:content="structure bugtask/bug/fmt:link">bug 161 tal:content="structure bugtask/bug/fmt:link">bug 1
@@ -64,6 +65,11 @@
64 </li>65 </li>
65 </ul>66 </ul>
6667
68 <p tal:condition="view/has_linked_branch">
69 The associated branch will be <em>unlinked</em>:
70 <a tal:replace="structure view/context/branch/fmt:link" />
71 </p>
72
67 <p>73 <p>
68 Series deletion is permanent.74 Series deletion is permanent.
69 </p>75 </p>

Subscribers

People subscribed via source and target branches

to status/vote changes: