Merge lp:~adeuring/launchpad/bug-172501 into lp:launchpad/db-devel
- bug-172501
- Merge into db-devel
Status: | Merged |
---|---|
Approved by: | Graham Binns |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~adeuring/launchpad/bug-172501 |
Merge into: | lp:launchpad/db-devel |
Diff against target: |
574 lines (+358/-28) 9 files modified
lib/lp/bugs/browser/bugattachment.py (+98/-9) lib/lp/bugs/browser/bugmessage.py (+23/-4) lib/lp/bugs/browser/configure.zcml (+7/-0) lib/lp/bugs/interfaces/bugattachment.py (+8/-1) lib/lp/bugs/interfaces/bugmessage.py (+3/-2) lib/lp/bugs/stories/bugattachments/10-add-bug-attachment.txt (+134/-0) lib/lp/bugs/stories/bugattachments/20-edit-bug-attachment.txt (+40/-12) lib/lp/bugs/templates/bug-attachment-confirm-is-patch.pt (+40/-0) lib/lp/bugs/templates/bug-attachment-edit.pt (+5/-0) |
To merge this branch: | bzr merge lp:~adeuring/launchpad/bug-172501 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | code | Approve | |
Martin Albisetti (community) | ui | Approve | |
Review via email: mp+18187@code.launchpad.net |
This proposal supersedes a proposal from 2010-01-12.
Commit message
Description of the change
Abel Deuring (adeuring) wrote : Posted in a previous version of this proposal | # |
Graham Binns (gmb) wrote : Posted in a previous version of this proposal | # |
Hi Abel,
This is a nice branch - thanks for working on it.
I've got a few comments and I think that the patch detection logic could
benefit from being factored out into a mixin class, so I'm marking this
as needs-fixing.
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -84,7 +84,27 @@
> else:
> new_type = BugAttachmentTy
> if new_type != self.context.type:
> - self.context.type = new_type
> + filename = self.context.
> + file_content = self.context.
> + guessed_type, encoding = guess_content_type(
> + name=filename, body=file_content)
> + # We expect that users set data['patch'] to True only for
> + # real patch data, indicated by guessed_
> + # 'text/x-diff'. If there are inconsistencies, we don't
> + # the value automatically, but show the user this form
This should read "we don't set the value automatically."
> + # again with an explanation when the flag data['patch'] should
> + # be used.
And I think this would read better as "with an explanation of when the
flag...".
> + new_type_
> + (new_type == BugAttachmentTy
> + (guessed_type != 'text/x-diff'))
I dislike the use of the logical XOR here, but I can't think of a better
way to express this. Please add a comment above this line to explain why
an XOR is needed.
> + is_confirmation
> + 'confirmation_
This is inconsistent with the is_confirmation
there a reason why you don't use that here?
> + if new_type_
> + self.context.type = new_type
> + else:
> + self.next_url = (canonical_
> + '?need_confirm=1')
>
> if data['title'] != self.context.title:
> self.context.title = data['title']
> @@ -122,4 +142,24 @@
> return smartquote('Bug #%d - Edit attachment "%s"') % (
> self.context.
>
> + @property
> + def is_confirmation
> + """Is the page is displayed to confirm an unexpected "patch" value?"""
> + return self.request.
See above re: consistency of is_confirmation
> + @property
> + def confirmation_
> + """An extra hidden input field for the patch flag confirmation step.
> + """
> + if self.is_
> + return ('<input type="hidden" name="confirmat
> + 'value="1"/>')
> + else:
> + return ''
> +
> + @property
> + def is_patch(self):
> + """True if this attachment contains a patch, else False."""
> + ...
Abel Deuring (adeuring) wrote : Posted in a previous version of this proposal | # |
Hi Graham,
thanks for your review!
On 12.01.2010 12:58, Graham Binns wrote:
> Review: Needs Fixing code
> Hi Abel,
>
> This is a nice branch - thanks for working on it.
>
> I've got a few comments and I think that the patch detection logic could
> benefit from being factored out into a mixin class, so I'm marking this
> as needs-fixing.
>
>> === modified file 'lib/lp/
>> --- lib/lp/
>> +++ lib/lp/
>> @@ -84,7 +84,27 @@
>> else:
>> new_type = BugAttachmentTy
>> if new_type != self.context.type:
>> - self.context.type = new_type
>> + filename = self.context.
>> + file_content = self.context.
>> + guessed_type, encoding = guess_content_type(
>> + name=filename, body=file_content)
>> + # We expect that users set data['patch'] to True only for
>> + # real patch data, indicated by guessed_
>> + # 'text/x-diff'. If there are inconsistencies, we don't
>> + # the value automatically, but show the user this form
>
> This should read "we don't set the value automatically."
oops, fixed.
>
>> + # again with an explanation when the flag data['patch'] should
>> + # be used.
>
> And I think this would read better as "with an explanation of when the
> flag...".
fixed.
>
>> + new_type_
>> + (new_type == BugAttachmentTy
>> + (guessed_type != 'text/x-diff'))
>
> I dislike the use of the logical XOR here, but I can't think of a better
> way to express this. Please add a comment above this line to explain why
> an XOR is needed.
Changed.
>
>> + is_confirmation
>> + 'confirmation_
>
> This is inconsistent with the is_confirmation
> there a reason why you don't use that here?
ouch, a stupid name conflict... Here, we want to figure out, if the form
data which the user submitted comes from the confirmation step, while
the property below is used to render HTML data for the confirmation
form. I renamed the property to "rendering_
>
>> + if new_type_
>> + self.context.type = new_type
>> + else:
>> + self.next_url = (canonical_
>> + '?need_confirm=1')
>>
>> if data['title'] != self.context.title:
>> self.context.title = data['title']
>> @@ -122,4 +142,24 @@
>> return smartquote('Bug #%d - Edit attachment "%s"') % (
>> self.context.
>>
>> + @property
>> + def is_confirmation
>> + """Is the page is displayed to confirm an unexpected "patch" value?"""
>> + return self.request.
>
> See above re: consistenc...
Graham Binns (gmb) : Posted in a previous version of this proposal | # |
Martin Albisetti (beuno) wrote : Posted in a previous version of this proposal | # |
I really like this change you're doing, it will make the experience so much nicer.
I do think this is an interaction we want to be very careful about, since it's easy for people to misunderstand what happened.
The first thing would be to reduce the amount of text on the page by, say, a million times (or as close to that as possible). The more text on there, the less people will read at all.
My proposal:
- Show the file name of the uploaded file with maybe a green check, to feed back that the upload was successful
- I think this is better phrased as a question rather than an edit form:
This looks like a patch (<small>what is a patch</small>)
<b>Is this a patch?</b>
[_yes_] [no]
------------------
This doesn't look like a patch (<small>what is a patch</small>)
<b>Is this a patch?</b>
[yes] [_no_]
Abel Deuring (adeuring) wrote : | # |
I'm re-submitting this branch for review, because Martin
suggested in his UI review to use simplified view where
the user must confirm that a bug attachment is indeed a patch,
if Launchpad believes it is not.
When a user sets the "patch" flag to an value considered to be
inconsistent by Launchpad, a click to the "change bugattachment"
and "create new bug comment" buttons leads him to a new view
"+confirm-
of the "patch" flag again.
As a drive-by, I also fixed bug 182286 ("Content Type:" on bug
attachment page is editable even when it has no effect). I simply
removed the code that unconditionally sets teh content type
of patches to "text/plain". As noted by Kjell Braden in a comment
on bug 182286, this is sometimes simply wrong. The alternative would
have been to carry over the overriding of the content type setting
for patches to the new view class BugAttachmentPa
That would have been a bit more effort for a questionaable feature
than to fix bug 182286 which simply meant that I had to remove a few
lines of code and tests.
The implementation is quite straightforward:
- a new template and a new view class for the page "+confirm-is-patch"
- a mixin class BugAttachmentCo
if the content type as guessed by Launchpad matches the patch flag
selection made by the user. This mixin class is used in
BugAttachment
test: ./bin/test -vv -f lp.bugs stories.
Demo/QA:
- Create a new bug attachment with a filename like "123.diff"
but do not check the "patch" flag
- Ensure that you are led to the "+confirm-is-patch" view.
- Ensure that the "patch" flag is currently set for this attachment:
On the main bug page, it should appear in the "patches" portlet.
- choose "no" in the confirmation view and click "change"
- The attachment should now appear in the "attachments" portlet of
the main bug page.
- Create a new bug attachment with a filename like "456.png" and
check the "patch" flag.
- Ensure that you are led to the "+confirm-is-patch" view.
- Ensure that the "patch" flag is currently _not_ set for this
attachment: On the main bug page, it should appear in the
"attachments" portlet.
- choose "yes" in the confirmation view and click "change"
- The attachment should now appear in the "patches" portlet of
the main bug page.
- Change the "patch" flag of the two new attachments in their "edit"
view. If the new value looks wrong to LP, you are again led to the
"+confirm-
screenshots:
http://
Add diff file without setting the "patch" flag
http://
confirmation view for the new attachment
http://
Add a PNG file and check the "patch" flag
http://
confirmation view for the new attachment/patch
http://
confirmation view shown when the "patch" flag is set to a value which
...
Martin Albisetti (beuno) wrote : | # |
This is much much better, thanks for re-working this Abel.
The only suggestion I'd leave is that "Is this a patch" should end with a question mark instead of a semi-colon. It may be a restriction of Launchpad forms, if it is, "Is this a patch?:" maybe be better.
Abel Deuring (adeuring) wrote : | # |
On 28.01.2010 15:26, Martin Albisetti wrote:
> Review: Approve ui
> This is much much better, thanks for re-working this Abel.
>
> The only suggestion I'd leave is that "Is this a patch" should end with a question mark instead of a semi-colon. It may be a restriction of Launchpad forms, if it is, "Is this a patch?:" maybe be better.
yes, I think too that a question mark would be better. The ':' is
generated by LPFormView -- but "Is this a patch ?:" would look quite
ugly, I think, so I'd prefer to stay with the simple ':'.
Martin Albisetti (beuno) wrote : | # |
I agree it's a bit ugly, but the question mark is a key symbol there :)
I'm not going to block on it, I'll leave it up to you.
Graham Binns (gmb) : | # |
Preview Diff
1 | === modified file 'lib/lp/bugs/browser/bugattachment.py' |
2 | --- lib/lp/bugs/browser/bugattachment.py 2009-11-26 10:35:21 +0000 |
3 | +++ lib/lp/bugs/browser/bugattachment.py 2010-02-02 14:56:19 +0000 |
4 | @@ -1,3 +1,4 @@ |
5 | + |
6 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | |
9 | @@ -5,6 +6,7 @@ |
10 | |
11 | __metaclass__ = type |
12 | __all__ = [ |
13 | + 'BugAttachmentContentCheck', |
14 | 'BugAttachmentSetNavigation', |
15 | 'BugAttachmentEditView', |
16 | 'BugAttachmentURL', |
17 | @@ -14,12 +16,15 @@ |
18 | |
19 | from zope.interface import implements |
20 | from zope.component import getUtility |
21 | +from zope.contenttype import guess_content_type |
22 | |
23 | -from canonical.launchpad.webapp import canonical_url, GetitemNavigation |
24 | +from canonical.launchpad.webapp import ( |
25 | + canonical_url, custom_widget, GetitemNavigation) |
26 | from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet |
27 | from canonical.launchpad.webapp.interfaces import ILaunchBag |
28 | from lp.bugs.interfaces.bugattachment import ( |
29 | - BugAttachmentType, IBugAttachmentEditForm, IBugAttachmentSet) |
30 | + BugAttachmentType, IBugAttachmentEditForm, |
31 | + IBugAttachmentIsPatchConfirmationForm, IBugAttachmentSet) |
32 | from canonical.launchpad.webapp.interfaces import ICanonicalUrlData |
33 | from canonical.launchpad.webapp.launchpadform import ( |
34 | action, LaunchpadFormView) |
35 | @@ -27,6 +32,33 @@ |
36 | |
37 | from canonical.lazr.utils import smartquote |
38 | |
39 | +from canonical.widgets.itemswidgets import LaunchpadBooleanRadioWidget |
40 | + |
41 | + |
42 | +class BugAttachmentContentCheck: |
43 | + """A mixin class that checks the consistency of patch flag and file type. |
44 | + """ |
45 | + |
46 | + def guessContentType(self, filename, file_content): |
47 | + """Guess the content type a file with the given anme and content.""" |
48 | + guessed_type, encoding = guess_content_type( |
49 | + name=filename, body=file_content) |
50 | + return guessed_type |
51 | + |
52 | + def attachmentTypeConsistentWithContentType( |
53 | + self, patch_flag_set, filename, file_content): |
54 | + """Return True iff patch_flag is consistent with filename and content. |
55 | + """ |
56 | + guessed_type = self.guessContentType(filename, file_content) |
57 | + # An XOR of "is the patch flag selected?" with "is the |
58 | + # guessed type not a diff?" tells us if the type selected |
59 | + # by the user matches the guessed type. |
60 | + return (patch_flag_set ^ (guessed_type != 'text/x-diff')) |
61 | + |
62 | + def nextUrlForInconsistentPatchFlags(self, attachment): |
63 | + """The next_url value used for an inconistent patch flag.""" |
64 | + return canonical_url(attachment) + '/+confirm-is-patch' |
65 | + |
66 | |
67 | class BugAttachmentSetNavigation(GetitemNavigation): |
68 | |
69 | @@ -57,12 +89,11 @@ |
70 | return u"+attachment/%d" % self.context.id |
71 | |
72 | |
73 | -class BugAttachmentEditView(LaunchpadFormView): |
74 | +class BugAttachmentEditView(LaunchpadFormView, BugAttachmentContentCheck): |
75 | """Edit a bug attachment.""" |
76 | |
77 | schema = IBugAttachmentEditForm |
78 | field_names = ['title', 'patch', 'contenttype'] |
79 | - label = "Change bug attachment information" |
80 | |
81 | def __init__(self, context, request): |
82 | LaunchpadFormView.__init__(self, context, request) |
83 | @@ -84,15 +115,27 @@ |
84 | else: |
85 | new_type = BugAttachmentType.UNSPECIFIED |
86 | if new_type != self.context.type: |
87 | - self.context.type = new_type |
88 | + filename = self.context.libraryfile.filename |
89 | + file_content = self.context.libraryfile.read() |
90 | + # We expect that users set data['patch'] to True only for |
91 | + # real patch data, indicated by guessed_content_type == |
92 | + # 'text/x-diff'. If there are inconsistencies, we don't |
93 | + # set the value automatically. Instead, we lead the user to |
94 | + # another form where we ask him if he is sure about his |
95 | + # choice of the patch flag. |
96 | + new_type_consistent_with_guessed_type = ( |
97 | + self.attachmentTypeConsistentWithContentType( |
98 | + new_type == BugAttachmentType.PATCH, filename, |
99 | + file_content)) |
100 | + if new_type_consistent_with_guessed_type: |
101 | + self.context.type = new_type |
102 | + else: |
103 | + self.next_url = self.nextUrlForInconsistentPatchFlags( |
104 | + self.context) |
105 | |
106 | if data['title'] != self.context.title: |
107 | self.context.title = data['title'] |
108 | |
109 | - # If it's a patch, 'text/plain' is always used. |
110 | - if (self.context.type == BugAttachmentType.PATCH |
111 | - and data['contenttype'] != 'text/plain'): |
112 | - data['contenttype'] = 'text/plain' |
113 | if self.context.libraryfile.mimetype != data['contenttype']: |
114 | self.updateContentType(data['contenttype']) |
115 | |
116 | @@ -123,3 +166,49 @@ |
117 | self.context.bug.id, self.context.title) |
118 | |
119 | page_title = label |
120 | + |
121 | + |
122 | +class BugAttachmentPatchConfirmationView(LaunchpadFormView): |
123 | + """Confirmation of the "patch" flag setting. |
124 | + |
125 | + If the user sets the "patch" flag to a value that is inconsistent |
126 | + with the result of a call of guess_content_type() for this |
127 | + attachment, we show this view to ask the user if he is sure |
128 | + about his selection. |
129 | + """ |
130 | + |
131 | + schema = IBugAttachmentIsPatchConfirmationForm |
132 | + |
133 | + custom_widget('patch', LaunchpadBooleanRadioWidget) |
134 | + |
135 | + def __init__(self, context, request): |
136 | + LaunchpadFormView.__init__(self, context, request) |
137 | + self.next_url = self.cancel_url = ( |
138 | + canonical_url(ICanonicalUrlData(context).inside)) |
139 | + |
140 | + def initialize(self): |
141 | + super(BugAttachmentPatchConfirmationView, self).initialize() |
142 | + self.widgets['patch'].setRenderedValue(self.is_patch) |
143 | + |
144 | + @property |
145 | + def label(self): |
146 | + return smartquote('Bug #%d - Confirm attachment type of "%s"') % ( |
147 | + self.context.bug.id, self.context.title) |
148 | + |
149 | + page_title = label |
150 | + |
151 | + @action('Change', name='change') |
152 | + def change_action(self, action, data): |
153 | + current_patch_setting = self.context.type == BugAttachmentType.PATCH |
154 | + if data['patch'] != current_patch_setting: |
155 | + if data['patch']: |
156 | + self.context.type = BugAttachmentType.PATCH |
157 | + #xxxxxxxxxx adjust content type! |
158 | + # xxx use mixin, together with BugAttachmnetEditView |
159 | + else: |
160 | + self.context.type = BugAttachmentType.UNSPECIFIED |
161 | + |
162 | + @property |
163 | + def is_patch(self): |
164 | + """True if this attachment contains a patch, else False.""" |
165 | + return self.context.type == BugAttachmentType.PATCH |
166 | |
167 | === modified file 'lib/lp/bugs/browser/bugmessage.py' |
168 | --- lib/lp/bugs/browser/bugmessage.py 2009-08-28 14:31:15 +0000 |
169 | +++ lib/lp/bugs/browser/bugmessage.py 2010-02-02 14:56:19 +0000 |
170 | @@ -12,13 +12,14 @@ |
171 | |
172 | from zope.component import getUtility |
173 | |
174 | +from lp.bugs.browser.bugattachment import BugAttachmentContentCheck |
175 | from lp.bugs.interfaces.bugmessage import IBugMessageAddForm |
176 | from lp.bugs.interfaces.bugwatch import IBugWatchSet |
177 | from canonical.launchpad.webapp import action, canonical_url |
178 | from canonical.launchpad.webapp import LaunchpadFormView |
179 | |
180 | |
181 | -class BugMessageAddFormView(LaunchpadFormView): |
182 | +class BugMessageAddFormView(LaunchpadFormView, BugAttachmentContentCheck): |
183 | """Browser view class for adding a bug comment/attachment.""" |
184 | |
185 | schema = IBugMessageAddForm |
186 | @@ -88,6 +89,7 @@ |
187 | self.request.response.addNotification( |
188 | "Thank you for your comment.") |
189 | |
190 | + self.next_url = canonical_url(self.context) |
191 | if file_: |
192 | |
193 | # Slashes in filenames cause problems, convert them to dashes |
194 | @@ -102,15 +104,32 @@ |
195 | file_description = filename |
196 | |
197 | # Process the attachment. |
198 | - bug.addAttachment( |
199 | + # If the patch flag is not consistent with the result of |
200 | + # the guess made in attachmentTypeConsistentWithContentType(), |
201 | + # we use the guessed type and lead the user to a page |
202 | + # where he can override the flag value, if Luanchpad's |
203 | + # guess is wrong. |
204 | + patch_flag_consistent = ( |
205 | + self.attachmentTypeConsistentWithContentType( |
206 | + data['patch'], filename, data['filecontent'])) |
207 | + if not patch_flag_consistent: |
208 | + guessed_type = self.guessContentType( |
209 | + filename, data['filecontent']) |
210 | + is_patch = guessed_type == 'text/x-diff' |
211 | + else: |
212 | + is_patch = data['patch'] |
213 | + attachment = bug.addAttachment( |
214 | owner=self.user, data=StringIO(data['filecontent']), |
215 | filename=filename, description=file_description, |
216 | - comment=message, is_patch=data['patch']) |
217 | + comment=message, is_patch=is_patch) |
218 | + |
219 | + if not patch_flag_consistent: |
220 | + self.next_url = self.nextUrlForInconsistentPatchFlags( |
221 | + attachment) |
222 | |
223 | self.request.response.addNotification( |
224 | "Attachment %s added to bug." % filename) |
225 | |
226 | - self.next_url = canonical_url(self.context) |
227 | |
228 | def shouldShowEmailMeWidget(self): |
229 | """Should the subscribe checkbox be shown?""" |
230 | |
231 | === modified file 'lib/lp/bugs/browser/configure.zcml' |
232 | --- lib/lp/bugs/browser/configure.zcml 2010-01-18 21:44:59 +0000 |
233 | +++ lib/lp/bugs/browser/configure.zcml 2010-02-02 14:56:19 +0000 |
234 | @@ -718,6 +718,13 @@ |
235 | name="+edit" |
236 | permission="launchpad.AnyPerson" |
237 | template="../templates/bug-attachment-edit.pt"/> |
238 | + <browser:page |
239 | + for="lp.bugs.interfaces.bugattachment.IBugAttachment" |
240 | + class="lp.bugs.browser.bugattachment.BugAttachmentPatchConfirmationView" |
241 | + facet="bugs" |
242 | + name="+confirm-is-patch" |
243 | + permission="launchpad.AnyPerson" |
244 | + template="../templates/bug-attachment-confirm-is-patch.pt"/> |
245 | <browser:navigation |
246 | module="lp.bugs.browser.bugattachment" |
247 | classes=" |
248 | |
249 | === modified file 'lib/lp/bugs/interfaces/bugattachment.py' |
250 | --- lib/lp/bugs/interfaces/bugattachment.py 2010-01-20 17:09:40 +0000 |
251 | +++ lib/lp/bugs/interfaces/bugattachment.py 2010-02-02 14:56:19 +0000 |
252 | @@ -12,6 +12,7 @@ |
253 | 'IBugAttachment', |
254 | 'IBugAttachmentSet', |
255 | 'IBugAttachmentEditForm', |
256 | + 'IBugAttachmentIsPatchConfirmationForm', |
257 | ] |
258 | |
259 | from zope.interface import Interface |
260 | @@ -136,5 +137,11 @@ |
261 | "text/plain"), |
262 | required=True) |
263 | patch = Bool( |
264 | - title=u"This attachment is a patch", |
265 | + title=u"This attachment contains a solution (patch) for this bug", |
266 | required=True, default=False) |
267 | + |
268 | + |
269 | +class IBugAttachmentIsPatchConfirmationForm(Interface): |
270 | + """Schema used to confirm the setting of the "patch" flag.""" |
271 | + |
272 | + patch = Bool(title=u"Is this file a patch", required=True, default=False) |
273 | |
274 | === modified file 'lib/lp/bugs/interfaces/bugmessage.py' |
275 | --- lib/lp/bugs/interfaces/bugmessage.py 2009-12-11 10:03:46 +0000 |
276 | +++ lib/lp/bugs/interfaces/bugmessage.py 2010-02-02 14:56:19 +0000 |
277 | @@ -81,8 +81,9 @@ |
278 | filecontent = Bytes( |
279 | title=u"Attachment", required=False, |
280 | constraint=attachment_size_constraint) |
281 | - patch = Bool(title=u"This attachment is a patch", required=False, |
282 | - default=False) |
283 | + patch = Bool( |
284 | + title=u"This attachment contains a solution (patch) for this bug", |
285 | + required=False, default=False) |
286 | attachment_description = Title(title=u'Description', required=False) |
287 | email_me = Bool( |
288 | title=u"E-mail me about changes to this bug report", |
289 | |
290 | === modified file 'lib/lp/bugs/stories/bugattachments/10-add-bug-attachment.txt' |
291 | --- lib/lp/bugs/stories/bugattachments/10-add-bug-attachment.txt 2009-09-22 16:35:00 +0000 |
292 | +++ lib/lp/bugs/stories/bugattachments/10-add-bug-attachment.txt 2010-02-02 14:56:19 +0000 |
293 | @@ -70,3 +70,137 @@ |
294 | ... print li_tag.a.renderContents() |
295 | Some information |
296 | bar.txt |
297 | + |
298 | +We can also declare an attachment to be a patch. |
299 | + |
300 | + >>> user_browser.open( |
301 | + ... 'http://bugs.launchpad.dev/firefox/+bug/1/+addcomment') |
302 | + |
303 | +Leading and trailing whitespace are stripped from the description of the |
304 | +attachment. |
305 | + |
306 | + >>> foo_file.seek(0) |
307 | + >>> user_browser.getControl('Attachment').add_file( |
308 | + ... foo_file, 'text/plain', 'foo.diff') |
309 | + >>> user_browser.getControl('Description').value = 'A fix for this bug.' |
310 | + >>> patch_control = user_browser.getControl( |
311 | + ... 'This attachment contains a solution (patch) for this bug') |
312 | + >>> patch_control.selected = True |
313 | + >>> user_browser.getControl( |
314 | + ... name="field.comment").value = 'Added some information' |
315 | + >>> user_browser.getControl('Post Comment').click() |
316 | + >>> user_browser.url |
317 | + 'http://bugs.launchpad.dev/firefox/+bug/1' |
318 | + |
319 | +If we add an attachment that looks like a patch but if we don't set |
320 | +the flag "this attachment is a patch"... |
321 | + |
322 | + >>> user_browser.open( |
323 | + ... 'http://bugs.launchpad.dev/firefox/+bug/1/+addcomment') |
324 | + >>> foo_file.seek(0) |
325 | + >>> user_browser.getControl('Attachment').add_file( |
326 | + ... foo_file, 'text/plain', 'foo2.diff') |
327 | + >>> user_browser.getControl('Description').value = 'More data' |
328 | + >>> patch_control = user_browser.getControl( |
329 | + ... 'This attachment contains a solution (patch) for this bug') |
330 | + >>> patch_control.selected = False |
331 | + >>> user_browser.getControl( |
332 | + ... name="field.comment").value = 'Added even more information' |
333 | + >>> user_browser.getControl('Post Comment').click() |
334 | + |
335 | +...we are redirected to a page... |
336 | + |
337 | + >>> user_browser.url |
338 | + 'http://bugs.launchpad.dev/firefox/+bug/1/+attachment/4/+confirm-is-patch' |
339 | + |
340 | +...where we see a message that we should double-check if this file |
341 | +is indeed not a patch. |
342 | + |
343 | + >>> print extract_text(find_tags_by_class( |
344 | + ... user_browser.contents, 'documentDescription')[0]) |
345 | + This file looks like a patch. |
346 | + What is a patch? |
347 | + |
348 | +Also, we have "yes"/"no" radio buttons to answer the question "Is this a |
349 | +patch?". The currently selected radio button is "yes". |
350 | + |
351 | + >>> patch_control_yes = user_browser.getControl('yes') |
352 | + >>> patch_control_yes.selected |
353 | + True |
354 | + >>> patch_control_no = user_browser.getControl('no') |
355 | + >>> patch_control_no.selected |
356 | + False |
357 | + |
358 | +We want indeed to declare the file as not being a patch, so we unselect |
359 | +the "patch" checkbox again and submit the form. |
360 | + |
361 | + >>> patch_control_no.selected = True |
362 | + >>> user_browser.getControl('Change').click() |
363 | + |
364 | +Now we are redirected to the main bug page, and the new file is |
365 | +listed as an ordinary attachment. |
366 | + |
367 | + >>> user_browser.url |
368 | + 'http://bugs.launchpad.dev/firefox/+bug/1' |
369 | + >>> attachments = find_portlet(user_browser.contents, 'Bug attachments') |
370 | + >>> for li_tag in attachments.findAll('li', 'download-attachment'): |
371 | + ... print li_tag.a.renderContents() |
372 | + Some information |
373 | + bar.txt |
374 | + More data |
375 | + |
376 | +Similary, if we add an attachment that does not look like a patch and |
377 | +if we set the "patch" flag for this attachment... |
378 | + |
379 | + >>> user_browser.open( |
380 | + ... 'http://bugs.launchpad.dev/firefox/+bug/1/+addcomment') |
381 | + >>> foo_file.seek(0) |
382 | + >>> user_browser.getControl('Attachment').add_file( |
383 | + ... foo_file, 'text/plain', 'foo.png') |
384 | + >>> user_browser.getControl('Description').value = 'A better icon for foo' |
385 | + >>> patch_control = user_browser.getControl( |
386 | + ... 'This attachment contains a solution (patch) for this bug') |
387 | + >>> patch_control.selected = True |
388 | + >>> user_browser.getControl('Post Comment').click() |
389 | + |
390 | +...we are redirected to the page where we must confirm that this attachment |
391 | +is indeed a patch. |
392 | + |
393 | + >>> user_browser.url |
394 | + 'http://bugs.launchpad.dev/firefox/+bug/1/+attachment/5/+confirm-is-patch' |
395 | + |
396 | +...where we see a message asking us if we really ant to declare this file |
397 | +as a patch. |
398 | + |
399 | + >>> print extract_text(find_tags_by_class( |
400 | + ... user_browser.contents, 'documentDescription')[0]) |
401 | + This file does not look like a patch. |
402 | + What is a patch? |
403 | + |
404 | +Also, the "patch" flag is not yet set. |
405 | + |
406 | + >>> patch_control_yes = user_browser.getControl('yes') |
407 | + >>> patch_control_yes.selected |
408 | + False |
409 | + >>> patch_control_no = user_browser.getControl('no') |
410 | + >>> patch_control_no.selected |
411 | + True |
412 | + |
413 | +Let's pretend that the file contains an improved icon, so we set |
414 | +the "patch" flag again and save the changes. |
415 | + |
416 | + >>> patch_control_yes.selected = True |
417 | + >>> user_browser.getControl('Change').click() |
418 | + |
419 | +Now we are redirected to the main bug page... |
420 | + |
421 | + >>> user_browser.url |
422 | + 'http://bugs.launchpad.dev/firefox/+bug/1' |
423 | + |
424 | +...and the new attachment is listed as a patch. |
425 | + |
426 | + >>> patches = find_portlet(user_browser.contents, 'Patches') |
427 | + >>> for li_tag in patches.findAll('li', 'download-attachment'): |
428 | + ... print li_tag.a.renderContents() |
429 | + A fix for this bug. |
430 | + A better icon for foo |
431 | |
432 | === modified file 'lib/lp/bugs/stories/bugattachments/20-edit-bug-attachment.txt' |
433 | --- lib/lp/bugs/stories/bugattachments/20-edit-bug-attachment.txt 2009-12-10 15:12:22 +0000 |
434 | +++ lib/lp/bugs/stories/bugattachments/20-edit-bug-attachment.txt 2010-02-02 14:56:19 +0000 |
435 | @@ -32,18 +32,50 @@ |
436 | We can edit the attachment to be a patch. |
437 | |
438 | >>> user_browser.getLink(url='+attachment/1').click() |
439 | - >>> user_browser.getControl('This attachment is a patch').selected = True |
440 | - >>> user_browser.getControl('Change').click() |
441 | + >>> patch_control = user_browser.getControl( |
442 | + ... 'This attachment contains a solution (patch) for this bug') |
443 | + >>> patch_control.selected = True |
444 | + >>> user_browser.getControl('Change').click() |
445 | + |
446 | +The server now checks if the attachment looks like a patch. In this case, |
447 | +is doesn't, and we get the edit page again, with a message asking us if |
448 | +the attachment should indeed be labeled as a patch |
449 | + |
450 | + >>> user_browser.url |
451 | + 'http://bugs.launchpad.dev/firefox/+bug/1/+attachment/1/+confirm-is-patch' |
452 | + |
453 | + >>> print extract_text(find_tags_by_class( |
454 | + ... user_browser.contents, 'documentDescription')[0]) |
455 | + This file does not look like a patch. |
456 | + What is a patch? |
457 | + |
458 | +We are sure that this file is indeed a patch, so let's activate the |
459 | +currenty inactive "yes" radion button of the question "Is this a patch?" |
460 | +again and submit the form. |
461 | + |
462 | + >>> patch_control_yes = user_browser.getControl('yes') |
463 | + >>> patch_control_yes.selected |
464 | + False |
465 | + >>> patch_control_no = user_browser.getControl('no') |
466 | + >>> patch_control_no.selected |
467 | + True |
468 | + >>> patch_control_yes.selected = True |
469 | + >>> user_browser.getControl('Change').click() |
470 | + |
471 | +Now we are redirected to the main bug page... |
472 | |
473 | >>> user_browser.url |
474 | 'http://bugs.launchpad.dev/firefox/+bug/1' |
475 | |
476 | -The attachment that became a patch is now shown in the portlet "Patches"... |
477 | +...the attachment that became a patch is now shown in the portlet |
478 | +"Patches"... |
479 | |
480 | >>> patches = find_portlet(user_browser.contents, 'Patches') |
481 | >>> for li_tag in patches.findAll('li', 'download-attachment'): |
482 | ... print li_tag.a.renderContents() |
483 | Another title |
484 | + A fix for this bug. |
485 | + A better icon for foo |
486 | |
487 | ...while it is gone from the portlet "Bug attachments". |
488 | |
489 | @@ -51,19 +83,15 @@ |
490 | >>> for li_tag in attachments.findAll('li', 'download-attachment'): |
491 | ... print li_tag.a.renderContents() |
492 | bar.txt |
493 | - |
494 | -The content type of a patch is automatically changed to text/plain. |
495 | - |
496 | - >>> user_browser.getLink(url='+attachment/1').click() |
497 | - >>> user_browser.getControl('Content Type').value |
498 | - 'text/plain' |
499 | - |
500 | -Clicking the link "Add patch" in the patches portlet open the form |
501 | + More data |
502 | + |
503 | +Clicking the link "Add patch" in the patches portlet opens the form |
504 | to add bug comments with the checkbox "This attachment is a patch" |
505 | enabled. |
506 | |
507 | >>> user_browser.open('http://bugs.launchpad.dev/firefox/+bug/1') |
508 | >>> user_browser.getLink('Add patch').click() |
509 | - >>> patch_checkbox = user_browser.getControl('This attachment is a patch') |
510 | + >>> patch_checkbox = user_browser.getControl( |
511 | + ... 'This attachment contains a solution (patch) for this bug') |
512 | >>> patch_checkbox.selected |
513 | True |
514 | |
515 | === added file 'lib/lp/bugs/templates/bug-attachment-confirm-is-patch.pt' |
516 | --- lib/lp/bugs/templates/bug-attachment-confirm-is-patch.pt 1970-01-01 00:00:00 +0000 |
517 | +++ lib/lp/bugs/templates/bug-attachment-confirm-is-patch.pt 2010-02-02 14:56:19 +0000 |
518 | @@ -0,0 +1,40 @@ |
519 | +<html |
520 | + xmlns="http://www.w3.org/1999/xhtml" |
521 | + xmlns:tal="http://xml.zope.org/namespaces/tal" |
522 | + xmlns:metal="http://xml.zope.org/namespaces/metal" |
523 | + xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
524 | + metal:use-macro="view/macro:page/main_only" |
525 | + i18n:domain="malone" |
526 | +> |
527 | +<body> |
528 | + <div metal:fill-slot="main"> |
529 | + |
530 | + <div metal:use-macro="context/@@launchpad_form/form"> |
531 | + |
532 | + <div metal:fill-slot="extra_info" class="documentDescription"> |
533 | + <tal:is_patch condition="view/is_patch"> |
534 | + <p> |
535 | + <strong>This file looks like a patch.</strong> |
536 | + <small> |
537 | + <a href="https://help.launchpad.net/Bugs/BugAttchements"> |
538 | + What is a patch? |
539 | + </a> |
540 | + </small> |
541 | + </p> |
542 | + </tal:is_patch> |
543 | + <tal:is_not_patch condition="not: view/is_patch"> |
544 | + <p> |
545 | + <strong>This file does not look like a patch. |
546 | + </strong> |
547 | + <small> |
548 | + <a href="https://help.launchpad.net/Bugs/BugAttchements"> |
549 | + What is a patch? |
550 | + </a> |
551 | + </small> |
552 | + </p> |
553 | + </tal:is_not_patch> |
554 | + </div> |
555 | + </div> |
556 | + </div> |
557 | +</body> |
558 | +</html> |
559 | |
560 | === modified file 'lib/lp/bugs/templates/bug-attachment-edit.pt' |
561 | --- lib/lp/bugs/templates/bug-attachment-edit.pt 2009-08-31 11:55:51 +0000 |
562 | +++ lib/lp/bugs/templates/bug-attachment-edit.pt 2010-02-02 14:56:19 +0000 |
563 | @@ -15,6 +15,11 @@ |
564 | An attachment can provide valuable information about the bug. It |
565 | can also provide a direct solution to a bug - for example a patch |
566 | that fixes the bug. |
567 | + <small> |
568 | + <a href="https://help.launchpad.net/Bugs/BugAttchements"> |
569 | + What is a patch? |
570 | + </a> |
571 | + </small> |
572 | </div> |
573 | </div> |
574 | </div> |
This branch fixes bug 172501: reject non-code patch attachements.
As discussed in that bug, non-code patches are not simply rejected. Instead, if a user labels an attachment as being a patch and if the file does not look like being a patch, he is redirected to the attachment edit page, where a message tells him that Launchpad thinks that the file does not seem to be a patch. Also, the patch flag is not yet set. If the user thinks that the file is indeed a patch, he can set the flag again and LP will finally set the flag on a click on the "change" button.
Similary, if a file is uploaded that looks like a patch but that is not marked as a patch, the patch flag is set by Launchpad and the user is redirected to the attachment edit page, where he can unset the flag, if he thinks that it makes sense.
The detection of the file content is unfortunately overly simple. It relies function zope.contenttyp e.guess_ content_ type(), which in turn relies mostly on Python's mimetypes. guess_type( ).
guess_type() simply looks at the filename suffix; while guess_content_ type() accepts a parameter "body", it is not used if guess_type() finds a mime type based on the filename.
So, a "mis-named" file is not properly detected, which may lead to some confusion. I considered to call file(1) or to make some other use of the "magic" database, but running "file some-diff- output. diff" returned several results like "ASCII text", "UTF8 text", "C code" but nothing related to diff output.
Implementation:
The methods BugMessage. save_action( ) and BugAttachmentEd itView. change_ action( ) now make the consistency check described above. If they find an inconistency, they set next_url to canonical_ url(attachment) +'?need_ confirm= 1'.
If the bug attachment edit page is called with the GET parameter need_confirm, and additional message is displayed, and a hidden form field named "confirmation_step" is added. The latter is used by BugAttachmentEd itView. change_ action( ) to avoid that next_url is set again to the edit view, when a user confirms his setting of the "patch" flag.
The label of "patch" flag is now a bit more verbose (the text was proposed by Karl).
The template lib/lp/ bugs/templates/ bug-attachment- edit.pt displays some additional explanations, when the page is loaded with the ?need_conform" GET parameter. This text also contians a link to https:/ /help.launchpad .net/Bugs/ BugAttchements , which tries to give even more explataions of what a patch is. (I suspect that this page could be improved...)
test: ./bin/test -vv -f lp.bugs stories. bugattachments
= Launchpad lint =
Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.
Linting changed files: bugs/browser/ bugattachment. py bugs/browser/ bugmessage. py bugs/interfaces /bugattachment. py bugs/interfaces /bugmessage. py bugs/stories/ bugattachments/ 10-add- bug-attachment. txt bugs/stories/ bugattachments/ 20-edit- bug-attachment. txt bugs/templates/ bug-attachment- edit.pt
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
== Pylint notices ==
lib/lp/ bugs/interfaces /bugattachment. py
19: [F0401] Unable to import 'lazr.enum' (No module named enum)
...