[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 &lt; &amp; &gt;")
+
+
+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" &lt; &amp; &gt; ")
+        self.verifyResult(self._widget(),
+                          ['<textarea', 'class="zc-comment-text"',
+                           'line 1\n\nline 3', '&lt; &amp; &gt; </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