Merge lp:~allenap/launchpad/sub-search-ui-bug-656823-4 into lp:launchpad

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 12180
Proposed branch: lp:~allenap/launchpad/sub-search-ui-bug-656823-4
Merge into: lp:launchpad
Prerequisite: lp:~allenap/launchpad/sub-search-ui-bug-656823-3
Diff against target: 616 lines (+273/-42)
15 files modified
lib/lp/bugs/browser/bug.py (+1/-1)
lib/lp/bugs/browser/bugsubscriptionfilter.py (+46/-1)
lib/lp/bugs/browser/bugtarget.py (+5/-7)
lib/lp/bugs/browser/bugtask.py (+1/-1)
lib/lp/bugs/browser/configure.zcml (+9/-0)
lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py (+55/-4)
lib/lp/bugs/browser/widgets/bug.py (+62/-10)
lib/lp/bugs/browser/widgets/configure.zcml (+19/-0)
lib/lp/bugs/doc/bug-tags.txt (+45/-2)
lib/lp/bugs/doc/bugwidget.txt (+1/-1)
lib/lp/bugs/interfaces/bugsubscriptionfilter.py (+10/-7)
lib/lp/bugs/templates/bug-subscription-filter-edit.pt (+14/-0)
lib/lp/bugs/templates/bug-subscription-filter.pt (+2/-0)
lib/lp/registry/stories/person/xx-person-subscriptions.txt (+3/-0)
lib/lp/services/fields/configure.zcml (+0/-8)
To merge this branch: bzr merge lp:~allenap/launchpad/sub-search-ui-bug-656823-4
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+45824@code.launchpad.net

Commit message

[r=adeuring][ui=none][bug=656823] Edit view for bug subscription filters.

Description of the change

The main purpose of this branch is to provide an edit view for bug
subscription filters. However, to pull this off several things also
got done:

- I updated the IBugSubscriptionFilter field descriptions to work
  better in the web UI. I think they make sense for the web service
  API doc too.

- Add a new widget, BugTagsFrozenSetWidget, that subclasses
  BugTagsWidget. I made some changes to the latter to make the
  subclassing a bit more elegant.

- I moved canonical.widgets.bug to lp.bugs.browser.widgets.bug. I also
  moved the widget registration ZCML to the lp.bugs.brower.widgets
  package.

- Added some rudimentary "(edit)" links to the structural subscription
  overview page. They are there as much to make manual testing and UI
  review easier (though I doubt this branch warrants a UI review as it
  stands).

- Some imports and lint needed fixing, but I've stuffed those into a
  later branch because this branch is noisy enough.

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote :

Nice work! Just one nitpick:

> find_all_tags = exported(
> Bool(
> - title=_("All given tags must be found, or any."),
> + title=_("All given tags must be found"),

I'd prefer something like "If enabled, all tags must match, else at least one tag must match", But that's something for a UI review.

review: Approve (code)
Revision history for this message
Gavin Panella (allenap) wrote :

I've added your line as the field's description (I forgot those existed...) and changed the title to "Find all tags". If there's any more tweaking to be done it'll be caught in a UI review, as you say.

Thanks for the suggestion, and review!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bug.py'
2--- lib/lp/bugs/browser/bug.py 2010-12-21 15:28:54 +0000
3+++ lib/lp/bugs/browser/bug.py 2011-01-11 21:16:20 +0000
4@@ -86,7 +86,6 @@
5 ICanonicalUrlData,
6 ILaunchBag,
7 )
8-from canonical.widgets.bug import BugTagsWidget
9 from canonical.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
10 from canonical.widgets.project import ProjectScopeWidget
11 from lp.app.browser.launchpadform import (
12@@ -97,6 +96,7 @@
13 )
14 from lp.app.browser.stringformatter import FormattersAPI
15 from lp.app.errors import NotFoundError
16+from lp.bugs.browser.widgets.bug import BugTagsWidget
17 from lp.bugs.interfaces.bug import (
18 IBug,
19 IBugSet,
20
21=== modified file 'lib/lp/bugs/browser/bugsubscriptionfilter.py'
22--- lib/lp/bugs/browser/bugsubscriptionfilter.py 2011-01-11 21:16:19 +0000
23+++ lib/lp/bugs/browser/bugsubscriptionfilter.py 2011-01-11 21:16:20 +0000
24@@ -9,8 +9,21 @@
25 ]
26
27
28+from zope.app.form.browser import TextWidget
29+
30 from canonical.launchpad.helpers import english_list
31-from canonical.launchpad.webapp.publisher import LaunchpadView
32+from canonical.launchpad.webapp.publisher import (
33+ canonical_url,
34+ LaunchpadView,
35+ )
36+from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget
37+from lp.app.browser.launchpadform import (
38+ action,
39+ custom_widget,
40+ LaunchpadEditFormView,
41+ )
42+from lp.bugs.browser.widgets.bug import BugTagsFrozenSetWidget
43+from lp.bugs.interfaces.bugsubscriptionfilter import IBugSubscriptionFilter
44
45
46 class BugSubscriptionFilterView(LaunchpadView):
47@@ -59,3 +72,35 @@
48 sorted(tags), conjunction=(
49 u"and" if self.context.find_all_tags else u"or")))
50 return conditions
51+
52+
53+class BugSubscriptionFilterEditView(LaunchpadEditFormView):
54+ """Edit view for `IBugSubscriptionFilter`."""
55+
56+ page_title = u"Edit filter"
57+ schema = IBugSubscriptionFilter
58+ field_names = (
59+ "description",
60+ "statuses",
61+ "importances",
62+ "tags",
63+ "find_all_tags",
64+ )
65+
66+ custom_widget("description", TextWidget, displayWidth=50)
67+ custom_widget("statuses", LabeledMultiCheckBoxWidget)
68+ custom_widget("importances", LabeledMultiCheckBoxWidget)
69+ custom_widget("tags", BugTagsFrozenSetWidget, displayWidth=35)
70+
71+ @action("Update", name="update")
72+ def update_action(self, action, data):
73+ """Update the bug filter with the form data."""
74+ self.updateContextFromData(data)
75+
76+ @property
77+ def next_url(self):
78+ """Return to the user's structural subscriptions page."""
79+ return canonical_url(
80+ self.user, view_name="+structural-subscriptions")
81+
82+ cancel_url = next_url
83
84=== modified file 'lib/lp/bugs/browser/bugtarget.py'
85--- lib/lp/bugs/browser/bugtarget.py 2010-12-02 16:14:30 +0000
86+++ lib/lp/bugs/browser/bugtarget.py 2011-01-11 21:16:20 +0000
87@@ -58,9 +58,7 @@
88 FeedsMixin,
89 )
90 from canonical.launchpad.browser.librarian import ProxiedLibraryFileAlias
91-from canonical.launchpad.interfaces.launchpad import (
92- ILaunchpadCelebrities,
93- )
94+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
95 from canonical.launchpad.searchbuilder import any
96 from canonical.launchpad.validators.name import valid_name_pattern
97 from canonical.launchpad.webapp import (
98@@ -74,10 +72,6 @@
99 from canonical.launchpad.webapp.interfaces import ILaunchBag
100 from canonical.launchpad.webapp.menu import structured
101 from canonical.launchpad.webapp.publisher import HTTP_MOVED_PERMANENTLY
102-from canonical.widgets.bug import (
103- BugTagsWidget,
104- LargeBugTagsWidget,
105- )
106 from canonical.widgets.bugtask import NewLineToSpacesWidget
107 from canonical.widgets.product import (
108 GhostCheckBoxWidget,
109@@ -103,6 +97,10 @@
110 )
111 from lp.bugs.browser.bugrole import BugRoleMixin
112 from lp.bugs.browser.bugtask import BugTaskSearchListingView
113+from lp.bugs.browser.widgets.bug import (
114+ BugTagsWidget,
115+ LargeBugTagsWidget,
116+ )
117 from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource
118 from lp.bugs.interfaces.bug import (
119 CreateBugParams,
120
121=== modified file 'lib/lp/bugs/browser/bugtask.py'
122--- lib/lp/bugs/browser/bugtask.py 2010-12-22 03:50:13 +0000
123+++ lib/lp/bugs/browser/bugtask.py 2011-01-11 21:16:20 +0000
124@@ -164,7 +164,6 @@
125 from canonical.launchpad.webapp.menu import structured
126 from canonical.lazr.interfaces import IObjectPrivacy
127 from canonical.lazr.utils import smartquote
128-from canonical.widgets.bug import BugTagsWidget
129 from canonical.widgets.bugtask import (
130 AssigneeDisplayWidget,
131 BugTaskAssigneeWidget,
132@@ -208,6 +207,7 @@
133 build_comments_from_chunks,
134 group_comments_with_activity,
135 )
136+from lp.bugs.browser.widgets.bug import BugTagsWidget
137 from lp.bugs.interfaces.bug import (
138 IBug,
139 IBugSet,
140
141=== modified file 'lib/lp/bugs/browser/configure.zcml'
142--- lib/lp/bugs/browser/configure.zcml 2011-01-11 21:16:19 +0000
143+++ lib/lp/bugs/browser/configure.zcml 2011-01-11 21:16:20 +0000
144@@ -1189,6 +1189,15 @@
145 template="../templates/bug-subscription-filter.pt"
146 permission="launchpad.View"
147 name="+definition" />
148+ <browser:page
149+ for="lp.bugs.interfaces.bugsubscriptionfilter.IBugSubscriptionFilter"
150+ class=".bugsubscriptionfilter.BugSubscriptionFilterEditView"
151+ template="../templates/bug-subscription-filter-edit.pt"
152+ permission="launchpad.Edit"
153+ name="+edit" />
154 </facet>
155
156+ <!-- Widgets -->
157+ <include package=".widgets"/>
158+
159 </configure>
160
161=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py'
162--- lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py 2011-01-11 21:16:19 +0000
163+++ lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py 2011-01-11 21:16:20 +0000
164@@ -31,10 +31,12 @@
165 from lp.testing import (
166 normalize_whitespace,
167 person_logged_in,
168+ login_person,
169 TestCaseWithFactory,
170 ws_object,
171 )
172 from lp.testing.views import create_initialized_view
173+from lp.bugs.publisher import BugsLayer
174
175
176 class TestBugSubscriptionFilterBase:
177@@ -306,7 +308,7 @@
178 # the absense of conditions.
179 self.assertRender(
180 u"This filter does not allow mail through.",
181- u"There are no filter conditions!")
182+ u"There are no filter conditions! (edit)")
183
184 def test_render_with_no_description_and_conditions(self):
185 # If conditions are set but no description, the rendered description
186@@ -325,7 +327,7 @@
187 self.subscription_filter.tags = [u"foo", u"bar"]
188 self.assertRender(
189 u"This filter allows mail through when:",
190- u" and ".join(self.view.conditions))
191+ u" and ".join(self.view.conditions) + u" (edit)")
192
193 def test_render_with_description_and_no_conditions(self):
194 # If a description is set it appears in the content of the dt tag,
195@@ -334,7 +336,7 @@
196 self.subscription_filter.description = u"The Wait"
197 self.assertRender(
198 u"\u201cThe Wait\u201d does not allow mail through.",
199- u"There are no filter conditions!")
200+ u"There are no filter conditions! (edit)")
201
202 def test_render_with_description_and_conditions(self):
203 # If a description is set it appears in the content of the dt tag,
204@@ -344,4 +346,53 @@
205 self.subscription_filter.tags = [u"foo"]
206 self.assertRender(
207 u"\u201cThe Wait\u201d allows mail through when:",
208- u" and ".join(self.view.conditions))
209+ u" and ".join(self.view.conditions) + u" (edit)")
210+
211+
212+class TestBugSubscriptionFilterEditView(
213+ TestBugSubscriptionFilterBase, TestCaseWithFactory):
214+
215+ layer = DatabaseFunctionalLayer
216+
217+ def test_view_properties(self):
218+ # The cancel url and next url will both point to the user's structural
219+ # subscription overview page.
220+ login_person(self.owner)
221+ view = create_initialized_view(
222+ self.subscription_filter, name="+edit",
223+ layer=BugsLayer, principal=self.owner)
224+ self.assertEqual([], view.errors)
225+ path = "/~%s/+structural-subscriptions" % self.owner.name
226+ self.assertEqual(path, urlparse(view.cancel_url).path)
227+ self.assertEqual(path, urlparse(view.next_url).path)
228+
229+ def test_edit(self):
230+ # The filter can be updated by using the update action.
231+ form = {
232+ "field.description": "New description",
233+ "field.statuses": ["NEW", "INCOMPLETE"],
234+ "field.importances": ["LOW", "MEDIUM"],
235+ "field.tags": u"foo bar",
236+ "field.find_all_tags": "on",
237+ "field.actions.update": "Update",
238+ }
239+ with person_logged_in(self.owner):
240+ view = create_initialized_view(
241+ self.subscription_filter, name="+edit",
242+ form=form, layer=BugsLayer, principal=self.owner)
243+ self.assertEqual([], view.errors)
244+ # The subscription filter has been updated.
245+ self.assertEqual(
246+ u"New description",
247+ self.subscription_filter.description)
248+ self.assertEqual(
249+ frozenset([BugTaskStatus.NEW, BugTaskStatus.INCOMPLETE]),
250+ self.subscription_filter.statuses)
251+ self.assertEqual(
252+ frozenset([BugTaskImportance.LOW, BugTaskImportance.MEDIUM]),
253+ self.subscription_filter.importances)
254+ self.assertEqual(
255+ frozenset([u"foo", u"bar"]),
256+ self.subscription_filter.tags)
257+ self.assertTrue(
258+ self.subscription_filter.find_all_tags)
259
260=== added directory 'lib/lp/bugs/browser/widgets'
261=== added file 'lib/lp/bugs/browser/widgets/__init__.py'
262=== renamed file 'lib/canonical/widgets/bug.py' => 'lib/lp/bugs/browser/widgets/bug.py'
263--- lib/canonical/widgets/bug.py 2010-10-03 15:30:06 +0000
264+++ lib/lp/bugs/browser/widgets/bug.py 2011-01-11 21:16:20 +0000
265@@ -2,6 +2,12 @@
266 # GNU Affero General Public License version 3 (see the file LICENSE).
267
268 __metaclass__ = type
269+__all__ = [
270+ "BugTagsFrozenSetWidget",
271+ "BugTagsWidget",
272+ "BugWidget",
273+ "LargeBugTagsWidget",
274+ ]
275
276 import re
277
278@@ -18,6 +24,7 @@
279
280 class BugWidget(IntWidget):
281 """A widget for displaying a field that is bound to an IBug."""
282+
283 def _toFormValue(self, value):
284 """See zope.app.form.widget.SimpleInputWidget."""
285 if value == self.context.missing_value:
286@@ -44,23 +51,46 @@
287 class BugTagsWidgetBase:
288 """Base class for bug tags widgets."""
289
290+ def _tagsFromFieldValue(self, tags):
291+ """Package up the tags for display.
292+
293+ Override this to provide custom ordering for example.
294+
295+ :return: `None` if there are no tags, else an iterable of tags.
296+ """
297+ if tags is None or len(tags) == 0:
298+ return None
299+ else:
300+ return tags
301+
302+ def _tagsToFieldValue(self, tags):
303+ """Package up the tags for the field.
304+
305+ :param tags: `None` if the submitted data was missing, otherwise an
306+ iterable of tags.
307+ """
308+ if tags is None:
309+ return []
310+ else:
311+ return sorted(set(tags))
312+
313 def _toFormValue(self, value):
314 """Convert a list of strings to a single, space separated, string."""
315- if value:
316- return u' '.join(value)
317+ tags = self._tagsFromFieldValue(value)
318+ if tags is None:
319+ return self._missing
320 else:
321- return self._missing
322+ return u" ".join(tags)
323
324 def _toFieldValue(self, input):
325 """Convert a space separated string to a list of strings."""
326 input = input.strip()
327 if input == self._missing:
328- return []
329+ return self._tagsToFieldValue(None)
330 else:
331- tags = set(tag.lower()
332- for tag in re.split(r'[,\s]+', input)
333- if len(tag) != 0)
334- return sorted(tags)
335+ return self._tagsToFieldValue(
336+ tag.lower() for tag in re.split(r'[,\s]+', input)
337+ if len(tag) != 0)
338
339 def getInputValue(self):
340 try:
341@@ -88,8 +118,9 @@
342 def _getInputValue(self):
343 raise NotImplementedError('_getInputValue must be overloaded')
344
345+
346 class BugTagsWidget(BugTagsWidgetBase, TextWidget):
347- """A widget for editing bug tags."""
348+ """A widget for editing bug tags in a `List` field."""
349
350 def __init__(self, field, value_type, request):
351 # We don't use value_type.
352@@ -99,8 +130,29 @@
353 return TextWidget.getInputValue(self)
354
355
356+class BugTagsFrozenSetWidget(BugTagsWidget):
357+ """A widget for editing bug tags in a `FrozenSet` field."""
358+
359+ def _tagsFromFieldValue(self, tags):
360+ """Order the tags for display.
361+
362+ The field value is assumed to be unordered.
363+ """
364+ if tags is None or len(tags) == 0:
365+ return None
366+ else:
367+ return sorted(tags)
368+
369+ def _tagsToFieldValue(self, tags):
370+ """Return a `frozenset` of tags."""
371+ if tags is None:
372+ return frozenset()
373+ else:
374+ return frozenset(tags)
375+
376+
377 class LargeBugTagsWidget(BugTagsWidgetBase, TextAreaWidget):
378- """A large widget for editing bug tags."""
379+ """A large widget for editing bug tags in a `List` field."""
380
381 def __init__(self, field, value_type, request):
382 # We don't use value_type.
383
384=== added file 'lib/lp/bugs/browser/widgets/configure.zcml'
385--- lib/lp/bugs/browser/widgets/configure.zcml 1970-01-01 00:00:00 +0000
386+++ lib/lp/bugs/browser/widgets/configure.zcml 2011-01-11 21:16:20 +0000
387@@ -0,0 +1,19 @@
388+<!-- Copyright 2011 Canonical Ltd. This software is licensed under the
389+ GNU Affero General Public License version 3 (see the file LICENSE).
390+-->
391+
392+<configure
393+ xmlns="http://namespaces.zope.org/zope"
394+ xmlns:browser="http://namespaces.zope.org/browser"
395+ xmlns:i18n="http://namespaces.zope.org/i18n"
396+ i18n_domain="launchpad">
397+
398+ <view
399+ type="zope.publisher.interfaces.browser.IBrowserRequest"
400+ for="lp.services.fields.IBugField"
401+ provides="zope.app.form.interfaces.IInputWidget"
402+ factory="lp.bugs.browser.widgets.bug.BugWidget"
403+ permission="zope.Public"
404+ />
405+
406+</configure>
407
408=== modified file 'lib/lp/bugs/doc/bug-tags.txt'
409--- lib/lp/bugs/doc/bug-tags.txt 2010-10-18 22:24:59 +0000
410+++ lib/lp/bugs/doc/bug-tags.txt 2011-01-11 21:16:20 +0000
411@@ -68,7 +68,7 @@
412 use BugTagsWidget.
413
414 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
415- >>> from canonical.widgets.bug import BugTagsWidget
416+ >>> from lp.bugs.browser.widgets.bug import BugTagsWidget
417 >>> from lp.bugs.interfaces.bug import IBug
418 >>> bug_tags_field = IBug['tags'].bind(bug_one)
419 >>> tag_field = bug_tags_field.value_type
420@@ -158,6 +158,49 @@
421 [u'bar', u'foo']
422
423
424+=== Bug Tags Widget for Frozen Sets ===
425+
426+A variant of `BugTagsWidget` exists for when tags are stored in a
427+`FrozenSet` field.
428+
429+ >>> from lp.bugs.browser.widgets.bug import BugTagsFrozenSetWidget
430+
431+Field-manipulation is not going to be examined here, and the widget
432+does not care what type the field is otherwise, so the field from
433+earlier can be used again.
434+
435+ >>> tags_frozen_set_widget = BugTagsFrozenSetWidget(
436+ ... bug_tags_field, tag_field, request)
437+
438+_tagsFromFieldValue() converts tags from the field value to tags for
439+display. The absense of tags causes it to return None:
440+
441+ >>> print tags_frozen_set_widget._tagsFromFieldValue(None)
442+ None
443+ >>> print tags_frozen_set_widget._tagsFromFieldValue(frozenset())
444+ None
445+
446+Tags are ordered before returning:
447+
448+ >>> tags_frozen_set_widget._tagsFromFieldValue(
449+ ... frozenset([5, 4, 1, 12]))
450+ [1, 4, 5, 12]
451+
452+_tagsToFieldValue() converts the tags entered in the form into a value
453+suitable for the field. In the absense of tags it returns an empty
454+frozenset():
455+
456+ >>> tags_frozen_set_widget._tagsToFieldValue(None)
457+ frozenset([])
458+ >>> tags_frozen_set_widget._tagsToFieldValue([])
459+ frozenset([])
460+
461+Otherwise it returns a `frozenset` of the tags given:
462+
463+ >>> tags_frozen_set_widget._tagsToFieldValue([u"foo", u"bar"])
464+ frozenset([u'foo', u'bar'])
465+
466+
467 === Large and Small Bug Tags Widget ===
468
469 A regular BugTagsWidget is rendered as an <input> tag,
470@@ -167,7 +210,7 @@
471
472 A LargeBugTagsWidget is rendered as a <textarea>,
473
474- >>> from canonical.widgets.bug import LargeBugTagsWidget
475+ >>> from lp.bugs.browser.widgets.bug import LargeBugTagsWidget
476 >>> large_text_widget = LargeBugTagsWidget(
477 ... bug_tags_field, tag_field, request)
478 >>> print large_text_widget()
479
480=== modified file 'lib/lp/bugs/doc/bugwidget.txt'
481--- lib/lp/bugs/doc/bugwidget.txt 2010-10-18 22:24:59 +0000
482+++ lib/lp/bugs/doc/bugwidget.txt 2011-01-11 21:16:20 +0000
483@@ -3,7 +3,7 @@
484 The BugWidget converts string bug ids to the corresponding bug object.
485
486 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
487- >>> from canonical.widgets.bug import BugWidget
488+ >>> from lp.bugs.browser.widgets.bug import BugWidget
489 >>> from lp.services.fields import BugField
490 >>> bug_field = BugField(__name__='bug', title=u'Bug')
491 >>> request = LaunchpadTestRequest(form={'field.bug': '1'})
492
493=== modified file 'lib/lp/bugs/interfaces/bugsubscriptionfilter.py'
494--- lib/lp/bugs/interfaces/bugsubscriptionfilter.py 2010-12-21 09:34:17 +0000
495+++ lib/lp/bugs/interfaces/bugsubscriptionfilter.py 2011-01-11 21:16:20 +0000
496@@ -48,37 +48,40 @@
497
498 find_all_tags = exported(
499 Bool(
500- title=_("All given tags must be found, or any."),
501+ title=_("Find all tags"),
502+ description=_(
503+ "If enabled, all tags must match, "
504+ "else at least one tag must match."),
505 required=True, default=False))
506 include_any_tags = Bool(
507- title=_("Include any tags."),
508+ title=_("Include any tags"),
509 required=True, default=False)
510 exclude_any_tags = Bool(
511- title=_("Exclude all tags."),
512+ title=_("Exclude all tags"),
513 required=True, default=False)
514
515 description = exported(
516 Text(
517- title=_("Description of this filter."),
518+ title=_("A short description of this filter"),
519 required=False))
520
521 statuses = exported(
522 FrozenSet(
523- title=_("The statuses to filter on."),
524+ title=_("The statuses interested in (empty for all)"),
525 required=True, default=frozenset(),
526 value_type=Choice(
527 title=_('Status'), vocabulary=BugTaskStatus)))
528
529 importances = exported(
530 FrozenSet(
531- title=_("The importances to filter on."),
532+ title=_("The importances interested in (empty for all)"),
533 required=True, default=frozenset(),
534 value_type=Choice(
535 title=_('Importance'), vocabulary=BugTaskImportance)))
536
537 tags = exported(
538 FrozenSet(
539- title=_("The tags to filter on."),
540+ title=_("The tags interested in"),
541 required=True, default=frozenset(),
542 value_type=SearchTag()))
543
544
545=== added file 'lib/lp/bugs/templates/bug-subscription-filter-edit.pt'
546--- lib/lp/bugs/templates/bug-subscription-filter-edit.pt 1970-01-01 00:00:00 +0000
547+++ lib/lp/bugs/templates/bug-subscription-filter-edit.pt 2011-01-11 21:16:20 +0000
548@@ -0,0 +1,14 @@
549+<html
550+ xmlns="http://www.w3.org/1999/xhtml"
551+ xmlns:tal="http://xml.zope.org/namespaces/tal"
552+ xmlns:metal="http://xml.zope.org/namespaces/metal"
553+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
554+ xml:lang="en" lang="en" dir="ltr"
555+ metal:use-macro="view/macro:page/main_only"
556+ i18n:domain="malone">
557+ <body>
558+ <div metal:fill-slot="main">
559+ <div metal:use-macro="context/@@launchpad_form/form" />
560+ </div>
561+ </body>
562+</html>
563
564=== modified file 'lib/lp/bugs/templates/bug-subscription-filter.pt'
565--- lib/lp/bugs/templates/bug-subscription-filter.pt 2011-01-11 21:16:19 +0000
566+++ lib/lp/bugs/templates/bug-subscription-filter.pt 2011-01-11 21:16:20 +0000
567@@ -27,8 +27,10 @@
568 <b>and</b> <br />
569 </tal:conjunction>
570 </tal:conditions>
571+ <br /><a tal:attributes="href context/fmt:url/+edit">(edit)</a>
572 </dd>
573 <dd tal:condition="not:conditions">
574 There are no filter conditions!
575+ <br /><a tal:attributes="href context/fmt:url/+edit">(edit)</a>
576 </dd>
577 </dl>
578
579=== modified file 'lib/lp/registry/stories/person/xx-person-subscriptions.txt'
580--- lib/lp/registry/stories/person/xx-person-subscriptions.txt 2011-01-11 21:16:19 +0000
581+++ lib/lp/registry/stories/person/xx-person-subscriptions.txt 2011-01-11 21:16:20 +0000
582@@ -253,6 +253,7 @@
583 only if it matches the following filter:
584 &#8220;First&#8221; allows mail through when:
585 the bug is tagged with foo
586+ (edit)
587
588 Multiple filters will be shown if they exist, with a slightly modified
589 message.
590@@ -267,5 +268,7 @@
591 only if it matches one or more of the following filters:
592 &#8220;First&#8221; allows mail through when:
593 the bug is tagged with foo
594+ (edit)
595 &#8220;Second&#8221; allows mail through when:
596 the bug is tagged with bar
597+ (edit)
598
599=== modified file 'lib/lp/services/fields/configure.zcml'
600--- lib/lp/services/fields/configure.zcml 2010-08-12 18:11:47 +0000
601+++ lib/lp/services/fields/configure.zcml 2011-01-11 21:16:20 +0000
602@@ -52,14 +52,6 @@
603
604 <view
605 type="zope.publisher.interfaces.browser.IBrowserRequest"
606- for="lp.services.fields.IBugField"
607- provides="zope.app.form.interfaces.IInputWidget"
608- factory="canonical.widgets.bug.BugWidget"
609- permission="zope.Public"
610- />
611-
612- <view
613- type="zope.publisher.interfaces.browser.IBrowserRequest"
614 for="lp.services.fields.IStrippedTextLine"
615 provides="zope.app.form.interfaces.IInputWidget"
616 factory="canonical.widgets.textwidgets.StrippedTextWidget"