Merge lp:~wgrant/launchpad/rework-bug-default-type-1 into lp:launchpad

Proposed by William Grant
Status: Merged
Merged at revision: 15725
Proposed branch: lp:~wgrant/launchpad/rework-bug-default-type-1
Merge into: lp:launchpad
Prerequisite: lp:~wgrant/launchpad/rework-bug-default-type-0
Diff against target: 544 lines (+166/-254)
5 files modified
lib/lp/bugs/browser/bugtarget.py (+117/-140)
lib/lp/bugs/browser/configure.zcml (+0/-6)
lib/lp/bugs/browser/tests/test_bugtarget_filebug.py (+5/-55)
lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt (+0/-51)
lib/lp/bugs/templates/bugtarget-macros-filebug.pt (+44/-2)
To merge this branch: bzr merge lp:~wgrant/launchpad/rework-bug-default-type-1
Reviewer Review Type Date Requested Status
Richard Harding (community) Approve
Review via email: mp+117380@code.launchpad.net

Commit message

Merge FileBugReportingGuidelines into FileBugViewBase, as ProjectGroup:+filebug doesn't need it rendered separately any more.

Description of the change

This branch is the second in a series of maybe four to rework bug information type defaults, which will eventually allow us to default to Proprietary when a project is so configured.

Some awkwardness arose during UI refactoring due to the split between FileBugViewBase and FileBugReportingGuidelines (which -- contrary to its name -- includes not just the bug reporting guidelines, but the privacy widgets too). I eventually realised that the split was for some code in ProjectGroup:+filebug that has since been removed, so I opted to merge the views, templates and tests.

The only minor awkwardness was that the form relies on the JSON request cache being fully populated, but form action templates are rendered in LaunchpadFormView.initialize(). So we can't do the super() call first as is usual. This problem was accidentally worked around with the old structure, since the JSON cache ended up populated by the separately initialised subview.

To post a comment you must log in.
Revision history for this message
Richard Harding (rharding) wrote :

Think I follow. Thanks for the cleanup.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugtarget.py'
2--- lib/lp/bugs/browser/bugtarget.py 2012-07-31 05:46:45 +0000
3+++ lib/lp/bugs/browser/bugtarget.py 2012-08-01 01:08:21 +0000
4@@ -243,30 +243,40 @@
5 self.updateContextFromData(data)
6
7
8-class FileBugReportingGuidelines(LaunchpadFormView):
9- """Provides access to common bug reporting attributes.
10-
11- Attributes provided are: information_type and bug_reporting_guidelines.
12-
13- This view is a superclass of `FileBugViewBase` so that non-ajax browsers
14- can load the file bug form, and it is also invoked directly via an XHR
15- request to provide an HTML snippet for Javascript enabled browsers.
16- """
17+class FileBugViewBase(LaunchpadFormView):
18+ """Base class for views related to filing a bug."""
19+
20+ implements(IBrowserPublisher)
21
22 schema = IBug
23
24- @property
25- def field_names(self):
26- """Return the list of field names to display."""
27- if self.is_bug_supervisor:
28- return ['information_type']
29- else:
30- return ['security_related']
31-
32 custom_widget('information_type', LaunchpadRadioWidgetWithDescription)
33
34+ extra_data_token = None
35+ advanced_form = False
36+ frontpage_form = False
37+ data_parser = None
38+
39+ def __init__(self, context, request):
40+ LaunchpadFormView.__init__(self, context, request)
41+ self.extra_data = FileBugData()
42+
43 def initialize(self):
44- super(FileBugReportingGuidelines, self).initialize()
45+ # redirect_ubuntu_filebug is a cached_property.
46+ # Access it first just to compute its value. Because it
47+ # makes a DB access to get the bug supervisor, it causes
48+ # trouble in tests when form validation errors occur. Because the
49+ # transaction is doomed, the storm cache is invalidated and accessing
50+ # the property will result in a a LostObjectError, because
51+ # the created objects disappeared. Not likely a problem in production
52+ # since the objects will still be in the DB, but doesn't hurt there
53+ # either. It makes for better diagnosis of failing tests.
54+ if self.redirect_ubuntu_filebug:
55+ pass
56+
57+ # The JSON cache must be populated before the super call, since
58+ # the form is rendered during LaunchpadFormView's initialize()
59+ # when an action is invokved.
60 cache = IJSONRequestCache(self.request)
61 cache.objects['private_types'] = [
62 type.name for type in PRIVATE_INFORMATION_TYPES]
63@@ -296,125 +306,12 @@
64 css_class_prefix='importance',
65 excluded_items=[BugTaskImportance.UNKNOWN])
66 cache.objects['bugtask_importance_data'] = bugtask_importance_data
67-
68- def setUpFields(self):
69- """Set up the form fields. See `LaunchpadFormView`."""
70- super(FileBugReportingGuidelines, self).setUpFields()
71-
72- # Project groups are special. The Next button sends you to
73- # Product:+filebug, so we need none of the usual stuff.
74- if IProjectGroup.providedBy(self.context):
75- return
76-
77- if self.is_bug_supervisor:
78- info_type_vocab = InformationTypeVocabulary(
79- types=self.context.pillar.getAllowedBugInformationTypes())
80- information_type_field = copy_field(
81- IBug['information_type'], readonly=False,
82- vocabulary=info_type_vocab)
83- self.form_fields = self.form_fields.omit('information_type')
84- self.form_fields += Fields(information_type_field)
85- else:
86- security_related_field = copy_field(
87- IBug['security_related'], readonly=False)
88- self.form_fields = self.form_fields.omit('security_related')
89- self.form_fields += Fields(security_related_field)
90-
91- @property
92- def initial_values(self):
93- """See `LaunchpadFormView`."""
94- value = InformationType.PUBLIC
95- if (self.context and IProduct.providedBy(self.context) and
96- self.context.private_bugs):
97- value = InformationType.USERDATA
98- return {'information_type': value}
99-
100- @property
101- def bug_reporting_guidelines(self):
102- """Guidelines for filing bugs in the current context.
103-
104- Returns a list of dicts, with each dict containing values for
105- "preamble" and "content".
106- """
107-
108- def target_name(target):
109- # IProjectGroup can be considered the target of a bug during
110- # the bug filing process, but does not extend IBugTarget
111- # and ultimately cannot actually be the target of a
112- # bug. Hence this function to determine a suitable
113- # name/title to display. Hurrumph.
114- if IBugTarget.providedBy(target):
115- return target.bugtargetdisplayname
116- else:
117- return target.displayname
118-
119- guidelines = []
120- bugtarget = self.context
121- if bugtarget is not None:
122- content = bugtarget.bug_reporting_guidelines
123- if content is not None and len(content) > 0:
124- guidelines.append({
125- "source": target_name(bugtarget),
126- "content": content,
127- })
128- # Distribution source packages are shown with both their
129- # own reporting guidelines and those of their
130- # distribution.
131- if IDistributionSourcePackage.providedBy(bugtarget):
132- distribution = bugtarget.distribution
133- content = distribution.bug_reporting_guidelines
134- if content is not None and len(content) > 0:
135- guidelines.append({
136- "source": target_name(distribution),
137- "content": content,
138- })
139- return guidelines
140-
141- def getMainContext(self):
142- if IDistributionSourcePackage.providedBy(self.context):
143- return self.context.distribution
144- else:
145- return self.context
146-
147- @cachedproperty
148- def is_bug_supervisor(self):
149- """ Return True if the logged in user is a bug supervisor."""
150- context = self.getMainContext()
151- return BugTask.userHasBugSupervisorPrivilegesContext(
152- context, self.user)
153-
154-
155-class FileBugViewBase(FileBugReportingGuidelines, LaunchpadFormView):
156- """Base class for views related to filing a bug."""
157-
158- implements(IBrowserPublisher)
159-
160- extra_data_token = None
161- advanced_form = False
162- frontpage_form = False
163- data_parser = None
164-
165- def __init__(self, context, request):
166- LaunchpadFormView.__init__(self, context, request)
167- self.extra_data = FileBugData()
168-
169- def initialize(self):
170- # redirect_ubuntu_filebug is a cached_property.
171- # Access it first just to compute its value. Because it
172- # makes a DB access to get the bug supervisor, it causes
173- # trouble in tests when form validation errors occur. Because the
174- # transaction is doomed, the storm cache is invalidated and accessing
175- # the property will result in a a LostObjectError, because
176- # the created objects disappeared. Not likely a problem in production
177- # since the objects will still be in the DB, but doesn't hurt there
178- # either. It makes for better diagnosis of failing tests.
179- if self.redirect_ubuntu_filebug:
180- pass
181- super(FileBugViewBase, self).initialize()
182- cache = IJSONRequestCache(self.request)
183 cache.objects['enable_bugfiling_duplicate_search'] = (
184 IProjectGroup.providedBy(self.context)
185 or self.context.enable_bugfiling_duplicate_search)
186+
187+ super(FileBugViewBase, self).initialize()
188+
189 if (self.extra_data_token is not None and
190 not self.extra_data_to_process):
191 # self.extra_data has been initialized in publishTraverse().
192@@ -492,10 +389,17 @@
193 @property
194 def initial_values(self):
195 """Give packagename a default value, if applicable."""
196- if not IDistributionSourcePackage.providedBy(self.context):
197- return {}
198-
199- return {'packagename': self.context.name}
200+ if (self.context and IProduct.providedBy(self.context)
201+ and self.context.private_bugs):
202+ type = InformationType.USERDATA
203+ else:
204+ type = InformationType.PUBLIC
205+ values = {'information_type': type}
206+
207+ if IDistributionSourcePackage.providedBy(self.context):
208+ values['packagename'] = self.context.name
209+
210+ return values
211
212 def contextIsProduct(self):
213 return IProduct.providedBy(self.context)
214@@ -588,7 +492,7 @@
215
216 def setUpWidgets(self):
217 """Customize the onKeyPress event of the package name chooser."""
218- LaunchpadFormView.setUpWidgets(self)
219+ super(FileBugViewBase, self).setUpWidgets()
220
221 if "packagename" in self.field_names:
222 self.widgets["packagename"].onKeyPress = (
223@@ -598,6 +502,25 @@
224 """Set up the form fields. See `LaunchpadFormView`."""
225 super(FileBugViewBase, self).setUpFields()
226
227+ # Project groups are special. The Next button sends you to
228+ # Product:+filebug, so we need none of the usual stuff.
229+ if IProjectGroup.providedBy(self.context):
230+ return
231+
232+ if self.is_bug_supervisor:
233+ info_type_vocab = InformationTypeVocabulary(
234+ types=self.context.pillar.getAllowedBugInformationTypes())
235+ information_type_field = copy_field(
236+ IBug['information_type'], readonly=False,
237+ vocabulary=info_type_vocab)
238+ self.form_fields = self.form_fields.omit('information_type')
239+ self.form_fields += Fields(information_type_field)
240+ else:
241+ security_related_field = copy_field(
242+ IBug['security_related'], readonly=False)
243+ self.form_fields = self.form_fields.omit('security_related')
244+ self.form_fields += Fields(security_related_field)
245+
246 # Override the vocabulary for the subscribe_to_existing_bug
247 # field.
248 subscribe_field = Choice(
249@@ -1017,6 +940,60 @@
250 else:
251 return True
252
253+ @property
254+ def bug_reporting_guidelines(self):
255+ """Guidelines for filing bugs in the current context.
256+
257+ Returns a list of dicts, with each dict containing values for
258+ "preamble" and "content".
259+ """
260+
261+ def target_name(target):
262+ # IProjectGroup can be considered the target of a bug during
263+ # the bug filing process, but does not extend IBugTarget
264+ # and ultimately cannot actually be the target of a
265+ # bug. Hence this function to determine a suitable
266+ # name/title to display. Hurrumph.
267+ if IBugTarget.providedBy(target):
268+ return target.bugtargetdisplayname
269+ else:
270+ return target.displayname
271+
272+ guidelines = []
273+ bugtarget = self.context
274+ if bugtarget is not None:
275+ content = bugtarget.bug_reporting_guidelines
276+ if content is not None and len(content) > 0:
277+ guidelines.append({
278+ "source": target_name(bugtarget),
279+ "content": content,
280+ })
281+ # Distribution source packages are shown with both their
282+ # own reporting guidelines and those of their
283+ # distribution.
284+ if IDistributionSourcePackage.providedBy(bugtarget):
285+ distribution = bugtarget.distribution
286+ content = distribution.bug_reporting_guidelines
287+ if content is not None and len(content) > 0:
288+ guidelines.append({
289+ "source": target_name(distribution),
290+ "content": content,
291+ })
292+ return guidelines
293+
294+ def getMainContext(self):
295+ if IDistributionSourcePackage.providedBy(self.context):
296+ return self.context.distribution
297+ else:
298+ return self.context
299+
300+ @cachedproperty
301+ def is_bug_supervisor(self):
302+ """ Return True if the logged in user is a bug supervisor."""
303+ context = self.getMainContext()
304+ return BugTask.userHasBugSupervisorPrivilegesContext(
305+ context, self.user)
306+
307
308 class FileBugAdvancedView(FileBugViewBase):
309 """Browser view for filing a bug.
310@@ -1131,7 +1108,7 @@
311 show_summary_in_results = True
312
313 def initialize(self):
314- FilebugShowSimilarBugsView.initialize(self)
315+ super(FileBugGuidedView, self).initialize()
316 if self.redirect_ubuntu_filebug:
317 # The user is trying to file a new Ubuntu bug via the web
318 # interface and without using apport. Redirect to a page
319
320=== modified file 'lib/lp/bugs/browser/configure.zcml'
321--- lib/lp/bugs/browser/configure.zcml 2012-07-29 23:38:26 +0000
322+++ lib/lp/bugs/browser/configure.zcml 2012-08-01 01:08:21 +0000
323@@ -103,12 +103,6 @@
324 template="../templates/bugtarget-filebug-inline-form.pt"
325 permission="launchpad.AnyPerson"/>
326 <browser:page
327- for="lp.bugs.interfaces.bugtarget.IHasBugs"
328- class="lp.bugs.browser.bugtarget.FileBugReportingGuidelines"
329- template="../templates/bugtarget-filebug-guidelines.pt"
330- permission="launchpad.AnyPerson"
331- name="+filebug-reporting-guidelines"/>
332- <browser:page
333 name="+manage-official-tags"
334 for="lp.bugs.interfaces.bugtarget.IOfficialBugTagTargetRestricted"
335 class="lp.bugs.browser.bugtarget.OfficialBugTagsManageView"
336
337=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_filebug.py'
338--- lib/lp/bugs/browser/tests/test_bugtarget_filebug.py 2012-07-26 07:54:40 +0000
339+++ lib/lp/bugs/browser/tests/test_bugtarget_filebug.py 2012-08-01 01:08:21 +0000
340@@ -340,17 +340,11 @@
341 self.assertEqual(0, len(view.errors))
342 self.assertTrue(view.added_bug is not None)
343
344-
345-class TestFileBugReportingGuidelines(TestCaseWithFactory):
346-
347- layer = DatabaseFunctionalLayer
348-
349 def test_filebug_reporting_details(self):
350 product = self.factory.makeProduct()
351 login_person(product.owner)
352 product.bug_reporting_guidelines = "Include bug details"
353- view = create_initialized_view(
354- product, '+filebug-reporting-guidelines')
355+ view = create_initialized_view(product, '+filebug')
356 expected_guidelines = [{
357 "source": product.displayname, "content": u"Include bug details",
358 }]
359@@ -522,53 +516,6 @@
360 self.assertIn("Thank you for your bug report.", msg)
361
362
363-class TestFileBugGuidelinesRequestCache(TestCaseWithFactory):
364- # Tests to ensure the request cache contains the expected values for
365- # file bug guidelines views.
366-
367- layer = DatabaseFunctionalLayer
368-
369- def _assert_cache_values(self, view, private_bugs, duplicate_search):
370- cache = IJSONRequestCache(view.request).objects
371- self.assertContentEqual(cache['private_types'], [
372- type.name for type in PRIVATE_INFORMATION_TYPES])
373- self.assertEqual(cache['bug_private_by_default'], private_bugs)
374-
375- def test_product(self):
376- project = self.factory.makeProduct(official_malone=True)
377- user = self.factory.makePerson()
378- login_person(user)
379- view = create_initialized_view(project,
380- '+filebug-reporting-guidelines', principal=user)
381- self._assert_cache_values(view, False, True)
382-
383- def test_product_default_private(self):
384- product = self.factory.makeProduct(official_malone=True)
385- removeSecurityProxy(product).private_bugs = True
386- user = self.factory.makePerson()
387- login_person(user)
388- view = create_initialized_view(product,
389- '+filebug-reporting-guidelines', principal=user)
390- self._assert_cache_values(view, True, True)
391-
392- def test_product_no_duplicate_search(self):
393- product = self.factory.makeProduct(official_malone=True)
394- removeSecurityProxy(product).enable_bugfiling_duplicate_search = False
395- user = self.factory.makePerson()
396- login_person(user)
397- view = create_initialized_view(product,
398- '+filebug-reporting-guidelines', principal=user)
399- self._assert_cache_values(view, False, False)
400-
401- def test_project_group(self):
402- project = self.factory.makeProject()
403- user = self.factory.makePerson()
404- login_person(user)
405- view = create_initialized_view(project,
406- '+filebug-reporting-guidelines', principal=user)
407- self._assert_cache_values(view, False, True)
408-
409-
410 class TestFileBugRequestCache(TestCaseWithFactory):
411 # Tests to ensure the request cache contains the expected values for
412 # file bug views.
413@@ -581,7 +528,7 @@
414 'disclosure.enhanced_choice_popup.enabled': 'true'
415 }))
416
417- def _assert_cache_values(self, view, duplicate_search, private_only=False):
418+ def _assert_cache_values(self, view, duplicate_search, private_bugs=False):
419 cache = IJSONRequestCache(view.request).objects
420 self.assertEqual(
421 duplicate_search, cache['enable_bugfiling_duplicate_search'])
422@@ -628,6 +575,9 @@
423 bugtask_importance_data.append(new_item)
424 self.assertEqual(
425 bugtask_importance_data, cache['bugtask_importance_data'])
426+ self.assertContentEqual(cache['private_types'], [
427+ type.name for type in PRIVATE_INFORMATION_TYPES])
428+ self.assertEqual(cache['bug_private_by_default'], private_bugs)
429 bugtask_info_type_data = []
430 if not IProjectGroup.providedBy(view.context):
431 for item in view.context.getAllowedBugInformationTypes():
432
433=== removed file 'lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt'
434--- lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt 2012-07-05 00:49:00 +0000
435+++ lib/lp/bugs/templates/bugtarget-filebug-guidelines.pt 1970-01-01 00:00:00 +0000
436@@ -1,51 +0,0 @@
437-<tal:root
438- xmlns:tal="http://xml.zope.org/namespaces/tal"
439- xmlns:metal="http://xml.zope.org/namespaces/metal"
440- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
441- >
442-
443- <tr id="extra-filebug-details"><td colspan="2" width="100%"><table><tbody>
444- <tr>
445- <metal:bug_reporting_guidelines
446- use-macro="context/@@+filebug-macros/bug_reporting_guidelines" />
447- </tr>
448-
449- <tr tal:define="security_context view/getMainContext">
450- <tal:information_type tal:condition="view/is_bug_supervisor">
451- <td colspan="2" width="100%"
452- tal:define="widget nocall: view/widgets/information_type|nothing"
453- tal:condition="widget">
454- <label tal:attributes="for widget/name">
455- This bug contains information that is:
456- </label>
457- <input tal:replace="structure widget" />
458- </td>
459- </tal:information_type>
460- <tal:security_related tal:condition="not: view/is_bug_supervisor">
461- <td colspan="2" width="100%"
462- tal:define="widget nocall: view/widgets/security_related|nothing"
463- tal:condition="widget">
464- <table>
465- <tbody>
466- <tr>
467- <td>
468- <input type="checkbox" tal:replace="structure widget" />
469- </td>
470- <td>
471- <label tal:attributes="for widget/name">
472- This bug is a security vulnerability
473- </label>
474- <div>
475- The security group for
476- <tal:security-context content="security_context/displayname" />
477- will be notified.
478- </div>
479- </td>
480- </tr>
481- </tbody>
482- </table>
483- </td>
484- </tal:security_related>
485- </tr>
486- </tbody></table></td></tr>
487-</tal:root>
488
489=== modified file 'lib/lp/bugs/templates/bugtarget-macros-filebug.pt'
490--- lib/lp/bugs/templates/bugtarget-macros-filebug.pt 2012-06-15 16:23:50 +0000
491+++ lib/lp/bugs/templates/bugtarget-macros-filebug.pt 2012-08-01 01:08:21 +0000
492@@ -57,8 +57,50 @@
493 <metal:row use-macro="context/@@launchpad_form/widget_row" />
494 </metal:description>
495
496- <tr id="extra-filebug-details"
497- tal:replace="structure context/@@+filebug-reporting-guidelines" />
498+ <tr id="extra-filebug-details"><td colspan="2" width="100%"><table><tbody>
499+ <tr>
500+ <metal:bug_reporting_guidelines
501+ use-macro="context/@@+filebug-macros/bug_reporting_guidelines" />
502+ </tr>
503+
504+ <tr tal:define="security_context view/getMainContext">
505+ <tal:information_type tal:condition="view/is_bug_supervisor">
506+ <td colspan="2" width="100%"
507+ tal:define="widget nocall: view/widgets/information_type|nothing"
508+ tal:condition="widget">
509+ <label tal:attributes="for widget/name">
510+ This bug contains information that is:
511+ </label>
512+ <input tal:replace="structure widget" />
513+ </td>
514+ </tal:information_type>
515+ <tal:security_related tal:condition="not: view/is_bug_supervisor">
516+ <td colspan="2" width="100%"
517+ tal:define="widget nocall: view/widgets/security_related|nothing"
518+ tal:condition="widget">
519+ <table>
520+ <tbody>
521+ <tr>
522+ <td>
523+ <input type="checkbox" tal:replace="structure widget" />
524+ </td>
525+ <td>
526+ <label tal:attributes="for widget/name">
527+ This bug is a security vulnerability
528+ </label>
529+ <div>
530+ The security group for
531+ <tal:security-context content="security_context/displayname" />
532+ will be notified.
533+ </div>
534+ </td>
535+ </tr>
536+ </tbody>
537+ </table>
538+ </td>
539+ </tal:security_related>
540+ </tr>
541+ </tbody></table></td></tr>
542
543 <tr>
544 <td colspan="2">