[Checkins] SVN: zc.comment/trunk/src/zc/comment/ Initial import. An
annotations-based drop-in aspect (i.e,
no change to a class is necessary) that gives objects a comments tab.
(note: probably need to rename ntests.py to ftests.py)
Gary Poster
gary at zope.com
Tue Aug 15 16:54:03 EDT 2006
Log message for revision 69537:
Initial import. An annotations-based drop-in aspect (i.e, no change to a class is necessary) that gives objects a comments tab. (note: probably need to rename ntests.py to ftests.py)
Changed:
A zc.comment/trunk/src/zc/comment/__init__.py
A zc.comment/trunk/src/zc/comment/browser/
A zc.comment/trunk/src/zc/comment/browser/__init__.py
A zc.comment/trunk/src/zc/comment/browser/comments.pt
A zc.comment/trunk/src/zc/comment/browser/comments.txt
A zc.comment/trunk/src/zc/comment/browser/commentssub.pt
A zc.comment/trunk/src/zc/comment/browser/configure.zcml
A zc.comment/trunk/src/zc/comment/browser/ftests.py
A zc.comment/trunk/src/zc/comment/browser/tests.py
A zc.comment/trunk/src/zc/comment/browser/views.py
A zc.comment/trunk/src/zc/comment/browser/widget.py
A zc.comment/trunk/src/zc/comment/comment.py
A zc.comment/trunk/src/zc/comment/comment.txt
A zc.comment/trunk/src/zc/comment/configure.zcml
A zc.comment/trunk/src/zc/comment/i18n.py
A zc.comment/trunk/src/zc/comment/interfaces.py
A zc.comment/trunk/src/zc/comment/ntests.py
A zc.comment/trunk/src/zc/comment/test.zcml
A zc.comment/trunk/src/zc/comment/tests.py
-=-
Added: zc.comment/trunk/src/zc/comment/__init__.py
===================================================================
--- zc.comment/trunk/src/zc/comment/__init__.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/__init__.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,14 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
Added: zc.comment/trunk/src/zc/comment/browser/__init__.py
===================================================================
--- zc.comment/trunk/src/zc/comment/browser/__init__.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/browser/__init__.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,14 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
Added: zc.comment/trunk/src/zc/comment/browser/comments.pt
===================================================================
--- zc.comment/trunk/src/zc/comment/browser/comments.pt 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/browser/comments.pt 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,17 @@
+<html metal:use-macro="context/@@standard_macros/view"
+ i18n:domain="zc.intranet">
+<head>
+</head>
+<body>
+<div metal:fill-slot="body" tal:define="empty not:view/formatter/items">
+ <div metal:use-macro="view/template:default/macros/form" >
+ <div metal:fill-slot="extra_info">
+ <div tal:condition="empty"
+ i18n:translate="">No comments have been made.</div>
+ <table tal:condition="not:empty" tal:replace="structure view/formatter">
+ </table>
+ </div>
+ </div>
+</div>
+</body>
+</html>
Added: zc.comment/trunk/src/zc/comment/browser/comments.txt
===================================================================
--- zc.comment/trunk/src/zc/comment/browser/comments.txt 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/browser/comments.txt 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,141 @@
+Commenting UI
+=============
+
+Create the browser object we'll be using.
+
+ >>> from zope.testbrowser.testing import Browser
+ >>> browser = Browser()
+ >>> browser.addHeader('Accept-Language', 'test')
+
+To see how comments work, we'll create an instance of a simple content
+object:
+
+ >>> browser.open('http://localhost/@@contents.html')
+ >>> browser.getLink('[[zope][[top]]]').click()
+ >>> browser.getLink('[[zc.comment][Content]]').click()
+ >>> browser.getControl(name='new_value').value = 'number'
+ >>> browser.getControl('[[zope][container-apply-button (Apply)]]').click()
+
+Let's visit the object and click on the comments tab:
+
+ >>> browser.getLink('number').click()
+ >>> browser.getLink('[[zc.comment][Comments]]').click()
+
+We see that no comments have been made yet:
+
+ >>> '[[zc.intranet][No comments have been made.]]' in browser.contents
+ True
+
+Let's add a new multi-line comment:
+
+ >>> browser.getControl('[[zc.comment][New Comment]]').value = '''\
+ ... I give my pledge, as an Earthling
+ ... to save, and faithfully defend from waste
+ ... the natural resources of my planet.
+ ... It's soils, minerals, forests, waters, and wildlife.
+ ... '''
+
+ >>> browser.getControl('[[zc.comment][Add Comment]]').click()
+
+Now, we get a table that displays the comment with it's date, text,
+and the user who made it:
+
+ >>> print browser.contents
+ <...
+ <th>
+ ...[[zc.comment][comment_column-date (Date)]]...
+ </th>
+ <th>
+ ...[[zc.comment][comment_column-principals (Principals)]]...
+ </th>
+ <th>
+ [[zc.comment][comment_column-comment (Comment)]]
+ </th>
+ ...
+ <td>
+ 2005 11 14 12:00:55 -500
+ </td>
+ <td>
+ Unauthenticated User
+ </td>
+ <td>
+ I give my pledge, as an Earthling<br />
+ to save, and faithfully defend from waste<br />
+ the natural resources of my planet.<br />
+ It's soils, minerals, forests, waters, and wildlife.<br />
+ ...
+ <label for="form.comment">
+ <span class="required">*</span><span>[[zc.comment][New Comment]]</span>
+ </label>
+ ...<textarea class="zc-comment-text"
+ style="width: 50ex; height: 6em;"
+ cols="60" id="form.comment"
+ name="form.comment" rows="15" ></textarea></div>
+ ...
+ <input type="submit"
+ id="form.actions.41646420436f6d6d656e74"
+ name="form.actions.41646420436f6d6d656e74"
+ value="[[zc.comment][Add Comment]]"
+ class="button" />
+ ...
+
+Now, we'll add another comment.
+
+ >>> browser.getControl('[[zc.comment][New Comment]]'
+ ... ).value = 'another comment'
+ >>> browser.getControl('[[zc.comment][Add Comment]]').click()
+ >>> print browser.contents
+ <...
+ <th>
+ ...[[zc.comment][comment_column-date (Date)]]...
+ </th>
+ <th>
+ ...[[zc.comment][comment_column-principals (Principals)]]...
+ </th>
+ <th>
+ [[zc.comment][comment_column-comment (Comment)]]
+ </th>
+ </tr>
+ ...
+ <td>
+ 2005 11 14 12:10:18 -500
+ </td>
+ <td>
+ Unauthenticated User
+ </td>
+ <td>
+ I give my pledge, as an Earthling<br />
+ to save, and faithfully defend from waste<br />
+ the natural resources of my planet.<br />
+ It's soils, minerals, forests, waters, and wildlife.<br />
+ <BLANKLINE>
+ </td>
+ </tr>
+ ...
+ <td>
+ 2005 11 14 12:10:18 -500
+ </td>
+ <td>
+ Unauthenticated User
+ </td>
+ <td>
+ another comment
+ </td>
+ </tr>
+ ...
+ <label for="form.comment">
+ <span class="required">*</span><span>[[zc.comment][New Comment]]</span>
+ </label>
+ ...
+ ...<textarea class="zc-comment-text"
+ style="width: 50ex; height: 6em;"
+ cols="60"
+ id="form.comment"
+ name="form.comment"
+ rows="15" ></textarea>...
+ <input type="submit"
+ id="form.actions.41646420436f6d6d656e74"
+ name="form.actions.41646420436f6d6d656e74"
+ value="[[zc.comment][Add Comment]]"
+ class="button" />
+ ...
Property changes on: zc.comment/trunk/src/zc/comment/browser/comments.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zc.comment/trunk/src/zc/comment/browser/commentssub.pt
===================================================================
--- zc.comment/trunk/src/zc/comment/browser/commentssub.pt 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/browser/commentssub.pt 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,11 @@
+<div tal:define="empty not:view/formatter/items">
+ <h5>Comments</h5>
+ <div tal:condition="empty">
+ No comments have been made.
+ </div>
+ <table class="listing" tal:condition="not:empty">
+ <thead tal:content="structure view/formatter/renderHeaders"></thead>
+ <tbody tal:content="structure view/formatter/renderRows"></tbody>
+ </table>
+ <div metal:use-macro="view/template:default/macros/form" />
+</div>
Added: zc.comment/trunk/src/zc/comment/browser/configure.zcml
===================================================================
--- zc.comment/trunk/src/zc/comment/browser/configure.zcml 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/browser/configure.zcml 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,34 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:zc="http://namespaces.zope.com/zc"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ i18n_domain="zc.comment"
+ >
+
+ <browser:page
+ for="..interfaces.ICommentable"
+ name="comments.html"
+ menu="zmi_views"
+ title="Comments"
+ template="comments.pt"
+ class=".views.Comments"
+ permission="zope.View"
+ />
+
+ <view
+ type="zope.publisher.interfaces.browser.IBrowserRequest"
+ for="zc.comment.interfaces.ICommentText"
+ provides="zope.app.form.interfaces.IDisplayWidget"
+ factory=".widget.Display"
+ permission="zope.Public"
+ />
+
+ <view
+ type="zope.publisher.interfaces.browser.IBrowserRequest"
+ for="zc.comment.interfaces.ICommentText"
+ provides="zope.app.form.interfaces.IInputWidget"
+ factory=".widget.Input"
+ permission="zope.Public"
+ />
+
+</configure>
Added: zc.comment/trunk/src/zc/comment/browser/ftests.py
===================================================================
--- zc.comment/trunk/src/zc/comment/browser/ftests.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/browser/ftests.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,72 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""Functional tests for the comment text widgets.
+
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+
+from zope import interface, component
+import zope.publisher.browser
+
+import zope.app.form.interfaces
+import zope.app.testing.functional
+
+import zc.comment.interfaces
+import zc.comment.browser.widget
+
+
+class IFace(interface.Interface):
+
+ foo = zc.comment.interfaces.CommentText(
+ title=u"Foo",
+ description=u"Foo description",
+ )
+
+class Face(object):
+
+ foo = (u"Foo<br />\n"
+ u"\n"
+ u"Bar < & >")
+
+
+class WidgetConfigurationTestCase(
+ zope.app.testing.functional.FunctionalTestCase):
+
+ """Check that configure.zcml sets up the widgets as expected."""
+
+ def setUp(self):
+ super(WidgetConfigurationTestCase, self).setUp()
+ self.field = IFace["foo"]
+ self.bound_field = self.field.bind(Face())
+ self.request = zope.publisher.browser.TestRequest()
+
+ def test_display_widget_lookup(self):
+ w = component.getMultiAdapter(
+ (self.bound_field, self.request),
+ zope.app.form.interfaces.IDisplayWidget)
+ self.failUnless(isinstance(w,
+ zc.comment.browser.widget.Display))
+
+ def test_input_widget_lookup(self):
+ w = component.getMultiAdapter(
+ (self.bound_field, self.request),
+ zope.app.form.interfaces.IInputWidget)
+ self.failUnless(isinstance(w, zc.comment.browser.widget.Input))
+
+
+def test_suite():
+ return unittest.makeSuite(WidgetConfigurationTestCase)
Added: zc.comment/trunk/src/zc/comment/browser/tests.py
===================================================================
--- zc.comment/trunk/src/zc/comment/browser/tests.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/browser/tests.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,115 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""Tests of the widget code for comment text.
+
+"""
+__docformat__ = "reStructuredText"
+
+import unittest
+
+from zope.app.form.browser.tests import test_browserwidget
+
+import zc.comment.interfaces
+import zc.comment.browser.widget
+
+
+class TestBase(object):
+
+ _FieldFactory = zc.comment.interfaces.CommentText
+
+
+class DisplayWidgetTestCase(TestBase, test_browserwidget.BrowserWidgetTest):
+ """Tests of the display widget."""
+
+ _WidgetFactory = zc.comment.browser.widget.Display
+
+ def test_render_empty_string(self):
+ self._widget.setRenderedValue("")
+ self.assertEqual(self._widget(),
+ '<div class="zc-comment-text"></div>')
+
+ def test_render_multiline(self):
+ self._widget.setRenderedValue("line 1<br />\n<br />\nline 2")
+ self.assertEqual(self._widget(),
+ '<div class="zc-comment-text">line 1<br />\n'
+ '<br />\n'
+ 'line 2</div>')
+
+ def test_render_missing_value(self):
+ self._widget.setRenderedValue(self._widget.context.missing_value)
+ self.assertEqual(self._widget(), '')
+
+
+class InputWidgetTestCase(TestBase, test_browserwidget.BrowserWidgetTest):
+
+ _WidgetFactory = zc.comment.browser.widget.Input
+
+ def setUp(self):
+ super(InputWidgetTestCase, self).setUp()
+ self.clearForm()
+
+ def clearForm(self):
+ form = self._widget.request.form
+ if "field.foo" in form:
+ del form["field.foo"]
+
+ def test_hasInput(self):
+ self.failIf(self._widget.hasInput())
+ form = self._widget.request.form
+ form["field.foo"] = u'some text'
+ self.failUnless(self._widget.hasInput())
+ self._widget.setRenderedValue(u"other text")
+ self.failUnless(self._widget.hasInput())
+ self.clearForm()
+ self.failIf(self._widget.hasInput())
+
+ def test_getInputValue_one_line(self):
+ self._widget.request.form["field.foo"] = u'line of text'
+ self.assertEqual(self._widget.getInputValue(), u'line of text')
+
+ def test_getInputValue_multi_line(self):
+ self._widget.request.form["field.foo"] = u'line 1\rline 2'
+ self.assertEqual(self._widget.getInputValue(), u'line 1<br />\nline 2')
+
+ def test_render_missing_value(self):
+ self._widget.setRenderedValue(self._widget.context.missing_value)
+ self.verifyResult(self._widget(),
+ ['<textarea', 'class="zc-comment-text"',
+ '></textarea>'],
+ inorder=True)
+
+ def test_render_empty_string(self):
+ self._widget.setRenderedValue("")
+ self.verifyResult(self._widget(),
+ ['<textarea', 'class="zc-comment-text"',
+ '></textarea>'],
+ inorder=True)
+
+ def test_render_multi_line(self):
+ self._widget.setRenderedValue(u"line 1<br />\n<br />\nline 3"
+ u" < & > ")
+ self.verifyResult(self._widget(),
+ ['<textarea', 'class="zc-comment-text"',
+ 'line 1\n\nline 3', '< & > </textarea'],
+ inorder=True)
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ for cls in (DisplayWidgetTestCase,
+ InputWidgetTestCase,
+ ):
+ suite.addTest(unittest.makeSuite(cls))
+ return suite
Added: zc.comment/trunk/src/zc/comment/browser/views.py
===================================================================
--- zc.comment/trunk/src/zc/comment/browser/views.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/browser/views.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,105 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""views for comments
+
+$Id: views.py 5074 2006-02-06 23:54:54Z fred $
+"""
+from zope import interface, schema, component
+import zope.cachedescriptors.property
+
+from zope.app import zapi
+from zope.app.pagetemplate import ViewPageTemplateFile
+
+import zope.formlib.form
+import zc.table.column
+import zc.table.interfaces
+from zc.table import table
+from zope.interface.common.idatetime import ITZInfo
+
+from zc.comment import interfaces
+from zc.comment.i18n import _
+
+class SortableColumn(zc.table.column.GetterColumn):
+ interface.implements(zc.table.interfaces.ISortableColumn)
+
+def dateFormatter(value, context, formatter):
+ value = value.astimezone(ITZInfo(formatter.request))
+ dateFormatter = formatter.request.locale.dates.getFormatter(
+ 'dateTime', length='long')
+ return dateFormatter.format(value)
+
+def principalsGetter(context, formatter):
+ principals = zapi.principals()
+ return [principals.getPrincipal(pid) for pid in context.principal_ids]
+
+def principalsFormatter(value, context, formatter):
+ return ', '.join([v.title for v in value])
+
+columns = [
+ SortableColumn(
+ _('comment_column-date','Date'), lambda c, f: c.date, dateFormatter),
+ SortableColumn(
+ _('comment_column-principals', 'Principals'), principalsGetter,
+ principalsFormatter),
+ zc.table.column.GetterColumn( # XXX escape?
+ _('comment_column-comment', 'Comment'), lambda c, f: c.body)
+ ]
+
+class Comments(zope.formlib.form.PageForm):
+
+ label = _("Comments")
+
+ template = ViewPageTemplateFile('comments.pt')
+
+ form_fields = zope.formlib.form.Fields(
+ interfaces.CommentText(
+ __name__ = 'comment',
+ title=_("New Comment"),
+ ),
+ )
+
+ def setUpWidgets(self, ignore_request=False):
+ super(Comments, self).setUpWidgets(ignore_request=ignore_request)
+ comment = self.widgets.get('comment')
+ if comment is not None:
+ comment.style="width: 50ex; height: 6em;"
+ comment.setRenderedValue(u'')
+
+ @zope.cachedescriptors.property.Lazy
+ def formatter(self):
+ adapted = interfaces.IComments(self.context)
+ factory = component.getUtility(zc.table.interfaces.IFormatterFactory)
+ formatter = factory(self.context, self.request, adapted,
+ columns=columns)
+ return formatter
+
+ @zope.formlib.form.action(_("Add Comment"))
+ def add(self, action, data):
+ comment = data.get('comment')
+ self.form_reset = True
+ if comment:
+ adapted = interfaces.IComments(self.context)
+ adapted.add(comment)
+ self.request.response.redirect(self.request.URL)
+
+class CommentsSubPage(zope.formlib.form.SubPageForm, Comments):
+
+ label = u''
+
+ template = ViewPageTemplateFile('commentssub.pt')
+
+class CommentsViewSubPage(CommentsSubPage):
+
+ actions = form_fields = ()
Added: zc.comment/trunk/src/zc/comment/browser/widget.py
===================================================================
--- zc.comment/trunk/src/zc/comment/browser/widget.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/browser/widget.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,94 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""Widgets for rich text fields.
+
+The *rich text* supported by these widgets is very simple, but we
+expect this to be forward-compatible with more elaborate rich text
+support in the future.
+
+The input is a conventional XHTML textarea; newlines are converted to
+<br/> elements and other characters are encoded into XHTML entity
+references as needed.
+
+"""
+__docformat__ = "reStructuredText"
+
+import HTMLParser
+import xml.sax.saxutils
+
+import zope.app.form.browser.textwidgets
+import zope.app.form.browser.widget
+
+
+class Input(zope.app.form.browser.textwidgets.TextAreaWidget):
+
+ cssClass = "zc-comment-text"
+
+ def _toFieldValue(self, value):
+ if value:
+ # normalize newlines:
+ value = value.replace("\r\n", "\n")
+ value = value.replace("\r", "\n")
+ # encode magical characters:
+ value = xml.sax.saxutils.escape(value)
+ # add <br/> tags:
+ value = value.replace("\n", "<br />\n")
+ return value
+
+ def _toFormValue(self, value):
+ if value == self.context.missing_value:
+ return ""
+ if value:
+ # rip out XHTML encoding, converting markup back to plain text
+ # (we're encoding simple rich text as plain text!)
+ p = ConversionParser()
+ p.feed(value)
+ p.close()
+ value = p.get_data()
+ return value
+
+
+class Display(zope.app.form.browser.widget.DisplayWidget):
+
+ cssClass = "zc-comment-text"
+ tag = "div"
+
+ def __call__(self):
+ if self._renderedValueSet():
+ value = self._data
+ else:
+ value = self.context.default
+ if value == self.context.missing_value:
+ return ""
+ if self.tag:
+ value = zope.app.form.browser.widget.renderElement(
+ self.tag, cssClass=self.cssClass, contents=value)
+ return value
+
+
+class ConversionParser(HTMLParser.HTMLParser):
+
+ def __init__(self):
+ HTMLParser.HTMLParser.__init__(self)
+ self.__buffer = []
+
+ def handle_data(self, data):
+ self.__buffer.append(data)
+
+ def handle_entityref(self, name):
+ self.__buffer.append("&%s;" % name)
+
+ def get_data(self):
+ return "".join(self.__buffer)
Added: zc.comment/trunk/src/zc/comment/comment.py
===================================================================
--- zc.comment/trunk/src/zc/comment/comment.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/comment.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,71 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""comment adapter
+
+$Id: comment.py 9472 2006-04-28 04:41:20Z gary $
+"""
+import datetime, UserList, pytz
+
+from persistent.list import PersistentList
+from zope import interface, component, event
+import zope.annotation.interfaces
+import zope.security.management
+import zope.publisher.interfaces
+
+from zc.comment import interfaces
+
+marker = 'zc.comment'
+
+class Comment(object):
+ interface.implements(interfaces.IComment)
+
+ def __init__(self, body):
+ if not isinstance(body, unicode):
+ raise ValueError("comment body must be unicode")
+ self.body = body
+ self.date = datetime.datetime.now(pytz.utc)
+ interaction = zope.security.management.getInteraction()
+ self.principal_ids = tuple(
+ [p.principal.id for p in interaction.participations
+ if zope.publisher.interfaces.IRequest.providedBy(p)])
+
+class Comments(object, UserList.UserList):
+ interface.implements(interfaces.IComments)
+ component.adapts(interfaces.ICommentable)
+ def pop(self):
+ raise AttributeError
+ pop = property(pop)
+ __setitem__ = __delitem__ = __setslice__ = __delslice__ = __iadd__ = pop
+ insert = append = remove = reverse = sort = extend = pop
+
+ def __init__(self, context):
+ self.__parent__ = context # so we can acquire grants
+ self.context = context
+ self.annotations = zope.annotation.interfaces.IAnnotations(
+ self.context)
+ self.data = self.annotations.get(marker)
+ if self.data is None:
+ self.data = []
+
+ def add(self, body):
+ if self.annotations.get(marker) is None:
+ self.data = self.annotations[marker] = PersistentList()
+ self.data.append(Comment(body))
+ event.notify(interfaces.CommentAdded(self.context, body))
+
+ def clear(self): # XXX no test
+ if marker in self.annotations:
+ del self.annotations[marker]
+
Added: zc.comment/trunk/src/zc/comment/comment.txt
===================================================================
--- zc.comment/trunk/src/zc/comment/comment.txt 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/comment.txt 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,114 @@
+========
+Comments
+========
+
+The comment package is a simple way to add comments to any IAnnotatable
+Zope content. The datetime and current principals are stamped on to the
+comment. The comment body is currently simply unicode text but intended to
+be html snippets ("rich text") at a later date.
+
+The inclusion of current principals requires an interaction, which is what we
+need to set up before we can use the system here. Below, we set up a dummy
+interaction with dummy participants, create some content that is
+IAttributeAnnotatable, and then finally show the system in use.
+
+ >>> import zope.security.management
+ >>> import zope.security.interfaces
+ >>> from zope import interface
+ >>> class DummyPrincipal(object):
+ ... interface.implements(zope.security.interfaces.IPrincipal)
+ ... def __init__(self, id, title, description):
+ ... self.id = id
+ ... self.title = title
+ ... self.description = description
+ ...
+ >>> alice = DummyPrincipal('alice', 'Alice Aal', 'first principal')
+ >>> betty = DummyPrincipal('betty', 'Betty Barnes', 'second principal')
+ >>> cathy = DummyPrincipal('cathy', 'Cathy Camero', 'third principal')
+ >>> class DummyParticipation(object):
+ ... interface.implements(zope.security.interfaces.IParticipation)
+ ... interaction = principal = None
+ ... def __init__(self, principal):
+ ... self.principal = principal
+ ...
+ >>> import zope.publisher.interfaces
+
+ >>> import zope.annotation.interfaces
+ >>> from zope.app.testing import ztapi
+ >>> import zope.annotation.attribute
+ >>> ztapi.provideAdapter( # hook up attribute annotations
+ ... (zope.annotation.interfaces.IAttributeAnnotatable,),
+ ... zope.annotation.interfaces.IAnnotations,
+ ... zope.annotation.attribute.AttributeAnnotations)
+
+ >>> from zc.comment import comment, interfaces
+ >>> ztapi.provideAdapter( # hook up our adapter
+ ... (zope.annotation.interfaces.IAnnotatable,),
+ ... interfaces.IComments,
+ ... comment.Comments)
+
+ >>> class DummyObject(object):
+ ... interface.implements(
+ ... zope.annotation.interfaces.IAttributeAnnotatable)
+ ...
+ >>> obj = DummyObject()
+
+ >>> a_p = DummyParticipation(alice)
+ >>> interface.directlyProvides(a_p, zope.publisher.interfaces.IRequest)
+ >>> zope.security.management.newInteraction(a_p)
+ >>> adapted = interfaces.IComments(obj)
+ >>> len(adapted)
+ 0
+ >>> import datetime, pytz
+ >>> before = datetime.datetime.now(pytz.utc)
+ >>> adapted.add(u"Foo! Bar!")
+ >>> after = datetime.datetime.now(pytz.utc)
+ >>> len(adapted)
+ 1
+ >>> adapted[0].body
+ u'Foo! Bar!'
+ >>> before <= adapted[0].date <= after
+ True
+ >>> adapted[0].principal_ids
+ ('alice',)
+ >>> zope.security.management.endInteraction()
+ >>> b_p = DummyParticipation(betty)
+ >>> interface.directlyProvides(b_p, zope.publisher.interfaces.IRequest)
+ >>> zope.security.management.newInteraction(b_p)
+ >>> adapted = interfaces.IComments(obj)
+ >>> before = datetime.datetime.now(pytz.utc)
+ >>> adapted.add(u"Shazam")
+ >>> after = datetime.datetime.now(pytz.utc)
+ >>> len(adapted)
+ 2
+ >>> adapted[1].body
+ u'Shazam'
+ >>> before <= adapted[1].date <= after
+ True
+ >>> adapted[1].principal_ids
+ ('betty',)
+ >>> zope.security.management.endInteraction()
+ >>> a_p = DummyParticipation(alice)
+ >>> b_p = DummyParticipation(betty)
+ >>> c_p = DummyParticipation(cathy)
+ >>> interface.directlyProvides(a_p, zope.publisher.interfaces.IRequest)
+ >>> interface.directlyProvides(b_p, zope.publisher.interfaces.IRequest)
+
+ >>> zope.security.management.newInteraction(a_p, b_p, c_p)
+ >>> adapted = interfaces.IComments(obj)
+ >>> before = datetime.datetime.now(pytz.utc)
+ >>> adapted.add(u"Boom.")
+ >>> after = datetime.datetime.now(pytz.utc)
+ >>> len(adapted)
+ 3
+ >>> adapted[2].body
+ u'Boom.'
+ >>> before <= adapted[2].date <= after
+ True
+ >>> adapted[2].principal_ids # cathy was not a request so not in list
+ ('alice', 'betty')
+ >>> adapted.add(42)
+ Traceback (most recent call last):
+ ...
+ ValueError: comment body must be unicode
+ >>> zope.security.management.endInteraction()
Property changes on: zc.comment/trunk/src/zc/comment/comment.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: zc.comment/trunk/src/zc/comment/configure.zcml
===================================================================
--- zc.comment/trunk/src/zc/comment/configure.zcml 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/configure.zcml 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,16 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope">
+
+ <class class=".comment.Comment">
+ <require permission="zope.Public"
+ interface=".interfaces.IComment"/>
+ </class>
+
+ <adapter
+ factory=".comment.Comments"
+ trusted="1" permission="zope.View"
+ />
+
+ <include package=".browser" />
+
+</configure>
Added: zc.comment/trunk/src/zc/comment/i18n.py
===================================================================
--- zc.comment/trunk/src/zc/comment/i18n.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/i18n.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""I18N support for comments.
+
+This defines a `MessageFactory` for the I18N domain for the comment
+application. This is normally used with this import::
+
+ from i18n import MessageFactory as _
+
+The factory is then used normally. Two examples::
+
+ text = _('some internationalized text')
+ text = _('helpful-descriptive-message-id', 'default text')
+"""
+__docformat__ = "reStructuredText"
+
+
+from zope import i18nmessageid
+
+MessageFactory = _ = i18nmessageid.MessageFactory("zc.comment")
Added: zc.comment/trunk/src/zc/comment/interfaces.py
===================================================================
--- zc.comment/trunk/src/zc/comment/interfaces.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/interfaces.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,82 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""interfaces for comment package
+
+$Id: interfaces.py 9472 2006-04-28 04:41:20Z gary $
+"""
+from zope import interface, schema
+from zope.interface.common.sequence import IReadSequence
+import zope.schema.interfaces
+
+import zope.annotation.interfaces
+import zope.lifecycleevent
+import zope.lifecycleevent.interfaces
+
+from zc.security.search import SimplePrincipalSource
+from i18n import _
+
+
+class ICommentText(schema.interfaces.IText):
+ """Type of rich text field used for comment text."""
+
+class CommentText(zope.schema.Text):
+ """Rich text field used for comment text."""
+
+ interface.implements(ICommentText)
+
+
+class IComment(interface.Interface):
+ date = schema.Datetime(
+ title=_("Creation Date"),
+ description=_("The date on which this comment was made"),
+ required=True, readonly=True)
+
+ principal_ids = schema.Tuple(
+ value_type=schema.Choice(
+ source=SimplePrincipalSource()),
+ title=_("Principals"),
+ description=_(
+ """The ids of the principals who made this comment"""),
+ required=True, readonly=True)
+
+ body = CommentText(
+ title=_("Comment Body"),
+ description=_("The comment text."),
+ required=False, readonly=True)
+
+class IComments(IReadSequence):
+
+ def add(body):
+ """add comment with given body.
+ """
+
+ def clear():
+ """Remove all comments.
+ """
+
+class ICommentable(zope.annotation.interfaces.IAnnotatable):
+ "Content that may be commented upon"
+
+class ICommentAdded(zope.lifecycleevent.interfaces.IObjectModifiedEvent):
+ """Somone added a comment to some content
+ """
+
+ comment = schema.Text(title=u"The comment entered")
+
+class CommentAdded(zope.lifecycleevent.ObjectModifiedEvent):
+
+ def __init__(self, object, comment):
+ zope.lifecycleevent.ObjectModifiedEvent.__init__(self, object)
+ self.comment = comment
Added: zc.comment/trunk/src/zc/comment/ntests.py
===================================================================
--- zc.comment/trunk/src/zc/comment/ntests.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/ntests.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+
+$Id: ntests.py 4280 2005-12-01 21:39:17Z benji $
+"""
+
+import persistent
+import pytz
+import re
+import unittest
+from zope.testing import doctest
+from zope import component, interface
+import zope.interface.common.idatetime
+import zope.testing.renormalizing
+import zc.table
+
+from zc.testlayer.ftesting import defineLayer
+
+
+class MyContent(persistent.Persistent):
+ x = 0
+
+ at zope.component.adapter(zope.publisher.interfaces.IRequest)
+ at zope.interface.implementer(zope.interface.common.idatetime.ITZInfo)
+def requestToTZInfo(request):
+ return pytz.timezone('US/Eastern')
+
+def formatterFactory(*args, **kw):
+ return zc.table.table.FormFullFormatter(*args, **kw)
+interface.directlyProvides(formatterFactory,
+ zc.table.interfaces.IFormatterFactory)
+
+defineLayer('CommentLayer')
+
+def test_suite():
+ checker = zope.testing.renormalizing.RENormalizing([
+ (re.compile(r'\d\d\d\d \d\d? \d\d?\s+\d\d:\d\d:\d\d( [+-]\d+)?'),
+ 'YYYY MM DD HH:MM:SS'),
+ ])
+ suite = doctest.DocFileSuite('browser/comments.txt', checker=checker,
+ optionflags=doctest.NORMALIZE_WHITESPACE |
+ doctest.ELLIPSIS)
+ suite.layer = CommentLayer
+ return suite
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
+
Added: zc.comment/trunk/src/zc/comment/test.zcml
===================================================================
--- zc.comment/trunk/src/zc/comment/test.zcml 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/test.zcml 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,53 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ package="zc.comment"
+ i18n_domain="zc.comment"
+ >
+
+<include package="zope.app" />
+
+<!-- uncomment to run interactively and use the test recorder
+<include package="zope.app.server" />
+
+<configure package="zope.browsertestrecorder">
+<browser:resourceDirectory
+ name="recorder"
+ directory="html"
+ layer="default"
+ />
+</configure>
+
+-->
+
+<securityPolicy
+ component="zope.security.simplepolicies.PermissiveSecurityPolicy" />
+
+<include package="zc.resourcelibrary" file="meta.zcml" />
+<include package="zc.resourcelibrary" />
+<include package="zope.formlib" />
+<include package="zc.comment" />
+<include package="zc.table" />
+
+<utility component=".ntests.formatterFactory" />
+<adapter factory=".ntests.requestToTZInfo" />
+
+<class class=".ntests.MyContent">
+ <implements
+ interface="zope.annotation.interfaces.IAttributeAnnotatable"
+ />
+ <implements interface="zc.comment.interfaces.ICommentable" />
+</class>
+
+<browser:addMenuItem
+ class=".ntests.MyContent"
+ title="Content"
+ permission="zope.ManageContent"
+ />
+
+<unauthenticatedPrincipal id="zope.anybody" title="Unauthenticated User" />
+
+<!-- Load a "default" i18n domain for debugging purposes
+ production sites shouldn't do this -->
+<include package="zope.app.i18n.tests" />
+
+</configure>
Added: zc.comment/trunk/src/zc/comment/tests.py
===================================================================
--- zc.comment/trunk/src/zc/comment/tests.py 2006-08-15 20:50:21 UTC (rev 69536)
+++ zc.comment/trunk/src/zc/comment/tests.py 2006-08-15 20:54:02 UTC (rev 69537)
@@ -0,0 +1,29 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Visible Source
+# License, Version 1.0 (ZVSL). A copy of the ZVSL should accompany this
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""listcontainer module test runner
+
+$Id: tests.py 678 2005-02-22 21:49:28Z gary $
+"""
+
+import unittest
+from zope.testing import doctest
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocFileSuite(
+ 'comment.txt'),))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
More information about the Checkins
mailing list