merge r46437 and r46946 from davisagli-features: respect the per-comment mime_type setting, and use the old cooked text/html from legacy comments when migrating

svn path=/plone.app.discussion/trunk/; revision=48356
This commit is contained in:
David Glick 2011-04-02 19:51:37 +00:00
parent bf5946367a
commit eb004aab44
11 changed files with 79 additions and 47 deletions

View File

@ -4,6 +4,19 @@ Changelog
2.0b1 (Unreleased) 2.0b1 (Unreleased)
------------------ ------------------
- Use the cooked text of legacy comments when migrating.
[davisagli]
- Make sure that comment text is transformed to plain text when indexing.
[davisagli]
- Move logic for transforming comment text to the Comment class's getText
method. Use a comment instance's mime_type attribute in preference to the
global setting for the source mimetype. Use text/x-html-safe as the target
mimetype to make sure the safe HTML filter is applied, in case the source is
untrusted HTML.
[davisagli]
- Provide a filter_callback option to the migration view, so that a custom - Provide a filter_callback option to the migration view, so that a custom
policy for which comments get migrated can be implemented. policy for which comments get migrated can be implemented.
[davisagli] [davisagli]

View File

@ -80,7 +80,7 @@
<div class="commentBody"> <div class="commentBody">
<span tal:replace="structure python:view.cook(reply.getText())" /> <span tal:replace="structure reply/getText" />
<div class="commentActions"> <div class="commentActions">
<form name="delete" <form name="delete"

View File

@ -271,17 +271,6 @@ class CommentsViewlet(ViewletBase):
# view methods # view methods
def cook(self, text):
transforms = getToolByName(self, 'portal_transforms')
targetMimetype = 'text/html'
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
mimetype = settings.text_transform
return transforms.convertTo(targetMimetype,
text,
context=self,
mimetype=mimetype).getData()
def can_reply(self): def can_reply(self):
"""Returns true if current user has the 'Reply to item' permission. """Returns true if current user has the 'Reply to item' permission.
""" """

View File

@ -70,7 +70,8 @@ class View(BrowserView):
# create a reply object # create a reply object
comment = CommentFactory() comment = CommentFactory()
comment.title = reply.Title() comment.title = reply.Title()
comment.text = reply.text comment.text = reply.cooked_text
comment.mime_type = 'text/html'
comment.creator = reply.Creator() comment.creator = reply.Creator()
email = reply.getProperty('email', None) email = reply.getProperty('email', None)

View File

@ -110,7 +110,7 @@
tal:content="item/in_response_to" /> tal:content="item/in_response_to" />
</td> </td>
<td> <td>
<span tal:replace="structure python:view.cook(item.Description)" /> <span tal:replace="structure item/Description" />
<a href="" <a href=""
tal:attributes="href string:${item/getURL}/getText" tal:attributes="href string:${item/getURL}/getText"
tal:condition="python:item.Description.endswith('[...]')" tal:condition="python:item.Description.endswith('[...]')"

View File

@ -2,7 +2,6 @@ from Acquisition import aq_inner, aq_parent
from Products.Five.browser import BrowserView from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.CMFCore.utils import getToolByName from Products.CMFCore.utils import getToolByName
@ -37,9 +36,6 @@ class View(BrowserView):
sort_order='reverse') sort_order='reverse')
return self.template() return self.template()
def cook(self, text):
return text
def moderation_enabled(self): def moderation_enabled(self):
"""Returns true if a 'review workflow' is enabled on 'Discussion Item' """Returns true if a 'review workflow' is enabled on 'Discussion Item'
content type. A 'review workflow' is characterized by implementing content type. A 'review workflow' is characterized by implementing

View File

@ -77,14 +77,14 @@ def creator(object):
@indexer(IComment) @indexer(IComment)
def description(object): def description(object):
# Return the first 25 words of the comment text and append ' [...]' # Return the first 25 words of the comment text and append ' [...]'
text = join(object.text.split()[:MAX_DESCRIPTION]) text = join(object.getText(targetMimetype='text/plain').split()[:MAX_DESCRIPTION])
if len(object.text.split()) > 25: if len(object.getText().split()) > 25:
text += " [...]" text += " [...]"
return text return text
@indexer(IComment) @indexer(IComment)
def searchable_text(object): def searchable_text(object):
return object.text return object.getText(targetMimetype='text/plain')
@indexer(IComment) @indexer(IComment)
def in_response_to(object): def in_response_to(object):

View File

@ -79,7 +79,7 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
title = u"" title = u""
mime_type = "text/plain" mime_type = None
text = u"" text = u""
creator = None creator = None
@ -113,10 +113,26 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
""" """
return self.id return self.id
def getText(self): def getText(self, targetMimetype=None):
"""The body text of a comment. """The body text of a comment.
""" """
return self.text transforms = getToolByName(self, 'portal_transforms')
if targetMimetype is None:
targetMimetype = 'text/x-html-safe'
sourceMimetype = getattr(self, 'mime_type', None)
if sourceMimetype is None:
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
sourceMimetype = settings.text_transform
text = self.text
if isinstance(text, unicode):
text = text.encode('utf8')
return transforms.convertTo(targetMimetype,
text,
context=self,
mimetype=sourceMimetype).getData()
def Title(self): def Title(self):
"""The title of the comment. """The title of the comment.

View File

@ -123,6 +123,44 @@ class CommentTest(PloneTestCase):
comment1 = createObject('plone.Comment') comment1 = createObject('plone.Comment')
self.assertEquals(comment1.Type(), 'Comment') self.assertEquals(comment1.Type(), 'Comment')
def test_getText(self):
comment1 = createObject('plone.Comment')
comment1.text = """First paragraph
Second paragraph"""
self.assertEquals(comment1.getText(),
"<p>First paragraph<br /><br /> Second paragraph</p>")
def test_getText_escapes_HTML(self):
comment1 = createObject('plone.Comment')
comment1.text = """<b>Got HTML?</b>"""
self.assertEquals(comment1.getText(),
"<p>&lt;b&gt;Got HTML?&lt;/b&gt;</p>")
def test_getText_with_non_ascii_characters(self):
comment1 = createObject('plone.Comment')
comment1.text = u"""Umlaute sind ä, ö und ü."""
self.assertEquals(comment1.getText(),
'<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>')
def test_getText_doesnt_link(self):
comment1 = createObject('plone.Comment')
comment1.text = "Go to http://www.plone.org"
self.assertEquals(comment1.getText(),
"<p>Go to http://www.plone.org</p>")
def test_getText_uses_comment_mime_type(self):
comment1 = createObject('plone.Comment')
comment1.text = "Go to http://www.plone.org"
comment1.mime_type = 'text/x-web-intelligent'
self.assertEquals(comment1.getText(),
'Go to <a href="http://www.plone.org" rel="nofollow">http://www.plone.org</a>')
def test_getText_w_custom_targetMimetype(self):
comment1 = createObject('plone.Comment')
comment1.text = 'para'
self.assertEquals(comment1.getText(targetMimetype='text/plain'), 'para')
def test_traversal(self): def test_traversal(self):
# make sure comments are traversable, have an id, absolute_url and # make sure comments are traversable, have an id, absolute_url and
# physical path # physical path

View File

@ -228,28 +228,6 @@ class TestCommentsViewlet(PloneTestCase):
settings = registry.forInterface(IDiscussionSettings) settings = registry.forInterface(IDiscussionSettings)
settings.globally_enabled = True settings.globally_enabled = True
def test_cook(self):
text = """First paragraph
Second paragraph"""
self.assertEquals(self.viewlet.cook(text),
"<p>First paragraph<br /><br /> Second paragraph</p>")
def test_cook_no_html(self):
text = """<b>Got HTML?</b>"""
self.assertEquals(self.viewlet.cook(text),
"<p>&lt;b&gt;Got HTML?&lt;/b&gt;</p>")
def test_cook_with_no_ascii_characters(self):
text = """Umlaute sind ä, ö und ü."""
self.assertEquals(self.viewlet.cook(text),
"<p>Umlaute sind \xc3\xa4, \xc3\xb6 und \xc3\xbc.</p>")
def test_cook_links(self):
text = "Go to http://www.plone.org"
self.assertEquals(self.viewlet.cook(text),
"<p>Go to http://www.plone.org</p>")
def test_can_reply(self): def test_can_reply(self):
# Portal owner can reply # Portal owner can reply
self.failUnless(self.viewlet.can_reply()) self.failUnless(self.viewlet.can_reply())

View File

@ -69,7 +69,8 @@ class MigrationTest(PloneTestCase):
comment1 = conversation.values()[0] comment1 = conversation.values()[0]
self.assert_(IComment.providedBy(comment1)) self.assert_(IComment.providedBy(comment1))
self.assertEquals(comment1.Title(), 'Jim on Document 1') self.assertEquals(comment1.Title(), 'Jim on Document 1')
self.assertEquals(comment1.text, 'My Text') self.assertEquals(comment1.text, '<p>My Text</p>\n')
self.assertEquals(comment1.mime_type, 'text/html')
self.assertEquals(comment1.Creator(), 'Jim') self.assertEquals(comment1.Creator(), 'Jim')
self.assertEquals(comment1.creation_date, self.assertEquals(comment1.creation_date,
datetime(2003, 3, 11, 9, 28, 6)) datetime(2003, 3, 11, 9, 28, 6))