Merge lp:~stevenk/launchpad/filebug-description-widget into lp:launchpad

Proposed by Steve Kowalik on 2012-11-06
Status: Merged
Approved by: Steve Kowalik on 2012-11-06
Approved revision: no longer in the source branch.
Merged at revision: 16238
Proposed branch: lp:~stevenk/launchpad/filebug-description-widget
Merge into: lp:launchpad
Diff against target: 352 lines (+75/-75)
3 files modified
lib/lp/bugs/browser/bugtarget.py (+10/-3)
lib/lp/bugs/browser/tests/test_bugtarget_filebug.py (+37/-10)
lib/lp/bugs/model/bug.py (+28/-62)
To merge this branch: bzr merge lp:~stevenk/launchpad/filebug-description-widget
Reviewer Review Type Date Requested Status
Ian Booth (community) 2012-11-06 Approve on 2012-11-06
Review via email: mp+133009@code.launchpad.net

Commit Message

Also check the extra_description + comment for being > 50000 in the validation method for filing a bug.

Description of the Change

As it stands, Apport uploads a blob using RootObject:+storeblob, and then passes the token from that blob in the +filebug URL. This blob may contain an extra_description, which is nailed onto the end of the comment. As it stood, the validation method in FileBugViewBase would check the length of the comment, and if it was over 50001, it would error. However, it didn't check anything about extra_description, so that the validation would pass, the extra_description would be added on, and then Storm would de-pram its toys when we try and create a Bug row with a 50001 character description.

Claw this branch back to net-neutral by doing a fair whack of whitespace cleanup.

To post a comment you must log in.
Ian Booth (wallyworld) wrote :

Change line 27 to > 50000 and it's goo to go

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-10-11 12:41:43 +0000
3+++ lib/lp/bugs/browser/bugtarget.py 2012-11-06 05:59:19 +0000
4@@ -255,8 +255,7 @@
5 self.context.pillar.getDefaultBugInformationType() in
6 PRIVATE_INFORMATION_TYPES)
7 json_dump_information_types(
8- cache,
9- self.context.pillar.getAllowedBugInformationTypes())
10+ cache, self.context.pillar.getAllowedBugInformationTypes())
11
12 bugtask_status_data = vocabulary_to_choice_edit_items(
13 BugTaskStatus, include_description=True, css_class_prefix='status',
14@@ -385,11 +384,19 @@
15 widget_error = self.widgets.get('comment')._error
16 if widget_error and isinstance(widget_error.errors, TooLong):
17 self.setFieldError('comment',
18- 'The description is too long. If you have lots '
19+ 'The description is too long. If you have lots of '
20 'text to add, attach a file to the bug instead.')
21 elif not comment or widget_error is not None:
22 self.setFieldError(
23 'comment', "Provide details about the issue.")
24+ # Check if there is an extra description, and if it is too long.
25+ if self.extra_data.extra_description:
26+ if len("%s\n\n%s" % (
27+ comment, self.extra_data.extra_description)) > 50000:
28+ self.setFieldError(
29+ 'comment', 'The description and the additional '
30+ 'information is too long. If you have lots of text '
31+ 'to add, attach a file to the bug instead.')
32 # Check a bug has been selected when the user wants to
33 # subscribe to an existing bug.
34 elif self.this_is_my_bug_action.submitted():
35
36=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_filebug.py'
37--- lib/lp/bugs/browser/tests/test_bugtarget_filebug.py 2012-10-08 22:59:03 +0000
38+++ lib/lp/bugs/browser/tests/test_bugtarget_filebug.py 2012-11-06 05:59:19 +0000
39@@ -3,7 +3,6 @@
40
41 __metaclass__ = type
42
43-
44 from textwrap import dedent
45 from BeautifulSoup import BeautifulSoup
46 from lazr.restful.interfaces import IJSONRequestCache
47@@ -368,11 +367,7 @@
48
49 def filebug_via_view(self, information_type=None,
50 bug_sharing_policy=None, extra_data_token=None):
51- form = {
52- 'field.title': 'A bug',
53- 'field.comment': 'A comment',
54- 'field.actions.submit_bug': 'Submit Bug Request',
55- }
56+ form = self.get_form()
57 if information_type:
58 form['field.information_type'] = information_type
59 product = self.factory.makeProduct(official_malone=True)
60@@ -695,6 +690,40 @@
61 'The file "attachment2" was attached to the bug report.'],
62 notifications)
63
64+ def test_submit_comment_with_large_extra_description(self):
65+ # Submitting a large blob will set a sensible error.
66+ string = 'y' * 50001
67+ token = self.process_extra_data("""\
68+ MIME-Version: 1.0
69+ Content-type: multipart/mixed; boundary=boundary
70+
71+ --boundary
72+ Content-disposition: inline
73+ Content-type: text/plain; charset=utf-8
74+
75+ %s
76+
77+ --boundary
78+ Content-disposition: inline
79+ Content-type: text/plain; charset=utf-8
80+
81+ A bug comment.
82+
83+ --boundary--
84+ """ % string)
85+ form = {
86+ 'field.title': 'Test title',
87+ 'field.comment': 'Test comment',
88+ 'field.actions.submit_bug': 'Submit Bug Report'}
89+ view = self.create_initialized_view(form)
90+ view.publishTraverse(view.request, token)
91+ view.validate(self.get_form())
92+ expected = [
93+ u'The description and the additional information is too long. '
94+ 'If you have lots of text to add, attach a file to the bug '
95+ 'instead.']
96+ self.assertContentEqual(expected, view.errors)
97+
98
99 class TestFileBugForNonBugSupervisors(TestCaseWithFactory):
100
101@@ -816,10 +845,8 @@
102 'help': '', 'disabled': False,
103 'css_class': 'status' + item.name}
104 bugtask_status_data.append(new_item)
105- self.assertEqual(
106- bugtask_status_data, cache['bugtask_status_data'])
107- excluded_importances = [
108- BugTaskImportance.UNKNOWN]
109+ self.assertEqual(bugtask_status_data, cache['bugtask_status_data'])
110+ excluded_importances = [BugTaskImportance.UNKNOWN]
111 bugtask_importance_data = []
112 for item in BugTaskImportance:
113 item = item.value
114
115=== modified file 'lib/lp/bugs/model/bug.py'
116--- lib/lp/bugs/model/bug.py 2012-10-25 05:09:07 +0000
117+++ lib/lp/bugs/model/bug.py 2012-11-06 05:59:19 +0000
118@@ -330,8 +330,7 @@
119 # db field names
120 name = StringCol(unique=True, default=None)
121 title = StringCol(notNull=True)
122- description = StringCol(notNull=False,
123- default=None)
124+ description = StringCol(notNull=False, default=None)
125 owner = ForeignKey(
126 dbName='owner', foreignKey='Person',
127 storm_validator=validate_public_person, notNull=True)
128@@ -433,16 +432,14 @@
129 """See `IBug`."""
130 return Store.of(self).find(
131 Person, BugAffectsPerson.person == Person.id,
132- BugAffectsPerson.affected,
133- BugAffectsPerson.bug == self)
134+ BugAffectsPerson.affected, BugAffectsPerson.bug == self)
135
136 @property
137 def users_unaffected(self):
138 """See `IBug`."""
139 return Store.of(self).find(
140 Person, BugAffectsPerson.person == Person.id,
141- Not(BugAffectsPerson.affected),
142- BugAffectsPerson.bug == self)
143+ Not(BugAffectsPerson.affected), BugAffectsPerson.bug == self)
144
145 @property
146 def user_ids_affected_with_dupes(self):
147@@ -466,8 +463,7 @@
148 def users_affected_with_dupes(self):
149 """See `IBug`."""
150 return Store.of(self).find(
151- Person,
152- Person.id.is_in(self.user_ids_affected_with_dupes))
153+ Person, Person.id.is_in(self.user_ids_affected_with_dupes))
154
155 @property
156 def users_affected_count_with_dupes(self):
157@@ -751,12 +747,9 @@
158 @cachedproperty
159 def initial_message(self):
160 """See `IBug`."""
161- store = Store.of(self)
162- messages = store.find(
163- Message,
164- BugMessage.bug == self,
165- BugMessage.message == Message.id).order_by('id')
166- return messages.first()
167+ return Store.of(self).find(
168+ Message, BugMessage.bug == self,
169+ BugMessage.message == Message.id).order_by('id').first()
170
171 @cachedproperty
172 def official_tags(self):
173@@ -766,13 +759,10 @@
174 from lp.registry.model.product import Product
175 table = OfficialBugTag
176 table = LeftJoin(
177- table,
178- Distribution,
179+ table, Distribution,
180 OfficialBugTag.distribution_id == Distribution.id)
181 table = LeftJoin(
182- table,
183- Product,
184- OfficialBugTag.product_id == Product.id)
185+ table, Product, OfficialBugTag.product_id == Product.id)
186 # When this method is typically called it already has the necessary
187 # info in memory, so rather than rejoin with Product etc, we do this
188 # bit in Python. If reviewing performance here feel free to change.
189@@ -903,17 +893,12 @@
190 return self.personIsSubscribedToDuplicate(person)
191
192 def _getMutes(self, person):
193- store = Store.of(self)
194- mutes = store.find(
195- BugMute,
196- BugMute.bug == self,
197- BugMute.person == person)
198- return mutes
199+ return Store.of(self).find(
200+ BugMute, BugMute.bug == self, BugMute.person == person)
201
202 def isMuted(self, person):
203 """See `IBug`."""
204- mutes = self._getMutes(person)
205- return not mutes.is_empty()
206+ return not self._getMutes(person).is_empty()
207
208 def mute(self, person, muted_by):
209 """See `IBug`."""
210@@ -928,19 +913,15 @@
211 if mutes.is_empty():
212 mute = BugMute(person, self)
213 Store.of(mute).flush()
214- else:
215- # It's already muted, pass.
216- pass
217
218 def unmute(self, person, unmuted_by):
219 """See `IBug`."""
220- store = Store.of(self)
221 if person is None:
222 # This may be a webservice request.
223 person = unmuted_by
224 mutes = self._getMutes(person)
225 if not mutes.is_empty():
226- store.remove(mutes.one())
227+ Store.of(self).remove(mutes.one())
228 return self.getSubscriptionForPerson(person)
229
230 @property
231@@ -1087,8 +1068,7 @@
232 def getSubscriptionForPerson(self, person):
233 """See `IBug`."""
234 return Store.of(self).find(
235- BugSubscription,
236- BugSubscription.person == person,
237+ BugSubscription, BugSubscription.person == person,
238 BugSubscription.bug == self).one()
239
240 def getAlsoNotifiedSubscribers(self, recipients=None, level=None):
241@@ -1152,8 +1132,8 @@
242 recipients = self.getBugNotificationRecipients(
243 level=BugNotificationLevel.COMMENTS)
244 getUtility(IBugNotificationSet).addNotification(
245- bug=self, is_comment=True,
246- message=message, recipients=recipients, activity=activity)
247+ bug=self, is_comment=True, message=message, recipients=recipients,
248+ activity=activity)
249
250 def addChange(self, change, recipients=None, deferred=False,
251 update_heat=True):
252@@ -1329,9 +1309,7 @@
253
254 def hasBranch(self, branch):
255 """See `IBug`."""
256- branch = BugBranch.selectOneBy(branch=branch, bug=self)
257-
258- return branch is not None
259+ return BugBranch.selectOneBy(branch=branch, bug=self) is not None
260
261 def linkBranch(self, branch, registrant):
262 """See `IBug`."""
263@@ -1358,18 +1336,15 @@
264
265 def getVisibleLinkedBranches(self, user, eager_load=False):
266 """Return all the branches linked to the bug that `user` can see."""
267- all_branches = getUtility(IAllBranches)
268- linked_branches = list(all_branches.visibleByUser(
269+ linked_branches = list(getUtility(IAllBranches).visibleByUser(
270 user).linkedToBugs([self]).getBranches(eager_load=eager_load))
271 if len(linked_branches) == 0:
272 return EmptyResultSet()
273 else:
274- store = Store.of(self)
275 branch_ids = [branch.id for branch in linked_branches]
276- return store.find(
277+ return Store.of(self).find(
278 BugBranch,
279- BugBranch.bug == self,
280- In(BugBranch.branchID, branch_ids))
281+ BugBranch.bug == self, In(BugBranch.branchID, branch_ids))
282
283 @cachedproperty
284 def has_cves(self):
285@@ -1522,8 +1497,7 @@
286 result = Store.of(self).find((BugMessage, Message, MessageChunk),
287 Message.id == MessageChunk.messageID,
288 BugMessage.messageID == Message.id,
289- BugMessage.bug == self.id,
290- *ranges)
291+ BugMessage.bug == self.id, *ranges)
292 result.order_by(BugMessage.index, MessageChunk.sequence)
293
294 def eager_load_owners(rows):
295@@ -1839,8 +1813,7 @@
296 @cachedproperty
297 def _cached_tags(self):
298 return list(Store.of(self).find(
299- BugTag.tag,
300- BugTag.bugID == self.id).order_by(BugTag.tag))
301+ BugTag.tag, BugTag.bugID == self.id).order_by(BugTag.tag))
302
303 def _setTags(self, tags):
304 """Set the tags from a list of strings."""
305@@ -1883,8 +1856,7 @@
306 if user is None:
307 return None
308 else:
309- return Store.of(self).get(
310- BugAffectsPerson, (self.id, user.id))
311+ return Store.of(self).get(BugAffectsPerson, (self.id, user.id))
312
313 def isUserAffected(self, user):
314 """See `IBug`."""
315@@ -1927,8 +1899,7 @@
316 # update the affected status.
317 if dupe_bug_ids:
318 Store.of(self).find(
319- BugAffectsPerson,
320- BugAffectsPerson.person == user,
321+ BugAffectsPerson, BugAffectsPerson.person == user,
322 BugAffectsPerson.bugID.is_in(dupe_bug_ids),
323 ).set(affected=affected)
324 for dupe in self.duplicates:
325@@ -2087,8 +2058,7 @@
326 store = Store.of(self)
327 subscriptions = store.find(
328 BugSubscription,
329- BugSubscription.bug == self,
330- BugSubscription.person == person)
331+ BugSubscription.bug == self, BugSubscription.person == person)
332 return not subscriptions.is_empty()
333
334 def personIsAlsoNotifiedSubscriber(self, person):
335@@ -2115,14 +2085,10 @@
336 return False
337 if person is None:
338 return False
339- store = Store.of(self)
340- subscriptions_from_dupes = store.find(
341- BugSubscription,
342- Bug.duplicateof == self,
343+ return not Store.of(self).find(
344+ BugSubscription, Bug.duplicateof == self,
345 BugSubscription.bug_id == Bug.id,
346- BugSubscription.person == person)
347-
348- return not subscriptions_from_dupes.is_empty()
349+ BugSubscription.person == person).is_empty()
350
351 def _reconcileAccess(self):
352 # reconcile_access_for_artifact will only use the pillar list if