Merge lp:~thumper/launchpad/fix-inline-diff-encoding into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Approved by: Michael Nelson
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~thumper/launchpad/fix-inline-diff-encoding
Merge into: lp:launchpad
Diff against target: 175 lines
5 files modified
lib/lp/code/browser/codereviewcomment.py (+34/-2)
lib/lp/code/browser/tests/test_branchmergeproposal.py (+37/-0)
lib/lp/code/mail/codereviewcomment.py (+5/-3)
lib/lp/code/mail/tests/test_codereviewcomment.py (+3/-2)
lib/lp/code/templates/codereviewcomment-body.pt (+2/-2)
To merge this branch: bzr merge lp:~thumper/launchpad/fix-inline-diff-encoding
Reviewer Review Type Date Requested Status
Michael Nelson (community) release-critical Approve
Michael Hudson-Doyle Approve
Review via email: mp+14196@code.launchpad.net

Commit message

The inline diffs are now decoded appropriately and rendered as a diff.

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

= Summary =

Fix bug 414852. This is due to the diff text of the emailed attachments not
being decoded correctly.

== Proposed fix ==

Decorate the attachment and decode the text that will be rendered.

== Implementation details ==

First added a test that replicates the current oops
(test_unicode_in_attachment_renders), although this found another error where
the attachments that were being emailed out were not being encoded correctly.
The production emailer probably handles this, but the test one doesn't like
non-ascii. This was fixed in the code review comment mailer. Instead of
directly adding message parts to be added as attachments, it now calls the
controller addAttachment method which does optimistic encoding of the
attachment.

Then changed the view to use the DiffAttachment class as it decodes the diff
text, and changed the template to use that. Added the other test to confirm
the decoding.

Finally changed the rendering of the diff from fmt:nice_pre to fmt:diff.

== Tests ==

TestCommentAttachmentRendering

== Demo and Q/A ==

make harness is your friend. You can copy the test case example and look at
it in the web browser.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/code/mail/codereviewcomment.py
  lib/lp/code/templates/codereviewcomment-body.pt
  lib/lp/code/browser/tests/test_branchmergeproposal.py
  lib/lp/code/browser/codereviewcomment.py

== Pylint notices ==

lib/lp/code/browser/codereviewcomment.py
    19: [F0401] Unable to import 'lazr.delegates' (No module named delegates)
    20: [F0401] Unable to import 'lazr.restful.interface' (No module named
restful)

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :
Download full text (7.1 KiB)

Basically looks fine. I wonder slightly what happens if it's not
really a diff? I guess it won't be harmful.

> === modified file 'lib/lp/code/browser/codereviewcomment.py'
> --- lib/lp/code/browser/codereviewcomment.py 2009-10-21 17:45:53 +0000
> +++ lib/lp/code/browser/codereviewcomment.py 2009-10-30 04:14:25 +0000
> @@ -16,11 +16,13 @@
> from zope.interface import Interface, implements
> from zope.schema import Text
>
> -from canonical.cachedproperty import cachedproperty
> from lazr.delegates import delegates
> from lazr.restful.interface import copy_field
>
> +from canonical.cachedproperty import cachedproperty
> +from canonical.config import config
> from canonical.launchpad import _
> +from canonical.launchpad.interfaces import ILibraryFileAlias
> from canonical.launchpad.webapp import (
> action, canonical_url, ContextMenu, custom_widget, LaunchpadFormView,
> LaunchpadView, Link)
> @@ -71,6 +73,36 @@
> return Link('+reply', 'Reply', icon='add', enabled=enabled)
>
>
> +class DiffAttachment:
> + """An attachment that we are going to display."""
> +
> + implements(ILibraryFileAlias)
> +
> + delegates(ILibraryFileAlias, 'alias')
> +
> + def __init__(self, alias):
> + self.alias = alias
> +
> + @cachedproperty
> + def text(self):
> + """Read the text out of the librarin."""
> + self.alias.open()
> + try:
> + return self.alias.read(config.diff.max_read_size)
> + finally:
> + self.alias.close()
> +
> + @cachedproperty
> + def diff_text(self):
> + """Get the text and attempt to decode it."""
> + try:
> + diff = self.text.decode('utf-8')
> + except UnicodeDecodeError:
> + diff = self.text.decode('windows-1252', 'replace')
> + # Strip off the trailing carriage returns.
> + return diff.rstrip('\n')
> +
> +
> class CodeReviewCommentView(LaunchpadView):
> """Standard view of a CodeReviewComment"""
> __used_for__ = ICodeReviewComment
> @@ -114,7 +146,7 @@
> @cachedproperty
> def display_attachments(self):
> # Attachments to show.
> - return self.all_attachments[0]
> + return [DiffAttachment(alias) for alias in self.all_attachments[0]]
>
> @cachedproperty
> def other_attachments(self):

This all looks nice.

> === modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
> --- lib/lp/code/browser/tests/test_branchmergeproposal.py 2009-09-18 22:28:26 +0000
> +++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2009-10-30 04:14:25 +0000
> @@ -25,7 +25,9 @@
> from lp.code.enums import BranchMergeProposalStatus, CodeReviewVote
> from lp.testing import (
> login_person, TestCaseWithFactory, time_counter)
> +from lp.testing.views import create_initialized_view
> from lp.code.model.diff import PreviewDiff, StaticDiff
> +from canonical.launchpad.database.message import MessageSet
> from canonical.launchpad.webapp.interfaces import IPrimaryContext
> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
> from canonical.testing import (
> @@ -655,5 +657,40 @@
> user=self.proposal.target_bra...

Read more...

review: Approve
Revision history for this message
Michael Nelson (michael.nelson) wrote :

Hi Tim, thanks for getting this in early!

This is the correct display there right?
http://people.canonical.com/~michaeln/tmp/tim_mp.png

If so, please add it as the first entry at https://dev.launchpad.net/CurrentRolloutBlockers :) and land.

Thanks,
Michael

review: Approve (release-critical)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/browser/codereviewcomment.py'
--- lib/lp/code/browser/codereviewcomment.py 2009-10-21 17:45:53 +0000
+++ lib/lp/code/browser/codereviewcomment.py 2009-11-01 23:19:18 +0000
@@ -16,11 +16,13 @@
16from zope.interface import Interface, implements16from zope.interface import Interface, implements
17from zope.schema import Text17from zope.schema import Text
1818
19from canonical.cachedproperty import cachedproperty
20from lazr.delegates import delegates19from lazr.delegates import delegates
21from lazr.restful.interface import copy_field20from lazr.restful.interface import copy_field
2221
22from canonical.cachedproperty import cachedproperty
23from canonical.config import config
23from canonical.launchpad import _24from canonical.launchpad import _
25from canonical.launchpad.interfaces import ILibraryFileAlias
24from canonical.launchpad.webapp import (26from canonical.launchpad.webapp import (
25 action, canonical_url, ContextMenu, custom_widget, LaunchpadFormView,27 action, canonical_url, ContextMenu, custom_widget, LaunchpadFormView,
26 LaunchpadView, Link)28 LaunchpadView, Link)
@@ -71,6 +73,36 @@
71 return Link('+reply', 'Reply', icon='add', enabled=enabled)73 return Link('+reply', 'Reply', icon='add', enabled=enabled)
7274
7375
76class DiffAttachment:
77 """An attachment that we are going to display."""
78
79 implements(ILibraryFileAlias)
80
81 delegates(ILibraryFileAlias, 'alias')
82
83 def __init__(self, alias):
84 self.alias = alias
85
86 @cachedproperty
87 def text(self):
88 """Read the text out of the librarin."""
89 self.alias.open()
90 try:
91 return self.alias.read(config.diff.max_read_size)
92 finally:
93 self.alias.close()
94
95 @cachedproperty
96 def diff_text(self):
97 """Get the text and attempt to decode it."""
98 try:
99 diff = self.text.decode('utf-8')
100 except UnicodeDecodeError:
101 diff = self.text.decode('windows-1252', 'replace')
102 # Strip off the trailing carriage returns.
103 return diff.rstrip('\n')
104
105
74class CodeReviewCommentView(LaunchpadView):106class CodeReviewCommentView(LaunchpadView):
75 """Standard view of a CodeReviewComment"""107 """Standard view of a CodeReviewComment"""
76 __used_for__ = ICodeReviewComment108 __used_for__ = ICodeReviewComment
@@ -114,7 +146,7 @@
114 @cachedproperty146 @cachedproperty
115 def display_attachments(self):147 def display_attachments(self):
116 # Attachments to show.148 # Attachments to show.
117 return self.all_attachments[0]149 return [DiffAttachment(alias) for alias in self.all_attachments[0]]
118150
119 @cachedproperty151 @cachedproperty
120 def other_attachments(self):152 def other_attachments(self):
121153
=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2009-09-18 22:28:26 +0000
+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2009-11-01 23:19:18 +0000
@@ -25,7 +25,9 @@
25from lp.code.enums import BranchMergeProposalStatus, CodeReviewVote25from lp.code.enums import BranchMergeProposalStatus, CodeReviewVote
26from lp.testing import (26from lp.testing import (
27 login_person, TestCaseWithFactory, time_counter)27 login_person, TestCaseWithFactory, time_counter)
28from lp.testing.views import create_initialized_view
28from lp.code.model.diff import PreviewDiff, StaticDiff29from lp.code.model.diff import PreviewDiff, StaticDiff
30from canonical.launchpad.database.message import MessageSet
29from canonical.launchpad.webapp.interfaces import IPrimaryContext31from canonical.launchpad.webapp.interfaces import IPrimaryContext
30from canonical.launchpad.webapp.servers import LaunchpadTestRequest32from canonical.launchpad.webapp.servers import LaunchpadTestRequest
31from canonical.testing import (33from canonical.testing import (
@@ -655,5 +657,40 @@
655 user=self.proposal.target_branch.owner)657 user=self.proposal.target_branch.owner)
656658
657659
660class TestCommentAttachmentRendering(TestCaseWithFactory):
661 """Test diff attachments are rendered correctly."""
662
663 layer = LaunchpadFunctionalLayer
664
665
666 def _makeCommentFromEmailWithAttachment(self, attachment_body):
667 # Make an email message with an attachment, and create a code
668 # review comment from it.
669 bmp = self.factory.makeBranchMergeProposal()
670 login_person(bmp.registrant)
671 msg = self.factory.makeEmailMessage(
672 body='testing',
673 attachments=[('test.diff', 'text/plain', attachment_body)])
674 message = MessageSet().fromEmail(msg.as_string())
675 return bmp.createCommentFromMessage(message, None, None, msg)
676
677 def test_nonascii_in_attachment_renders(self):
678 # The view should render without errors.
679 comment = self._makeCommentFromEmailWithAttachment('\xe2\x98\x95')
680 # Need to commit in order to read the diff out of the librarian.
681 transaction.commit()
682 view = create_initialized_view(comment, '+comment-body')
683 view()
684
685 def test_nonascii_in_attachment_decoded(self):
686 # The diff_text should be a unicode string.
687 comment = self._makeCommentFromEmailWithAttachment('\xe2\x98\x95')
688 # Need to commit in order to read the diff out of the librarian.
689 transaction.commit()
690 view = create_initialized_view(comment, '+comment-body')
691 [diff_attachment] = view.display_attachments
692 self.assertEqual(u'\u2615', diff_attachment.diff_text)
693
694
658def test_suite():695def test_suite():
659 return unittest.TestLoader().loadTestsFromName(__name__)696 return unittest.TestLoader().loadTestsFromName(__name__)
660697
=== modified file 'lib/lp/code/mail/codereviewcomment.py'
--- lib/lp/code/mail/codereviewcomment.py 2009-07-17 00:26:05 +0000
+++ lib/lp/code/mail/codereviewcomment.py 2009-11-01 23:19:18 +0000
@@ -52,7 +52,8 @@
52 else:52 else:
53 content_type = part['content-type']53 content_type = part['content-type']
54 if (filename, content_type) in include_attachments:54 if (filename, content_type) in include_attachments:
55 self.attachments.append(part)55 self.attachments.append(
56 (part.get_payload(), filename, content_type))
56 self._generateBodyBits()57 self._generateBodyBits()
5758
58 @classmethod59 @classmethod
@@ -132,6 +133,7 @@
132 def _addAttachments(self, ctrl, email):133 def _addAttachments(self, ctrl, email):
133 """Add the attachments from the original message."""134 """Add the attachments from the original message."""
134 # Only reattach the display_aliases.135 # Only reattach the display_aliases.
135 for attachment in self.attachments:136 for content, content_type, filename in self.attachments:
136 # Append directly to the controller's list.137 # Append directly to the controller's list.
137 ctrl.attachments.append(attachment)138 ctrl.addAttachment(
139 content, content_type=content_type, filename=filename)
138140
=== modified file 'lib/lp/code/mail/tests/test_codereviewcomment.py'
--- lib/lp/code/mail/tests/test_codereviewcomment.py 2009-07-17 00:26:05 +0000
+++ lib/lp/code/mail/tests/test_codereviewcomment.py 2009-11-01 23:19:18 +0000
@@ -212,8 +212,9 @@
212 comment = bmp.createCommentFromMessage(message, None, None, msg)212 comment = bmp.createCommentFromMessage(message, None, None, msg)
213 mailer = CodeReviewCommentMailer.forCreation(comment, msg)213 mailer = CodeReviewCommentMailer.forCreation(comment, msg)
214 # The attachments of the mailer should have only the diff.214 # The attachments of the mailer should have only the diff.
215 first, diff, image = msg.get_payload()215 [outgoing_attachment] = mailer.attachments
216 self.assertEqual([diff], mailer.attachments)216 self.assertEqual('inc.diff', outgoing_attachment[1])
217 self.assertEqual('text/x-diff', outgoing_attachment[2])
217218
218 def makeCommentAndParticipants(self):219 def makeCommentAndParticipants(self):
219 """Create a merge proposal and comment.220 """Create a merge proposal and comment.
220221
=== modified file 'lib/lp/code/templates/codereviewcomment-body.pt'
--- lib/lp/code/templates/codereviewcomment-body.pt 2009-07-17 17:59:07 +0000
+++ lib/lp/code/templates/codereviewcomment-body.pt 2009-11-01 23:19:18 +0000
@@ -6,9 +6,9 @@
6 <tal:message replace="structure view/body_text/fmt:obfuscate-email/fmt:nice_pre" />6 <tal:message replace="structure view/body_text/fmt:obfuscate-email/fmt:nice_pre" />
77
8 <tal:good-attachments repeat="attachment view/display_attachments">8 <tal:good-attachments repeat="attachment view/display_attachments">
9 <div class="boardComment attachment" tal:define="body_text python:str(attachment.read())">9 <div class="boardComment attachment">
10 <div class="boardCommentDetails filename"><a tal:content="attachment/filename" tal:attributes="href attachment/getURL"/></div>10 <div class="boardCommentDetails filename"><a tal:content="attachment/filename" tal:attributes="href attachment/getURL"/></div>
11 <div class="boardCommentBody attachmentBody" tal:content="structure body_text/fmt:nice_pre"/>11 <div class="boardCommentBody attachmentBody" tal:content="structure attachment/diff_text/fmt:diff"/>
12 </div>12 </div>
13 </tal:good-attachments>13 </tal:good-attachments>
14 <tal:other-attachments repeat="attachment view/other_attachments">14 <tal:other-attachments repeat="attachment view/other_attachments">