[Zope3-checkins] SVN: messageboard/trunk/step Initial checkin of steps.

Stephan Richter srichter at cosmos.phy.tufts.edu
Sun Aug 15 15:26:49 EDT 2004


Log message for revision 27140:
  Initial checkin of steps.
  


Changed:
  A   messageboard/trunk/step09/
  A   messageboard/trunk/step09/__init__.py
  A   messageboard/trunk/step09/browser/
  A   messageboard/trunk/step09/browser/__init__.py
  A   messageboard/trunk/step09/browser/configure.zcml
  A   messageboard/trunk/step09/browser/details.pt
  A   messageboard/trunk/step09/browser/ftests/
  A   messageboard/trunk/step09/browser/ftests/__init__.py
  A   messageboard/trunk/step09/browser/ftests/test_message.py
  A   messageboard/trunk/step09/browser/help/
  A   messageboard/trunk/step09/browser/help/board_review.rst
  A   messageboard/trunk/step09/browser/help/msg_edit.rst
  A   messageboard/trunk/step09/browser/help/package_intro.rst
  A   messageboard/trunk/step09/browser/message.png
  A   messageboard/trunk/step09/browser/message.py
  A   messageboard/trunk/step09/browser/messageboard.png
  A   messageboard/trunk/step09/browser/messageboard.py
  A   messageboard/trunk/step09/browser/messageboard_add.pt
  A   messageboard/trunk/step09/browser/review.pt
  A   messageboard/trunk/step09/browser/subscriptions.pt
  A   messageboard/trunk/step09/browser/subthread.pt
  A   messageboard/trunk/step09/browser/tests/
  A   messageboard/trunk/step09/browser/tests/__init__.py
  A   messageboard/trunk/step09/browser/tests/test_widgets.py
  A   messageboard/trunk/step09/browser/thread.pt
  A   messageboard/trunk/step09/browser/thread.py
  A   messageboard/trunk/step09/browser/widgets.py
  A   messageboard/trunk/step09/configure.zcml
  A   messageboard/trunk/step09/fields.py
  A   messageboard/trunk/step09/interfaces.py
  A   messageboard/trunk/step09/locales/
  A   messageboard/trunk/step09/locales/de/
  A   messageboard/trunk/step09/locales/de/LC_MESSAGES/
  A   messageboard/trunk/step09/locales/de/LC_MESSAGES/messageboard.mo
  A   messageboard/trunk/step09/locales/de/LC_MESSAGES/messageboard.po
  A   messageboard/trunk/step09/locales/en/
  A   messageboard/trunk/step09/locales/en/LC_MESSAGES/
  A   messageboard/trunk/step09/locales/en/LC_MESSAGES/messageboard.mo
  A   messageboard/trunk/step09/locales/en/LC_MESSAGES/messageboard.po
  A   messageboard/trunk/step09/locales/messageboard.pot
  A   messageboard/trunk/step09/message.py
  A   messageboard/trunk/step09/messageboard.py
  A   messageboard/trunk/step09/security.zcml
  A   messageboard/trunk/step09/tests/
  A   messageboard/trunk/step09/tests/__init__.py
  A   messageboard/trunk/step09/tests/test_fields.py
  A   messageboard/trunk/step09/tests/test_message.py
  A   messageboard/trunk/step09/tests/test_messageboard.py
  A   messageboard/trunk/step09/workflow.xml
  A   messageboard/trunk/step10/
  A   messageboard/trunk/step10/__init__.py
  A   messageboard/trunk/step10/browser/
  A   messageboard/trunk/step10/browser/__init__.py
  A   messageboard/trunk/step10/browser/configure.zcml
  A   messageboard/trunk/step10/browser/details.pt
  A   messageboard/trunk/step10/browser/ftests/
  A   messageboard/trunk/step10/browser/ftests/__init__.py
  A   messageboard/trunk/step10/browser/ftests/test_message.py
  A   messageboard/trunk/step10/browser/help/
  A   messageboard/trunk/step10/browser/help/board_review.rst
  A   messageboard/trunk/step10/browser/help/msg_edit.rst
  A   messageboard/trunk/step10/browser/help/package_intro.rst
  A   messageboard/trunk/step10/browser/message.png
  A   messageboard/trunk/step10/browser/message.py
  A   messageboard/trunk/step10/browser/messageboard.png
  A   messageboard/trunk/step10/browser/messageboard.py
  A   messageboard/trunk/step10/browser/messageboard_add.pt
  A   messageboard/trunk/step10/browser/review.pt
  A   messageboard/trunk/step10/browser/subscriptions.pt
  A   messageboard/trunk/step10/browser/subthread.pt
  A   messageboard/trunk/step10/browser/tests/
  A   messageboard/trunk/step10/browser/tests/__init__.py
  A   messageboard/trunk/step10/browser/tests/test_widgets.py
  A   messageboard/trunk/step10/browser/thread.pt
  A   messageboard/trunk/step10/browser/thread.py
  A   messageboard/trunk/step10/browser/widgets.py
  A   messageboard/trunk/step10/configure.zcml
  A   messageboard/trunk/step10/fields.py
  A   messageboard/trunk/step10/filerepresentation.py
  A   messageboard/trunk/step10/interfaces.py
  A   messageboard/trunk/step10/locales/
  A   messageboard/trunk/step10/locales/de/
  A   messageboard/trunk/step10/locales/de/LC_MESSAGES/
  A   messageboard/trunk/step10/locales/de/LC_MESSAGES/messageboard.mo
  A   messageboard/trunk/step10/locales/de/LC_MESSAGES/messageboard.po
  A   messageboard/trunk/step10/locales/en/
  A   messageboard/trunk/step10/locales/en/LC_MESSAGES/
  A   messageboard/trunk/step10/locales/en/LC_MESSAGES/messageboard.mo
  A   messageboard/trunk/step10/locales/en/LC_MESSAGES/messageboard.po
  A   messageboard/trunk/step10/locales/messageboard.pot
  A   messageboard/trunk/step10/message.py
  A   messageboard/trunk/step10/messageboard.py
  A   messageboard/trunk/step10/security.zcml
  A   messageboard/trunk/step10/tests/
  A   messageboard/trunk/step10/tests/__init__.py
  A   messageboard/trunk/step10/tests/test_fields.py
  A   messageboard/trunk/step10/tests/test_filerepresentation.py
  A   messageboard/trunk/step10/tests/test_message.py
  A   messageboard/trunk/step10/tests/test_messageboard.py
  A   messageboard/trunk/step10/workflow.xml
  A   messageboard/trunk/step11/
  A   messageboard/trunk/step11/__init__.py
  A   messageboard/trunk/step11/browser/
  A   messageboard/trunk/step11/browser/__init__.py
  A   messageboard/trunk/step11/browser/configure.zcml
  A   messageboard/trunk/step11/browser/details.pt
  A   messageboard/trunk/step11/browser/ftests/
  A   messageboard/trunk/step11/browser/ftests/__init__.py
  A   messageboard/trunk/step11/browser/ftests/test_message.py
  A   messageboard/trunk/step11/browser/help/
  A   messageboard/trunk/step11/browser/help/board_review.rst
  A   messageboard/trunk/step11/browser/help/msg_edit.rst
  A   messageboard/trunk/step11/browser/help/package_intro.rst
  A   messageboard/trunk/step11/browser/message.png
  A   messageboard/trunk/step11/browser/message.py
  A   messageboard/trunk/step11/browser/messageboard.png
  A   messageboard/trunk/step11/browser/messageboard.py
  A   messageboard/trunk/step11/browser/messageboard_add.pt
  A   messageboard/trunk/step11/browser/review.pt
  A   messageboard/trunk/step11/browser/subscriptions.pt
  A   messageboard/trunk/step11/browser/subthread.pt
  A   messageboard/trunk/step11/browser/tests/
  A   messageboard/trunk/step11/browser/tests/__init__.py
  A   messageboard/trunk/step11/browser/tests/test_widgets.py
  A   messageboard/trunk/step11/browser/thread.pt
  A   messageboard/trunk/step11/browser/thread.py
  A   messageboard/trunk/step11/browser/widgets.py
  A   messageboard/trunk/step11/configure.zcml
  A   messageboard/trunk/step11/fields.py
  A   messageboard/trunk/step11/filerepresentation.py
  A   messageboard/trunk/step11/interfaces.py
  A   messageboard/trunk/step11/locales/
  A   messageboard/trunk/step11/locales/de/
  A   messageboard/trunk/step11/locales/de/LC_MESSAGES/
  A   messageboard/trunk/step11/locales/de/LC_MESSAGES/messageboard.mo
  A   messageboard/trunk/step11/locales/de/LC_MESSAGES/messageboard.po
  A   messageboard/trunk/step11/locales/en/
  A   messageboard/trunk/step11/locales/en/LC_MESSAGES/
  A   messageboard/trunk/step11/locales/en/LC_MESSAGES/messageboard.mo
  A   messageboard/trunk/step11/locales/en/LC_MESSAGES/messageboard.po
  A   messageboard/trunk/step11/locales/messageboard.pot
  A   messageboard/trunk/step11/message.py
  A   messageboard/trunk/step11/messageboard.py
  A   messageboard/trunk/step11/security.zcml
  A   messageboard/trunk/step11/tests/
  A   messageboard/trunk/step11/tests/__init__.py
  A   messageboard/trunk/step11/tests/test_fields.py
  A   messageboard/trunk/step11/tests/test_filerepresentation.py
  A   messageboard/trunk/step11/tests/test_message.py
  A   messageboard/trunk/step11/tests/test_messageboard.py
  A   messageboard/trunk/step11/tests/test_xmlrpc.py
  A   messageboard/trunk/step11/workflow.xml
  A   messageboard/trunk/step11/xmlrpc.py
  A   messageboard/trunk/step11/xmlrpc_client.py
  A   messageboard/trunk/step12/
  A   messageboard/trunk/step12/__init__.py
  A   messageboard/trunk/step12/browser/
  A   messageboard/trunk/step12/browser/__init__.py
  A   messageboard/trunk/step12/browser/configure.zcml
  A   messageboard/trunk/step12/browser/details.pt
  A   messageboard/trunk/step12/browser/ftests/
  A   messageboard/trunk/step12/browser/ftests/__init__.py
  A   messageboard/trunk/step12/browser/ftests/test_message.py
  A   messageboard/trunk/step12/browser/help/
  A   messageboard/trunk/step12/browser/help/board_review.rst
  A   messageboard/trunk/step12/browser/help/msg_edit.rst
  A   messageboard/trunk/step12/browser/help/package_intro.rst
  A   messageboard/trunk/step12/browser/message.png
  A   messageboard/trunk/step12/browser/message.py
  A   messageboard/trunk/step12/browser/messageboard.png
  A   messageboard/trunk/step12/browser/messageboard.py
  A   messageboard/trunk/step12/browser/messageboard_add.pt
  A   messageboard/trunk/step12/browser/review.pt
  A   messageboard/trunk/step12/browser/skin/
  A   messageboard/trunk/step12/browser/skin/__init__.py
  A   messageboard/trunk/step12/browser/skin/board.css
  A   messageboard/trunk/step12/browser/skin/board_intro.pt
  A   messageboard/trunk/step12/browser/skin/board_posts.pt
  A   messageboard/trunk/step12/browser/skin/board_review.pt
  A   messageboard/trunk/step12/browser/skin/configure.zcml
  A   messageboard/trunk/step12/browser/skin/dialog_macros.pt
  A   messageboard/trunk/step12/browser/skin/logo.png
  A   messageboard/trunk/step12/browser/skin/msg_details.pt
  A   messageboard/trunk/step12/browser/skin/template.pt
  A   messageboard/trunk/step12/browser/skin/views.py
  A   messageboard/trunk/step12/browser/subscriptions.pt
  A   messageboard/trunk/step12/browser/subthread.pt
  A   messageboard/trunk/step12/browser/tests/
  A   messageboard/trunk/step12/browser/tests/__init__.py
  A   messageboard/trunk/step12/browser/tests/test_widgets.py
  A   messageboard/trunk/step12/browser/thread.pt
  A   messageboard/trunk/step12/browser/thread.py
  A   messageboard/trunk/step12/browser/widgets.py
  A   messageboard/trunk/step12/configure.zcml
  A   messageboard/trunk/step12/fields.py
  A   messageboard/trunk/step12/filerepresentation.py
  A   messageboard/trunk/step12/interfaces.py
  A   messageboard/trunk/step12/locales/
  A   messageboard/trunk/step12/locales/de/
  A   messageboard/trunk/step12/locales/de/LC_MESSAGES/
  A   messageboard/trunk/step12/locales/de/LC_MESSAGES/messageboard.mo
  A   messageboard/trunk/step12/locales/de/LC_MESSAGES/messageboard.po
  A   messageboard/trunk/step12/locales/en/
  A   messageboard/trunk/step12/locales/en/LC_MESSAGES/
  A   messageboard/trunk/step12/locales/en/LC_MESSAGES/messageboard.mo
  A   messageboard/trunk/step12/locales/en/LC_MESSAGES/messageboard.po
  A   messageboard/trunk/step12/locales/messageboard.pot
  A   messageboard/trunk/step12/message.py
  A   messageboard/trunk/step12/messageboard.py
  A   messageboard/trunk/step12/security.zcml
  A   messageboard/trunk/step12/tests/
  A   messageboard/trunk/step12/tests/__init__.py
  A   messageboard/trunk/step12/tests/test_fields.py
  A   messageboard/trunk/step12/tests/test_filerepresentation.py
  A   messageboard/trunk/step12/tests/test_message.py
  A   messageboard/trunk/step12/tests/test_messageboard.py
  A   messageboard/trunk/step12/tests/test_xmlrpc.py
  A   messageboard/trunk/step12/workflow.xml
  A   messageboard/trunk/step12/xmlrpc.py
  A   messageboard/trunk/step12/xmlrpc_client.py


-=-
Added: messageboard/trunk/step09/__init__.py
===================================================================
--- messageboard/trunk/step09/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step09/browser/__init__.py
===================================================================
--- messageboard/trunk/step09/browser/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step09/browser/configure.zcml
===================================================================
--- messageboard/trunk/step09/browser/configure.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/configure.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,173 @@
+<configure
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:help="http://namespaces.zope.org/help"
+    xmlns:zope="http://namespaces.zope.org/zope">
+
+  <help:register
+      id="messageboard"
+      title="Message Board Help"
+      parent="ui"
+      for="book.messageboard.interfaces.IMessageBoard"
+      doc_path="./help/package_intro.rst"/>
+
+  <addform
+      label="Add Message Board"
+      name="AddMessageBoard.html"
+      template="messageboard_add.pt"
+      class=".messageboard.AddMessageBoard"
+      schema="book.messageboard.interfaces.IMessageBoard"
+      content_factory="book.messageboard.messageboard.MessageBoard"
+      fields="description"
+      permission="zope.ManageContent"
+      />
+
+  <addMenuItem
+      class="book.messageboard.messageboard.MessageBoard"
+      title="Message Board"
+      description="A Message Board"
+      permission="zope.ManageContent"
+      view="AddMessageBoard.html" 
+      />
+
+  <editform
+      schema="book.messageboard.interfaces.IMessageBoard"
+      for="book.messageboard.interfaces.IMessageBoard"
+      label="Change Message Board"
+      name="edit.html"
+      permission="zope.ManageContent"
+      menu="zmi_views" title="Edit" 
+      />
+
+  <containerViews
+      for="book.messageboard.interfaces.IMessageBoard"
+      index="book.messageboard.View"
+      contents="book.messageboard.Edit"
+      add="book.messageboard.Add"
+      />
+
+  <page
+      name="thread.html"
+      for="book.messageboard.interfaces.IMessageBoard"
+      class=".thread.Thread"
+      template="thread.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Thread"/>
+
+  <defaultView
+      for="book.messageboard.interfaces.IMessageBoard"
+      name="thread.html"/>
+
+  <icon
+      name="zmi_icon"
+      for="book.messageboard.interfaces.IMessageBoard"
+      file="messageboard.png" />
+
+  <page
+      name="review.html"
+      for="book.messageboard.interfaces.IMessageBoard"
+      class=".messageboard.ReviewMessages"
+      permission="book.messageboard.PublishContent"
+      template="review.pt"
+      menu="zmi_views" title="Review Messages"/>
+
+  <help:register
+      id="board.review"
+      title="Publication Review"
+      parent="ui/messageboard"
+      for="book.messageboard.interfaces.IMessageBoard"
+      view="review.html"
+      doc_path="./help/board_review.rst"/>
+
+  <addform
+      label="Add Message"
+      name="AddMessage.html"
+      schema="book.messageboard.interfaces.IMessage"
+      content_factory="book.messageboard.message.Message"
+      fields="title body"
+      permission="book.messageboard.Add"
+      />
+
+  <addMenuItem
+      class="book.messageboard.message.Message"
+      title="Message"
+      description="A Message"
+      permission="book.messageboard.Add"
+      view="AddMessage.html" 
+      />
+
+  <editform
+      schema="book.messageboard.interfaces.IMessage"
+      for="book.messageboard.interfaces.IMessage"
+      label="Change Message"
+      fields="title body"
+      name="edit.html"
+      permission="book.messageboard.Edit"
+      menu="zmi_views" title="Edit" 
+      />
+
+  <help:register
+      id="message.edit"
+      title="Change Message"
+      parent="ui/messageboard"
+      for="book.messageboard.interfaces.IMessage"
+      view="edit.html"
+      doc_path="./help/msg_edit.rst"/>
+
+  <containerViews
+      for="book.messageboard.interfaces.IMessage"
+      index="book.messageboard.View"
+      contents="book.messageboard.Edit"
+      add="book.messageboard.Add"
+      />
+
+  <page
+      name="details.html"
+      for="book.messageboard.interfaces.IMessage"
+      class=".message.MessageDetails"
+      template="details.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Preview"/>
+
+  <defaultView
+      for="book.messageboard.interfaces.IMessage"
+      name="details.html"/>
+
+  <page
+      name="thread.html"
+      for="book.messageboard.interfaces.IMessage"
+      class=".thread.Thread"
+      template="thread.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Thread"/>
+
+  <icon
+      name="zmi_icon"
+      for="book.messageboard.interfaces.IMessage"
+      file="message.png" />
+
+
+  <zope:view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="book.messageboard.interfaces.IHTML"
+      provides="zope.app.form.interfaces.IInputWidget"
+      factory=".widgets.HTMLSourceWidget"
+      permission="zope.Public"
+      />
+
+  <pages
+      for="book.messageboard.interfaces.IMessage"
+      class=".message.MailSubscriptions"
+      permission="book.messageboard.Edit"
+      >
+    <page 
+        name="subscriptions.html" 
+        template="subscriptions.pt"
+        menu="zmi_views" title="Subscriptions" 
+        />
+    <page 
+       name="changeSubscriptions.html" 
+       attribute="change" 
+       />
+  </pages>
+
+</configure>

Added: messageboard/trunk/step09/browser/details.pt
===================================================================
--- messageboard/trunk/step09/browser/details.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/details.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Message Details</h1>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Title</div>
+            <div class="field" tal:content="context/title" />
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Author</div>
+            <div class="field" tal:content="view/author"/>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Date/Time</div>
+            <div class="field" tal:content="view/modified"/>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Parent</div>
+            <div class="field" tal:define="info view/parent_info">
+              <a href="../" 
+                  tal:condition="info"
+                  tal:content="info/title" />
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Body</div>
+            <div class="field" tal:content="structure context/body"/>
+        </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step09/browser/ftests/__init__.py
===================================================================
--- messageboard/trunk/step09/browser/ftests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/ftests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step09/browser/ftests/test_message.py
===================================================================
--- messageboard/trunk/step09/browser/ftests/test_message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/ftests/test_message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Functional Tests
+
+$Id$
+"""
+import unittest
+from zope.app.tests.functional import BrowserTestCase
+
+class MessageTest(BrowserTestCase):
+
+    def testAddMessage(self):
+        response = self.publish(
+            '/+/AddMessageBoard.html=board',
+            basic='mgr:mgrpw',
+            form={'field.description': u'Message Board',
+                  'UPDATE_SUBMIT': 'Add'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/@@contents.html')
+        response = self.publish(
+            '/board/+/AddMessage.html=msg1',
+            basic='mgr:mgrpw',
+            form={'field.title': u'Message 1',
+                  'field.body': u'Body',
+                  'UPDATE_SUBMIT': 'Add'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/board/@@contents.html')
+       
+    def testMessageDetails(self):
+        self.testAddMessage()
+        response = self.publish('/board/msg1/@@details.html',
+                                basic='mgr:mgrpw')
+        body = response.getBody()
+        self.checkForBrokenLinks(body, '/board/msg1/@@details.html',
+                                 basic='mgr:mgrpw')
+        
+        self.assert_(body.find('Message Details') > 0)
+        self.assert_(body.find('Message 1') > 0)
+        self.assert_(body.find('Body') > 0)
+        
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MessageTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step09/browser/help/board_review.rst
===================================================================
--- messageboard/trunk/step09/browser/help/board_review.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/help/board_review.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,3 @@
+This view lists all messages in the board that are pending for
+publication. Each listed method is a link that brings you to the
+message's "Workflow" view where you can initiate a transition.

Added: messageboard/trunk/step09/browser/help/msg_edit.rst
===================================================================
--- messageboard/trunk/step09/browser/help/msg_edit.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/help/msg_edit.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,9 @@
+  This screen allows you to edit the data (i.e. the subject and body) of
+  the Message object.
+  
+  title - A one line unicode text string that briefly describes the
+          purpose/subject of the message.
+  
+  body - A multiple line unicode text string that is the actual content of 
+         the message. It is accepting HTML, but restricts the user to a 
+         couple of selected tags. Feel free to type anything you wish.

Added: messageboard/trunk/step09/browser/help/package_intro.rst
===================================================================
--- messageboard/trunk/step09/browser/help/package_intro.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/help/package_intro.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,6 @@
+==========================
+Message Board Demo Package
+==========================
+
+This package demos various features of the Zope 3 Framework. If you 
+have questions or concerns, please let me know. 

Added: messageboard/trunk/step09/browser/message.png
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step09/browser/message.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step09/browser/message.py
===================================================================
--- messageboard/trunk/step09/browser/message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,70 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser Views for IMessage
+
+$Id: message.py,v 1.3 2003/12/13 17:24:36 srichter Exp $
+"""
+from zope.i18n import MessageIDFactory
+
+from zope.app import zapi
+from zope.app.dublincore.interfaces import ICMFDublinCore
+
+from book.messageboard.interfaces import IMessage
+from book.messageboard.interfaces import IMailSubscriptions
+
+_ = MessageIDFactory('messageboard')
+
+class MessageDetails:
+
+    def author(self):
+        """Get user who last modified the Message."""
+        creators = ICMFDublinCore(self.context).creators
+        if not creators:
+            return _('unknown')
+        return creators[0]
+
+    def modified(self):
+        """Get last modification date."""
+        date = ICMFDublinCore(self.context).modified
+        if date is None:
+            date = ICMFDublinCore(self.context).created
+        if date is None:
+            return ''
+        formatter = self.request.locale.dates.getFormatter('dateTime', 'short')
+        return formatter.format(date)
+
+    def parent_info(self):
+        """Get the parent of the message"""
+        parent = zapi.getParent(self.context)
+        if not IMessage.providedBy(parent):
+            return None
+        return {'name': zapi.name(parent), 'title': parent.title}
+
+
+class MailSubscriptions:
+
+    def subscriptions(self):
+        return IMailSubscriptions(self.context).getSubscriptions()
+
+    def change(self):
+        if 'ADD' in self.request:
+            emails = self.request['emails'].split('\n')
+            IMailSubscriptions(self.context).addSubscriptions(emails)
+        elif 'REMOVE' in self.request:
+            emails = self.request['remails']
+            if isinstance(emails, (str, unicode)):
+                emails = [emails]
+            IMailSubscriptions(self.context).removeSubscriptions(emails)
+
+        self.request.response.redirect('./@@subscriptions.html')

Added: messageboard/trunk/step09/browser/messageboard.png
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step09/browser/messageboard.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step09/browser/messageboard.py
===================================================================
--- messageboard/trunk/step09/browser/messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,126 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser Views for IMessageBoard
+
+$Id$
+"""
+import os
+from zope.proxy import removeAllProxies
+
+from zope.app import zapi
+from zope.app.registration.interfaces import ActiveStatus
+from zope.app.site.interfaces import ISite
+from zope.app.site.service import SiteManager, ServiceRegistration
+from zope.app.utility.utility import LocalUtilityService, UtilityRegistration
+from zope.app.workflow.interfaces import IProcessDefinitionImportHandler
+from zope.app.workflow.interfaces import IProcessInstanceContainer
+from zope.app.workflow.stateful.contentworkflow import ContentWorkflowsManager
+from zope.app.workflow.stateful.definition import StatefulProcessDefinition
+from zope.app.workflow.stateful.interfaces import IContentWorkflowsManager
+from zope.app.workflow.stateful.interfaces import IStatefulProcessDefinition
+
+import book.messageboard
+from book.messageboard.interfaces import IMessage
+
+
+class AddMessageBoard(object):
+    """Add a message board."""
+  
+    def createAndAdd(self, data):
+        content = super(AddMessageBoard, self).createAndAdd(data)
+  
+        if self.request.get('workflow'):
+            folder = removeAllProxies(zapi.getParent(content))
+            if not ISite.providedBy(folder):
+                sm = SiteManager(folder)
+                folder.setSiteManager(sm)
+            default = zapi.traverse(folder.getSiteManager(), 'default')
+ 
+            # Create Local Utility Service
+            default['Utilities'] = LocalUtilityService()
+            rm = default.getRegistrationManager()
+            registration = ServiceRegistration(zapi.servicenames.Utilities,
+                                               'Utilities', rm)
+            key = rm.addRegistration(registration)
+            zapi.traverse(rm, key).status = ActiveStatus
+
+            # Create the process definition
+            default['publish-message'] = StatefulProcessDefinition()
+            pd_path = zapi.getPath(default['publish-message'])
+            registration = UtilityRegistration(
+                'publish-message', IStatefulProcessDefinition, pd_path)
+            pd_id = rm.addRegistration(registration)
+            zapi.traverse(rm, pd_id).status = ActiveStatus
+
+            import_util = IProcessDefinitionImportHandler(
+                default['publish-message'])
+            
+            xml = os.path.join(
+                os.path.dirname(book.messageboard.__file__), 'workflow.xml')
+                
+            import_util.doImport(open(xml, mode='r').read())
+  
+            # Create Content Workflows Manager
+            default['ContentWorkflows'] = ContentWorkflowsManager()
+            cm_path = zapi.getPath(default['ContentWorkflows'])
+            registration = UtilityRegistration(
+                'wfcontentmgr', IContentWorkflowsManager, cm_path)
+            cm_id = rm.addRegistration(registration)
+            zapi.traverse(rm, cm_id).status = ActiveStatus
+
+            contentmgr = default['ContentWorkflows']
+            contentmgr.register(IMessage, 'publish-message')
+
+        return content
+
+
+class ReviewMessages:
+    """Workflow: Review all pending messages"""
+
+    def getPendingMessages(self, pmsg):
+        """Get all pending messages recursively."""
+        msgs = []
+        for name, msg in pmsg.items():
+            if IMessage.providedBy(msg):
+                if hasMessageStatus(msg, 'pending'):
+                    msgs.append(msg)
+                msgs += self.getPendingMessages(msg)
+        return msgs
+
+    def getPendingMessagesInfo(self):
+        """Get all the display info for pending messages"""
+        msg_infos = []
+        for msg in self.getPendingMessages(self.context):
+            info = {}
+            info['title'] = msg.title
+            info['url'] = zapi.getView(
+                msg, 'absolute_url', self.request)() + '/@@workflows.html'
+            msg_infos.append(info)
+        return msg_infos
+
+
+def hasMessageStatus(msg, status, workflow='publish-message'):
+    """Check whether a particular message matches a given status"""
+    adapter = IProcessInstanceContainer(msg)
+    if adapter:
+        # No workflow is defined, so the message is always shown.
+        if not adapter.keys():
+            return True
+        for item in adapter.values():
+            if item.processDefinitionName != workflow:
+                continue
+            if item.status == status:
+                return True
+
+    return False

Added: messageboard/trunk/step09/browser/messageboard_add.pt
===================================================================
--- messageboard/trunk/step09/browser/messageboard_add.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/messageboard_add.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,25 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <div metal:use-macro="views/form_macros/addform">
+
+        <div metal:fill-slot="extra_bottom" class="row">
+          <div class="field">
+            <h3><input type="checkbox" name="workflow:int" 
+                    value="1" checked=""/>
+              <span i18n:translate="">Create Workflow</span>
+            </h3>
+            <span i18n:translate="">Without the workflow you will 
+              not be able to review messages before they are 
+              published. Note that you can always modify the 
+              messageboard workflow later to make all transitions 
+              automatically.</span>
+          </div>
+        </div>
+
+      </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step09/browser/review.pt
===================================================================
--- messageboard/trunk/step09/browser/review.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/review.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,16 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Pending Messages</h1>
+
+      <div class="row" tal:repeat="msg view/getPendingMessagesInfo">
+        <div class="field">
+          <a href="" tal:attributes="href msg/url"
+              tal:content="msg/title" />
+        </div>
+      </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step09/browser/subscriptions.pt
===================================================================
--- messageboard/trunk/step09/browser/subscriptions.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/subscriptions.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,43 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <form action="changeSubscriptions.html" method="post">
+
+        <div class="row">
+            <div class="label" 
+                i18n:translate="">Current Subscriptions</div>
+            <div class="field">
+           <div tal:repeat="email view/subscriptions">
+                <input type="checkbox" name="remails:list" 
+                       value="" tal:attributes="value email">
+                <div tal:replace="email">zope3 at zope3.org</div>
+              </div>
+              <input type="submit" name="REMOVE" value="Remove" 
+                   i18n:attributes="value remove-button">
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">
+              Enter new Users (separate by 'Return')
+            </div>
+            <div class="field">
+           <textarea name="emails" cols="40" rows="10"></textarea>
+            </div>
+        </div>
+
+             <div class="row">
+               <div class="controls">
+                 <input type="submit" value="Refresh" 
+                i18n:attributes="value refresh-button" />
+                 <input type="submit" name="ADD" value="Add" 
+                     i18n:attributes="value add-button" />
+               </div>
+             </div>
+
+      </form>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step09/browser/subthread.pt
===================================================================
--- messageboard/trunk/step09/browser/subthread.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/subthread.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,8 @@
+<ul>
+  <li tal:repeat="item view/listContentInfo">
+    <a href="" 
+        tal:attributes="href item/url"
+        tal:content="item/title">Message 1</a>
+    <div tal:replace="structure item/thread"/>
+  </li>
+</ul>
\ No newline at end of file

Added: messageboard/trunk/step09/browser/tests/__init__.py
===================================================================
--- messageboard/trunk/step09/browser/tests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/tests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step09/browser/tests/test_widgets.py
===================================================================
--- messageboard/trunk/step09/browser/tests/test_widgets.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/tests/test_widgets.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,76 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""HTMLSourceWidget Tests
+
+$Id: test_widgets.py,v 1.1 2003/06/10 14:40:44 srichter Exp $
+"""
+import unittest
+from zope.app.form.browser.tests.test_textareawidget import TextAreaWidgetTest
+from book.messageboard.browser.widgets import HTMLSourceWidget
+from book.messageboard.fields import HTML
+
+class HTMLSourceWidgetTest(TextAreaWidgetTest):
+
+    _FieldFactory = HTML
+    _WidgetFactory = HTMLSourceWidget
+
+
+    def test_AllowedTagsConvert(self):
+        widget = self._widget
+        widget.context.allowed_tags=('h1','pre')
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1>Blah</h1>')) 
+        self.assertEqual(u'<pre>Blah</pre>',
+                         widget._toFieldValue(u'<pre>Blah</pre>') )
+        self.assertEqual(u'<h1><pre>Blah</pre></h1>',
+                         widget._toFieldValue(u'<h1><pre>Blah</pre></h1>')) 
+        self.assertEqual(u'<h1 attr=".">Blah</h1>',
+                         widget._toFieldValue(u'<h1 attr=".">Blah</h1>')) 
+
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2>Blah</h2>')) 
+        self.assertEqual(u'<pre>Blah</pre>',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2 a="b">Blah</h2>')) 
+
+
+    def test_ForbiddenTagsConvert(self):
+        widget = self._widget
+        widget.context.forbidden_tags=('h2','pre')
+
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1>Blah</h1>')) 
+        self.assertEqual(u'<h1 a="b">Blah</h1>',
+                         widget._toFieldValue(u'<h1 a="b">Blah</h1>')) 
+
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2>Blah</h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<pre>Blah</pre>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1><pre>Blah</pre></h1>')) 
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(HTMLSourceWidgetTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step09/browser/thread.pt
===================================================================
--- messageboard/trunk/step09/browser/thread.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/thread.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,11 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Discussion Thread</h1>
+
+      <div tal:replace="structure view/subthread" />
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step09/browser/thread.py
===================================================================
--- messageboard/trunk/step09/browser/thread.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/thread.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,45 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser View for the sub-thread of a Message or MessageBoard
+
+$Id$
+"""
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+
+from book.messageboard.interfaces import IMessage
+from messageboard import hasMessageStatus
+
+class Thread:
+
+    def __init__(self, context, request, base_url=''):
+        self.context = context
+        self.request = request
+        self.base_url = base_url
+
+    def listContentInfo(self):
+        children = []
+        for name, child in self.context.items():
+            if IMessage.providedBy(child) and \
+                   hasMessageStatus(child, 'published'):
+                info = {}
+                info['title'] = child.title
+                url = self.base_url + name + '/'
+                info['url'] = url + '@@thread.html'
+                thread = Thread(child, self.request, url)
+                info['thread'] = thread.subthread()
+                children.append(info)
+        return children
+
+    subthread = ViewPageTemplateFile('subthread.pt')
+

Added: messageboard/trunk/step09/browser/widgets.py
===================================================================
--- messageboard/trunk/step09/browser/widgets.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/browser/widgets.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Module containing custom widget definitions.
+
+$Id: widgets.py,v 1.1 2003/06/10 14:40:44 srichter Exp $
+"""
+import re
+from zope.app.form.browser import TextAreaWidget
+from book.messageboard.fields import forbidden_regex, allowed_regex
+
+class HTMLSourceWidget(TextAreaWidget):
+
+    def _toFieldValue(self, input):
+        input = super(HTMLSourceWidget, self)._toFieldValue(input)
+
+        if self.context.forbidden_tags:
+            regex = forbidden_regex %'|'.join(
+                self.context.forbidden_tags)
+            input = re.sub(regex, '', input)
+
+        if self.context.allowed_tags:
+            regex = allowed_regex %'[ />]|'.join(
+                self.context.allowed_tags)
+            input = re.sub(regex, '', input)
+
+        return input
+

Added: messageboard/trunk/step09/configure.zcml
===================================================================
--- messageboard/trunk/step09/configure.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/configure.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,124 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    xmlns:mail="http://namespaces.zope.org/mail"
+    i18n_domain="messageboard">
+
+  <permission
+      id="book.messageboard.View"
+      title="View Message Board and Messages"
+      description="View the Message Board and all its content."
+      />
+  <permission
+      id="book.messageboard.Add"
+      title="Add Message"
+      description="Add Message."
+      />
+  <permission
+      id="book.messageboard.Edit"
+      title="Edit Messages"
+      description="Edit Messages."
+      />
+  <permission
+      id="book.messageboard.Delete"
+      title="Delete Message"
+      description="Delete Message."
+      />
+
+  <permission
+      id="book.messageboard.PublishContent"
+      title="Publish Message"
+      description="Publish Message."/>
+
+  <interface 
+      interface=".interfaces.IMessageBoard" 
+      type="zope.app.content.interfaces.IContentType"
+      /> 
+
+  <content class=".messageboard.MessageBoard">
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContentContainer" 
+        />
+    <factory
+        id="book.messageboard.MessageBoard"
+        description="Message Board" 
+        />
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessageBoard"
+        />
+    <require
+        permission="book.messageboard.Edit"
+        set_schema=".interfaces.IMessageBoard"
+        />
+  </content>
+
+  <interface 
+      interface=".interfaces.IMessage" 
+      type="zope.app.content.interfaces.IContentType"
+      /> 
+
+  <content class=".message.Message">
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContentContainer" 
+        />
+    <implements interface=
+        "zope.app.workflow.interfaces.IProcessInstanceContainerAdaptable"/>
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessage"
+        />
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessageContainer"
+        />
+    <require
+        permission="book.messageboard.Add"
+        set_schema=".interfaces.IMessage"
+        />
+  </content>
+
+  <adapter
+      factory=".message.MessageSized"
+      provides="zope.app.size.interfaces.ISized"
+      for=".interfaces.IMessage"
+      />
+
+  <adapter
+      factory=".message.MailSubscriptions"
+      provides=".interfaces.IMailSubscriptions"
+      for=".interfaces.IMessage"
+      permission="book.messageboard.Add"      
+      trusted="true" />
+
+  <mail:smtpMailer name="msgboard-smtp" hostname="localhost" port="25" />
+  
+  <mail:queuedDelivery 
+      name="msgboard-delivery"
+      permission="zope.SendMail"
+      queuePath="./mail-queue"
+      mailer="msgboard-smtp" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.event.interfaces.IObjectModifiedEvent" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.container.interfaces.IObjectAddedEvent" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.container.interfaces.IObjectRemovedEvent" />
+
+  <i18n:registerTranslations directory="locales" />
+
+  <include package=".browser" />
+
+</configure>

Added: messageboard/trunk/step09/fields.py
===================================================================
--- messageboard/trunk/step09/fields.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/fields.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Module containing custom field definitions.
+
+$Id$
+"""
+import re
+
+from zope.i18n import MessageIDFactory
+from zope.schema import Text
+from zope.schema.interfaces import ValidationError
+
+_ = MessageIDFactory('messageboard')
+
+forbidden_regex = r'</?(?:%s).*?/?>'
+allowed_regex = r'</??(?!%s[ />])[a-zA-Z0-9]*? ?(?:[a-z0-9]*?=?".*?")*/??>'
+
+class ForbiddenTags(ValidationError):
+    __doc__ = _("Forbidden HTML Tags used.")
+
+
+class HTML(Text):
+  
+    allowed_tags = ()
+    forbidden_tags = ()
+
+    def __init__(self, allowed_tags=(), forbidden_tags=(), **kw):
+        self.allowed_tags = allowed_tags
+        self.forbidden_tags = forbidden_tags
+        super(HTML, self).__init__(**kw)
+
+    def _validate(self, value):
+        super(HTML, self)._validate(value)
+
+        if self.forbidden_tags:
+            regex = forbidden_regex %'|'.join(self.forbidden_tags)
+            if re.findall(regex, value):
+                raise ForbiddenTags(value, self.forbidden_tags)
+
+        if self.allowed_tags:
+            regex = allowed_regex %'[ />]|'.join(self.allowed_tags)
+            if re.findall(regex, value):
+                raise ForbiddenTags(value, self.allowed_tags)
+
+

Added: messageboard/trunk/step09/interfaces.py
===================================================================
--- messageboard/trunk/step09/interfaces.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/interfaces.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,120 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Interfaces
+
+Interfaces for the Zope 3 based Message Board Package
+
+$Id$
+"""
+from zope.i18n import MessageIDFactory
+from zope.interface import classImplements, Interface
+from zope.schema import Text, TextLine, Field, Tuple
+from zope.schema.interfaces import IText
+
+from zope.app.container.constraints import ContainerTypesConstraint
+from zope.app.container.constraints import ItemTypePrecondition
+from zope.app.container.interfaces import IContained, IContainer
+from zope.app.file.interfaces import IFile
+
+from fields import HTML
+
+_ = MessageIDFactory('messageboard')
+
+
+class IMessage(Interface):
+    """A message object."""
+
+    title = TextLine(
+        title=_("Title/Subject"),
+        description=_("Title and/or subject of the message."),
+        default=u"",
+        required=True)
+
+    body = HTML(
+        title=_("Message Body"),
+        description=_("This is the actual message. Type whatever!"),
+        default=u"",
+        allowed_tags=('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'a',
+                      'br', 'b', 'i', 'u', 'em', 'sub', 'sup',
+                      'table', 'tr', 'td', 'th', 'code', 'pre',
+                      'center', 'div', 'span', 'p', 'font', 'ol',
+                      'ul', 'li', 'q', 's', 'strong'),
+        required=False)
+
+
+class IMessageBoard(IContainer):
+    """The message board is the base object for our package. It can only
+    contain IMessage objects."""
+
+    def __setitem__(name, object):
+        """Add a IMessage object."""
+
+    __setitem__.precondition = ItemTypePrecondition(IMessage)
+
+    description = Text(
+        title=_("Description"),
+        description=_("A detailed description of the content of the board."),
+        default=u"",
+        required=False)
+
+
+class IMessageContained(IContained):
+    """Interface that specifies the type of objects that can contain
+    messages."""
+    __parent__ = Field(
+        constraint = ContainerTypesConstraint(IMessageBoard, IMessage))
+
+
+class IMessageContainer(IContainer):
+    """We also want to make the message object a container that can contain
+    responses (other messages) and attachments (files and images)."""
+
+    def __setitem__(name, object):
+        """Add a IMessage object."""
+
+    __setitem__.precondition = ItemTypePrecondition(IMessage, IFile)
+
+
+class IHTML(IText):
+    """A text field that handles HTML input."""
+
+    allowed_tags = Tuple(
+        title=_("Allowed HTML Tags"),
+        description=_("""\
+        Only listed tags can be used in the value of the field.
+        """),
+        required=False)
+
+    forbidden_tags = Tuple(
+        title=_("Forbidden HTML Tags"),
+        description=_("""\
+        Listed tags cannot be used in the value of the field.
+        """),
+        required=False)
+
+classImplements(HTML, IHTML)
+
+
+class IMailSubscriptions(Interface):
+    """This interface allows you to retrieve a list of E-mails for
+    mailings. In our context these are messages."""
+
+    def getSubscriptions():
+        """Return a list of E-mails."""
+
+    def addSubscriptions(emails):
+        """Add a bunch of subscriptions; one would be okay too."""
+
+    def removeSubscriptions(emails):
+        """Remove a set of subscriptions."""

Added: messageboard/trunk/step09/locales/de/LC_MESSAGES/messageboard.mo
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step09/locales/de/LC_MESSAGES/messageboard.mo
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step09/locales/de/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step09/locales/de/LC_MESSAGES/messageboard.po	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/locales/de/LC_MESSAGES/messageboard.po	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,295 @@
+# translation of messageboard.po to German
+# translation of messageboard.po to English US
+# Copyright (C) YEAR ORGANIZATION.
+# Stephan Richter <stephan.richter at tufts.edu>, 2003.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"POT-Creation-Date: Mon Dec 15 00:30:36 2003\n"
+"PO-Revision-Date: 2003-12-15 00:14-0500\n"
+"Last-Translator: Stephan Richter <stephan.richter at tufts.edu>\n"
+"Language-Team: German <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.4\n"
+"X-Generator: KBabel 1.3\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+msgid "add-button"
+msgstr "  Add  "
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr "Add Content"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:109
+msgid "Menu of objects to be added to Messages."
+msgstr "Menu of objects to be added to Messages."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+#: src/zopeproducts/messageboard/browser/configure.zcml:140
+msgid "Change Message"
+msgstr "Change Message"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:148
+msgid "Preview"
+msgstr "Preview"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:156
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr "Message Details"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:16
+msgid "Menu of objects to be added to Message Boards."
+msgstr "Menu of objects to be added to Message Boards."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:184
+msgid "Subscriptions"
+msgstr "Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:189
+msgid "Message Mail Subscriptions"
+msgstr "Message Mail Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:20
+#: src/zopeproducts/messageboard/browser/configure.zcml:113
+msgid "Add"
+msgstr "Add"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:203
+msgid "Image"
+msgstr "Image"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:209
+msgid "File"
+msgstr "File"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Add Message Board"
+msgstr "Add Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+msgid "Edit"
+msgstr "Edit"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:50
+msgid "Change Message Board"
+msgstr "Change Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:58
+#: src/zopeproducts/messageboard/browser/configure.zcml:164
+msgid "Thread"
+msgstr "Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:66
+#: src/zopeproducts/messageboard/browser/configure.zcml:172
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr "Discussion Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:74
+#: src/zopeproducts/messageboard/browser/configure.zcml:215
+msgid "Contents"
+msgstr "Contents"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:8
+msgid "Message Board Help"
+msgstr "Message Board Help"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:82
+msgid "Review Messages"
+msgstr "Review Messages"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:90
+msgid "Publication Review"
+msgstr "Publication Review"
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr "Author"
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr "Date/Time"
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr "Parent"
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr "Body"
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr "Title"
+
+# 8/browser/messageboard_add.pt:19
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr "Workflow erstellen"
+
+# 8/browser/messageboard_add.pt:21
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+msgstr ""
+"Ohne dem Workflow wirst Du nicht in der Lage sein Anzeigen vor der "
+"Veröffentlichung zu prüfen. Es ist zu erwähnen das Du jederzeit den Workflow "
+"später ändern kannst, so dass all Übergänge automatisch ausgeführt werden."
+
+# 7/browser/subscriptions.pt:35
+# Default: "Refresh"
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+msgid "refresh-button"
+msgstr "refresh-button"
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+msgid "submit-button"
+msgstr "Abschicken"
+
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr "Schwebende Anzeigen"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr "Current Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+msgid "remove-button"
+msgstr "   Add  "
+
+# 7/browser/subscriptions.pt:25
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr "Enter new Users (separate by 'Return')"
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Users that actually use the Message Board."
+msgstr "Users that actually use the Message Board."
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Message Board User"
+msgstr "Message Board User"
+
+#: src/zopeproducts/messageboard/configure.zcml:103
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+#: src/zopeproducts/messageboard/browser/configure.zcml:197
+msgid "Message"
+msgstr "Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "Message Board Editor"
+msgstr "Message Board Editor"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "The Editor can edit and delete Messages."
+msgstr "The Editor can edit and delete Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View the Message Board and all its content."
+msgstr "View the Message Board and all its content."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View Message Board and Messages"
+msgstr "View Message Board and Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+msgid "Add Message."
+msgstr "Add Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+msgid "Add Message"
+msgstr "Add Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages."
+msgstr "Edit Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages"
+msgstr "Edit Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message."
+msgstr "Delete Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message"
+msgstr "Delete Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message"
+msgstr "Publish Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message."
+msgstr "Publish Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:77
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Message Board"
+msgstr "Message Board"
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr "Forbidden HTML Tags used."
+
+#: src/zopeproducts/messageboard/interfaces.py:38
+msgid "Description"
+msgstr "Description"
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "A detailed description of the content of the board."
+msgstr "A detailed description of the content of the board."
+
+#: src/zopeproducts/messageboard/interfaces.py:48
+msgid "Title/Subject"
+msgstr "Title/Subject"
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title and/or subject of the message."
+msgstr "Title and/or subject of the message."
+
+#: src/zopeproducts/messageboard/interfaces.py:54
+msgid "Message Body"
+msgstr "Message Body"
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "This is the actual message. Type whatever!"
+msgstr "This is the actual message. Type whatever!"
+
+#: src/zopeproducts/messageboard/interfaces.py:82
+msgid "Allowed HTML Tags"
+msgstr "Allowed HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:83
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:89
+msgid "Forbidden HTML Tags"
+msgstr "Forbidden HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:90
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "

Added: messageboard/trunk/step09/locales/en/LC_MESSAGES/messageboard.mo
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step09/locales/en/LC_MESSAGES/messageboard.mo
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step09/locales/en/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step09/locales/en/LC_MESSAGES/messageboard.po	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/locales/en/LC_MESSAGES/messageboard.po	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,297 @@
+# translation of messageboard.po to German
+# translation of messageboard.po to English US
+# Copyright (C) YEAR ORGANIZATION.
+# Stephan Richter <stephan.richter at tufts.edu>, 2003.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"POT-Creation-Date: Mon Dec 15 00:30:36 2003\n"
+"PO-Revision-Date: 2003-12-15 00:16-0500\n"
+"Last-Translator: Stephan Richter <stephan.richter at tufts.edu>\n"
+"Language-Team: German <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.4\n"
+"X-Generator: KBabel 1.3\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+msgid "add-button"
+msgstr "  Add  "
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr "Add Content"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:109
+msgid "Menu of objects to be added to Messages."
+msgstr "Menu of objects to be added to Messages."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+#: src/zopeproducts/messageboard/browser/configure.zcml:140
+msgid "Change Message"
+msgstr "Change Message"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:148
+msgid "Preview"
+msgstr "Preview"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:156
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr "Message Details"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:16
+msgid "Menu of objects to be added to Message Boards."
+msgstr "Menu of objects to be added to Message Boards."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:184
+msgid "Subscriptions"
+msgstr "Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:189
+msgid "Message Mail Subscriptions"
+msgstr "Message Mail Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:20
+#: src/zopeproducts/messageboard/browser/configure.zcml:113
+msgid "Add"
+msgstr "Add"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:203
+msgid "Image"
+msgstr "Image"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:209
+msgid "File"
+msgstr "File"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Add Message Board"
+msgstr "Add Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+msgid "Edit"
+msgstr "Edit"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:50
+msgid "Change Message Board"
+msgstr "Change Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:58
+#: src/zopeproducts/messageboard/browser/configure.zcml:164
+msgid "Thread"
+msgstr "Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:66
+#: src/zopeproducts/messageboard/browser/configure.zcml:172
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr "Discussion Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:74
+#: src/zopeproducts/messageboard/browser/configure.zcml:215
+msgid "Contents"
+msgstr "Contents"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:8
+msgid "Message Board Help"
+msgstr "Message Board Help"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:82
+msgid "Review Messages"
+msgstr "Review Messages"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:90
+msgid "Publication Review"
+msgstr "Publication Review"
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr "Author"
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr "Date/Time"
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr "Parent"
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr "Body"
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr "Title"
+
+# 8/browser/messageboard_add.pt:19
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr "Create Workflow"
+
+# 8/browser/messageboard_add.pt:21
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+msgstr ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+
+# 8/browser/messageboard_add.pt:30
+# 8/browser/subscriptions.pt:35
+# Default: "Refresh"
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+msgid "refresh-button"
+msgstr "Refresh"
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+msgid "submit-button"
+msgstr "Submit"
+
+# 8/browser/review.pt:5
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr "Pending Messages"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr "Current Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+msgid "remove-button"
+msgstr "Remove"
+
+# 8/browser/subscriptions.pt:25
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr "Enter new Users (separate by 'Return')"
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Users that actually use the Message Board."
+msgstr "Users that actually use the Message Board."
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Message Board User"
+msgstr "Message Board User"
+
+#: src/zopeproducts/messageboard/configure.zcml:103
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+#: src/zopeproducts/messageboard/browser/configure.zcml:197
+msgid "Message"
+msgstr "Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "Message Board Editor"
+msgstr "Message Board Editor"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "The Editor can edit and delete Messages."
+msgstr "The Editor can edit and delete Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View the Message Board and all its content."
+msgstr "View the Message Board and all its content."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View Message Board and Messages"
+msgstr "View Message Board and Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+msgid "Add Message."
+msgstr "Add Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+msgid "Add Message"
+msgstr "Add Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages."
+msgstr "Edit Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages"
+msgstr "Edit Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message."
+msgstr "Delete Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message"
+msgstr "Delete Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message"
+msgstr "Publish Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message."
+msgstr "Publish Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:77
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Message Board"
+msgstr "Message Board"
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr "Forbidden HTML Tags used."
+
+#: src/zopeproducts/messageboard/interfaces.py:38
+msgid "Description"
+msgstr "Description"
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "A detailed description of the content of the board."
+msgstr "A detailed description of the content of the board."
+
+#: src/zopeproducts/messageboard/interfaces.py:48
+msgid "Title/Subject"
+msgstr "Title/Subject"
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title and/or subject of the message."
+msgstr "Title and/or subject of the message."
+
+#: src/zopeproducts/messageboard/interfaces.py:54
+msgid "Message Body"
+msgstr "Message Body"
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "This is the actual message. Type whatever!"
+msgstr "This is the actual message. Type whatever!"
+
+#: src/zopeproducts/messageboard/interfaces.py:82
+msgid "Allowed HTML Tags"
+msgstr "Allowed HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:83
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:89
+msgid "Forbidden HTML Tags"
+msgstr "Forbidden HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:90
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "

Added: messageboard/trunk/step09/locales/messageboard.pot
===================================================================
--- messageboard/trunk/step09/locales/messageboard.pot	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/locales/messageboard.pot	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,292 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: Zope X3 Pre-M4\n"
+"POT-Creation-Date: Mon Dec 15 00:30:36 2003\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: Zope 3 Developers <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: ENCODING\n"
+"Generated-By: zope/app/translation_files/extract.py\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+# Default: "Add"
+msgid "add-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:109
+msgid "Menu of objects to be added to Messages."
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+#: src/zopeproducts/messageboard/browser/configure.zcml:140
+msgid "Change Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:148
+msgid "Preview"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:156
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:16
+msgid "Menu of objects to be added to Message Boards."
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:184
+msgid "Subscriptions"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:189
+msgid "Message Mail Subscriptions"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:20
+#: src/zopeproducts/messageboard/browser/configure.zcml:113
+msgid "Add"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:203
+msgid "Image"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:209
+msgid "File"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Add Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+msgid "Edit"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:50
+msgid "Change Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:58
+#: src/zopeproducts/messageboard/browser/configure.zcml:164
+msgid "Thread"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:66
+#: src/zopeproducts/messageboard/browser/configure.zcml:172
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:74
+#: src/zopeproducts/messageboard/browser/configure.zcml:215
+msgid "Contents"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:8
+msgid "Message Board Help"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:82
+msgid "Review Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:90
+msgid "Publication Review"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid "Without the workflow you will not be able to review messages before they are published. Note that you can always modify the messageboard workflow later to make all transitions automatically."
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+# Default: "Refresh"
+msgid "refresh-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+# Default: "Submit"
+msgid "submit-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+# Default: "Remove"
+msgid "remove-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Users that actually use the Message Board."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Message Board User"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:103
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+#: src/zopeproducts/messageboard/browser/configure.zcml:197
+msgid "Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "Message Board Editor"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "The Editor can edit and delete Messages."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View the Message Board and all its content."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View Message Board and Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+msgid "Add Message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+msgid "Add Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:77
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:38
+msgid "Description"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "A detailed description of the content of the board."
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:48
+msgid "Title/Subject"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title and/or subject of the message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:54
+msgid "Message Body"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "This is the actual message. Type whatever!"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:82
+msgid "Allowed HTML Tags"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:83
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:89
+msgid "Forbidden HTML Tags"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:90
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+

Added: messageboard/trunk/step09/message.py
===================================================================
--- messageboard/trunk/step09/message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,361 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Implementation
+
+An implementation of the Message using BTreeContainers as base.
+
+$Id: message.py,v 1.1 2003/06/07 11:24:48 srichter Exp $
+"""
+from zope.i18n import MessageIDFactory
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.container.btree import BTreeContainer
+from zope.app.container.interfaces import IObjectAddedEvent
+from zope.app.container.interfaces import IObjectRemovedEvent
+from zope.app.event.interfaces import IObjectModifiedEvent
+from zope.app.mail.interfaces import IMailDelivery
+from zope.app.size.interfaces import ISized
+
+from book.messageboard.interfaces import IMessage
+from book.messageboard.interfaces import IMessageContained, IMessageContainer
+from book.messageboard.interfaces import IMailSubscriptions
+
+_ = MessageIDFactory('messageboard')
+
+
+class Message(BTreeContainer):
+    """A simple implementation of a message.
+
+    Make sure that the ``Message`` implements the ``IMessage`` interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMessage, Message)
+    True
+
+    Here is an example of changing the title and description of the message:
+
+    >>> message = Message()
+    >>> message.title
+    u''
+    >>> message.body
+    u''
+    >>> message.title = u'Message Title'
+    >>> message.body = u'Message Body'
+    >>> message.title
+    u'Message Title'
+    >>> message.body
+    u'Message Body'
+    """
+    implements(IMessage, IMessageContained, IMessageContainer)
+
+    # See book.messageboard.interfaces.IMessage
+    title = u''
+
+    # See book.messageboard.interfaces.IMessage
+    body = u''
+
+  
+class MessageSized(object):
+
+    implements(ISized)
+    __used_for__ = IMessage
+
+    def __init__(self, message):
+        self._message = message
+
+    def sizeForSorting(self):
+        """See ISized
+
+        Create the adapter first.
+
+        >>> size = MessageSized(Message())
+
+        Here are some examples of the expected output.
+
+        >>> size.sizeForSorting()
+        ('item', 0)
+        >>> size._message['msg1'] = Message()
+        >>> size.sizeForSorting()
+        ('item', 1)
+        >>> size._message['att1'] = object()
+        >>> size.sizeForSorting()
+        ('item', 2)
+        """
+        return ('item', len(self._message))
+
+    def sizeForDisplay(self):
+        """See ISized
+
+        Creater the adapter first.
+
+        >>> size = MessageSized(Message())
+
+        Here are some examples of the expected output.
+
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 0, atts: 0'
+        >>> size._message['msg1'] = Message()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'1 reply, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 1, atts: 0'
+        >>> size._message['att1'] = object()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'1 reply, 1 attachment'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 1, atts: 1'
+        >>> size._message['msg2'] =  Message()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, 1 attachment'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 2, atts: 1'
+        >>> size._message['att2'] = object()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 2, atts: 2'
+        """
+        messages = 0
+        for obj in self._message.values():
+            if IMessage.providedBy(obj):
+                messages += 1
+
+        attachments = len(self._message)-messages
+
+        if messages == 1 and attachments == 1: 
+            size = _('1 reply, 1 attachment')
+        elif messages == 1 and attachments != 1:
+            size = _('1 reply, ${attachments} attachments')
+        elif messages != 1 and attachments == 1:
+            size = _('${messages} replies, 1 attachment')
+        else: 
+            size = _('${messages} replies, ${attachments} attachments')
+  
+        size.mapping = {'messages': `messages`, 'attachments': `attachments`}
+
+        return size
+
+
+SubscriberKey='http://www.zope.org/messageboard#1.0/MailSubscriptions/emails'
+
+
+class MailSubscriptions:
+    """Message Mail Subscriptions.
+
+    Verify the interface implementation
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMailSubscriptions, MailSubscriptions)
+    True
+
+    Create asubscription instance of a message
+
+    >>> msg = Message()
+    >>> sub = MailSubscriptions(msg)
+
+    Verify that we have initially no subscriptions and then add some.
+
+    >>> sub.getSubscriptions()
+    ()
+    >>> sub.addSubscriptions(('foo at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com',)
+    >>> sub.addSubscriptions(('blah at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com', 'blah at bar.com')
+    >>> sub.addSubscriptions(('doh at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com', 'blah at bar.com', 'doh at bar.com')
+
+    Now let's also check that we can remove entries.
+
+    >>> sub.removeSubscriptions(('foo at bar.com',))
+    >>> sub.getSubscriptions()
+    ('blah at bar.com', 'doh at bar.com')
+
+    When we construct a new mail subscription adapter instance, the values
+    should still be there.
+
+    >>> sub1 = MailSubscriptions(msg)
+    >>> sub1.getSubscriptions()
+    ('blah at bar.com', 'doh at bar.com')
+    """
+    implements(IMailSubscriptions)
+    __used_for__ = IMessage
+
+    def __init__(self, context):
+        self.context = context
+        self._annotations = IAnnotations(context)
+        if not self._annotations.get(SubscriberKey):
+            self._annotations[SubscriberKey] = ()
+
+    def getSubscriptions(self):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        return self._annotations[SubscriberKey]
+        
+    def addSubscriptions(self, emails):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        subscribers = list(self._annotations[SubscriberKey])
+        for email in emails:
+            if email not in subscribers:
+                subscribers.append(email.strip())
+        self._annotations[SubscriberKey] = tuple(subscribers)
+                
+    def removeSubscriptions(self, emails):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        subscribers = list(self._annotations[SubscriberKey])
+        for email in emails:
+            if email in subscribers:
+                subscribers.remove(email)
+        self._annotations[SubscriberKey] = tuple(subscribers)
+
+
+class MessageMailer:
+    """Class to handle all outgoing mail."""
+  
+    def __call__(self, event):
+        r"""Called by the event system.
+
+        Here is a demonstration on how the notification process and mail
+        sending works.
+
+        Before we can test this method, we have to create a mail delivery
+        object for testing.
+
+        >>> mail_result = [] 
+
+        >>> from zope.interface import implements
+        >>> from zope.app.mail.interfaces import IMailDelivery
+        
+        >>> class MailDeliveryStub(object):
+        ...     implements(IMailDelivery)
+        ... 
+        ...     def send(self, fromaddr, toaddrs, message):
+        ...         mail_result.append((fromaddr, toaddrs, message))
+
+        >>> from zope.app.tests import ztapi
+        >>> ztapi.provideUtility(IMailDelivery, MailDeliveryStub(),
+        ...                      name='msgboard-delivery')
+
+        Create a message.
+
+        >>> from zope.interface import directlyProvides
+        >>> from zope.app.traversing.interfaces import IContainmentRoot
+
+        >>> msg = Message()
+        >>> directlyProvides(msg, IContainmentRoot)
+        >>> msg.__name__ = 'msg'
+        >>> msg.__parent__ = None
+        >>> msg.title = 'Hello'
+        >>> msg.body = 'Hello World!'
+
+        Add a subscription to message.
+
+        >>> msg_sub = MailSubscriptions(msg)
+        >>> msg_sub.context.__annotations__[SubscriberKey] = ('foo at bar.com',)
+
+        Now, create an event and send it to the message mailer object.
+
+        >>> from zope.app.event.objectevent import ObjectModifiedEvent
+        >>> event = ObjectModifiedEvent(msg)
+        >>> mailer(event)
+
+        >>> from pprint import pprint
+        >>> pprint(mail_result)
+        [('mailer at messageboard.org',
+          ('foo at bar.com',),
+          'Subject: Modified: msg\n\n\nHello World!')]
+        """
+        if IMessage.providedBy(event.object):
+            if IObjectAddedEvent.providedBy(event):
+                self.handleAdded(event.object)
+            elif IObjectModifiedEvent.providedBy(event):
+                self.handleModified(event.object)
+            elif IObjectRemovedEvent.providedBy(event):
+                self.handleRemoved(event.object)
+  
+    def handleAdded(self, object):
+        subject = 'Added: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = object.body
+        self.mail(emails, subject, body)        
+  
+    def handleModified(self, object):
+        subject = 'Modified: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = object.body
+        self.mail(emails, subject, body)
+  
+    def handleRemoved(self, object):
+        subject = 'Removed: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = subject
+        self.mail(emails, subject, body)
+  
+    def getAllSubscribers(self, object):
+        """Retrieves all email subscribers.
+
+        Here a small demonstration of retrieving all subscribers.
+
+        >>> from zope.interface import directlyProvides
+        >>> from zope.app.traversing.interfaces import IContainmentRoot
+
+        Create a parent message as it would be located in the message
+        board. Also add a subscriber to the message.
+
+        >>> msg1 = Message()
+        >>> directlyProvides(msg1, IContainmentRoot)
+        >>> msg1.__name__ = 'msg1'
+        >>> msg1.__parent__ = None
+        >>> msg1_sub = MailSubscriptions(msg1)
+        >>> msg1_sub.context.__annotations__[SubscriberKey] = ('foo at bar.com',)
+
+        Create a reply to the first message and also give it a subscriber.
+       
+        >>> msg2 = Message()
+        >>> msg2_sub = MailSubscriptions(msg2)
+        >>> msg2_sub.context.__annotations__[SubscriberKey] = ('blah at bar.com',)
+        >>> msg1['msg2'] = msg2
+
+        When asking for all subscriptions of message 2, we should get the
+        subscriber from message 1 as well.
+
+        >>> mailer.getAllSubscribers(msg2)
+        ('blah at bar.com', 'foo at bar.com')
+        """
+        emails = ()
+        msg = object
+        while IMessage.providedBy(msg):
+            emails += tuple(IMailSubscriptions(msg).getSubscriptions())
+            msg = zapi.getParent(msg)
+        return emails
+  
+    def mail(self, toaddrs, subject, body):
+        """Mail out the Message Board change message."""
+        if not toaddrs:
+            return
+        msg = 'Subject: %s\n\n\n%s' %(subject, body)
+        mail_utility = zapi.getUtility(IMailDelivery, 'msgboard-delivery')
+        mail_utility.send('mailer at messageboard.org' , toaddrs, msg)
+  
+mailer = MessageMailer()

Added: messageboard/trunk/step09/messageboard.py
===================================================================
--- messageboard/trunk/step09/messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,47 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Implementation
+
+An implementation of the Message Board using BTreeContainers as base.
+
+$Id$
+"""
+from zope.interface import implements
+from zope.app.container.btree import BTreeContainer
+
+from book.messageboard.interfaces import IMessageBoard
+
+class MessageBoard(BTreeContainer):
+    """A very simple implementation of a message board using B-Tree Containers
+
+    Make sure that the ``MessageBoard`` implements the ``IMessageBoard``
+    interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMessageBoard, MessageBoard)
+    True
+    
+    Here is an example of changing the description of the board:
+
+    >>> board = MessageBoard()
+    >>> board.description
+    u''
+    >>> board.description = u'Message Board Description'
+    >>> board.description
+    u'Message Board Description'
+    """
+    implements(IMessageBoard)
+
+    # See book.messageboard.interfaces.IMessageBoard
+    description = u''

Added: messageboard/trunk/step09/security.zcml
===================================================================
--- messageboard/trunk/step09/security.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/security.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,54 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="messageboard">
+
+  <role
+      id="book.messageboard.User"
+      title="Message Board User"
+      description="Users that actually use the Message Board."
+      />
+  <grant
+      permission="book.messageboard.View"
+      role="book.messageboard.User"
+      />
+  <grant
+      permission="book.messageboard.Add"
+      role="book.messageboard.User"
+      />
+
+  <role
+      id="book.messageboard.Editor"
+      title="Message Board Editor"
+      description="The Editor can edit and delete Messages."
+      />
+  <grant
+      permission="book.messageboard.Edit"
+      role="book.messageboard.Editor"
+      />
+  <grant
+      permission="book.messageboard.Delete"
+      role="book.messageboard.Editor"
+      />
+  <grant
+      permission="book.messageboard.PublishContent"
+      role="book.messageboard.Editor"/>
+
+
+  <grant
+      permission="book.messageboard.View"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Add"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Edit"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Delete"
+      role="zope.Manager"
+      /> 
+
+</configure>

Added: messageboard/trunk/step09/tests/__init__.py
===================================================================
--- messageboard/trunk/step09/tests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/tests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step09/tests/test_fields.py
===================================================================
--- messageboard/trunk/step09/tests/test_fields.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/tests/test_fields.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_fields.py,v 1.1 2003/06/10 14:40:45 srichter Exp $
+"""
+import unittest
+from zope.schema.tests.test_strfield import TextTest
+
+from book.messageboard.fields import HTML, ForbiddenTags
+
+class HTMLTest(TextTest):
+
+    _Field_Factory = HTML
+
+    def test_AllowedTagsHTMLValidate(self):
+        html = self._Field_Factory(allowed_tags=('h1','pre'))
+        html.validate(u'<h1>Blah</h1>') 
+        html.validate(u'<pre>Blah</pre>') 
+        html.validate(u'<h1><pre>Blah</pre></h1>') 
+        html.validate(u'<h1 style="..."><pre>Blah</pre></h1>') 
+        html.validate(u'<h1 style="..."><pre f="">Blah</pre></h1>') 
+
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2>Foo</h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2><pre>Foo</pre></h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2 attr="blah">Foo</h2>')
+
+
+    def test_ForbiddenTagsHTMLValidate(self):
+        html = self._Field_Factory(forbidden_tags=('h2','pre'))
+        html.validate(u'<h1>Blah</h1>') 
+        html.validate(u'<h1 style="...">Blah</h1>') 
+        html.validate(u'<h1 style="..."><div>Blah</div></h1>') 
+        html.validate(u'<h1 style="..."><div f="">Blah</div></h1>') 
+
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2>Foo</h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2><div>Foo</div></h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2 attr="blah">Foo</h2>')
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(HTMLTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step09/tests/test_message.py
===================================================================
--- messageboard/trunk/step09/tests/test_message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/tests/test_message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_message.py,v 1.2 2003/08/20 17:07:46 srichter Exp $
+"""
+import unittest
+from zope.interface import classImplements 
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.annotation.interfaces import IAttributeAnnotatable
+from zope.app.annotation.attribute import AttributeAnnotations
+from zope.app.container.tests.test_icontainer import TestSampleContainer
+from zope.app.location.traversing import LocationPhysicallyLocatable
+from zope.app.location.interfaces import ILocation
+from zope.app.tests import placelesssetup
+from zope.app.tests import ztapi
+from zope.app.traversing.interfaces import IPhysicallyLocatable
+
+from book.messageboard.interfaces import IMailSubscriptions
+from book.messageboard.interfaces import IMessage
+from book.messageboard.message import MailSubscriptions
+from book.messageboard.message import Message
+
+
+class Test(TestSampleContainer):
+
+    def makeTestObject(self):
+        return Message()
+
+
+def setUp():
+    placelesssetup.setUp()
+    classImplements(Message, IAttributeAnnotatable)
+    ztapi.provideAdapter(IAttributeAnnotatable, IAnnotations,
+                         AttributeAnnotations)
+    ztapi.provideAdapter(ILocation, IPhysicallyLocatable,
+                         LocationPhysicallyLocatable)
+    ztapi.provideAdapter(IMessage, IMailSubscriptions, MailSubscriptions)
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite('book.messageboard.message',
+                     setUp=setUp, tearDown=placelesssetup.tearDown),
+        unittest.makeSuite(Test),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step09/tests/test_messageboard.py
===================================================================
--- messageboard/trunk/step09/tests/test_messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/tests/test_messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_messageboard.py,v 1.2 2003/08/20 17:07:46 srichter Exp $
+"""
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.container.tests.test_icontainer import TestSampleContainer
+
+from book.messageboard.messageboard import MessageBoard
+
+
+class Test(TestSampleContainer):
+
+    def makeTestObject(self):
+        return MessageBoard()
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite('book.messageboard.messageboard'),
+        unittest.makeSuite(Test),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step09/workflow.xml
===================================================================
--- messageboard/trunk/step09/workflow.xml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step09/workflow.xml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<workflow type="StatefulWorkflow" title="Message Publication Review">
+  <schema name=""/>
+  <states>
+    <state name="INITIAL" title="initial" />
+    <state name="private" title="Private" />
+    <state name="pending" title="Pending Publication" />
+    <state name="published" title="Public" />
+  </states>
+  <transitions>
+     
+    <transition 
+        sourceState="published"
+        destinationState="private"
+        name="published_private"
+        title="Unpublish Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="private"
+        destinationState="pending"
+        name="private_pending"
+        title="Submit Message"
+        permission="book.messageboard.Edit"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="INITIAL" 
+        destinationState="private"
+        name="initial_private"
+        title="Make Private"
+        triggerMode="Automatic" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="published"
+        name="pending_published"
+        title="Publish Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="private"
+        name="pending_private"
+        title="Retract Message"
+        permission="book.messageboard.Edit"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="private"
+        name="pending_private_reject"
+        title="Reject Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+    
+  </transitions>
+  
+</workflow>

Added: messageboard/trunk/step10/__init__.py
===================================================================
--- messageboard/trunk/step10/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step10/browser/__init__.py
===================================================================
--- messageboard/trunk/step10/browser/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step10/browser/configure.zcml
===================================================================
--- messageboard/trunk/step10/browser/configure.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/configure.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,173 @@
+<configure
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:help="http://namespaces.zope.org/help"
+    xmlns:zope="http://namespaces.zope.org/zope">
+
+  <help:register
+      id="messageboard"
+      title="Message Board Help"
+      parent="ui"
+      for="book.messageboard.interfaces.IMessageBoard"
+      doc_path="./help/package_intro.rst"/>
+
+  <addform
+      label="Add Message Board"
+      name="AddMessageBoard.html"
+      template="messageboard_add.pt"
+      class=".messageboard.AddMessageBoard"
+      schema="book.messageboard.interfaces.IMessageBoard"
+      content_factory="book.messageboard.messageboard.MessageBoard"
+      fields="description"
+      permission="zope.ManageContent"
+      />
+
+  <addMenuItem
+      class="book.messageboard.messageboard.MessageBoard"
+      title="Message Board"
+      description="A Message Board"
+      permission="zope.ManageContent"
+      view="AddMessageBoard.html" 
+      />
+
+  <editform
+      schema="book.messageboard.interfaces.IMessageBoard"
+      for="book.messageboard.interfaces.IMessageBoard"
+      label="Change Message Board"
+      name="edit.html"
+      permission="zope.ManageContent"
+      menu="zmi_views" title="Edit" 
+      />
+
+  <containerViews
+      for="book.messageboard.interfaces.IMessageBoard"
+      index="book.messageboard.View"
+      contents="book.messageboard.Edit"
+      add="book.messageboard.Add"
+      />
+
+  <page
+      name="thread.html"
+      for="book.messageboard.interfaces.IMessageBoard"
+      class=".thread.Thread"
+      template="thread.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Thread"/>
+
+  <defaultView
+      for="book.messageboard.interfaces.IMessageBoard"
+      name="thread.html"/>
+
+  <icon
+      name="zmi_icon"
+      for="book.messageboard.interfaces.IMessageBoard"
+      file="messageboard.png" />
+
+  <page
+      name="review.html"
+      for="book.messageboard.interfaces.IMessageBoard"
+      class=".messageboard.ReviewMessages"
+      permission="book.messageboard.PublishContent"
+      template="review.pt"
+      menu="zmi_views" title="Review Messages"/>
+
+  <help:register
+      id="board.review"
+      title="Publication Review"
+      parent="ui/messageboard"
+      for="book.messageboard.interfaces.IMessageBoard"
+      view="review.html"
+      doc_path="./help/board_review.rst"/>
+
+  <addform
+      label="Add Message"
+      name="AddMessage.html"
+      schema="book.messageboard.interfaces.IMessage"
+      content_factory="book.messageboard.message.Message"
+      fields="title body"
+      permission="book.messageboard.Add"
+      />
+
+  <addMenuItem
+      class="book.messageboard.message.Message"
+      title="Message"
+      description="A Message"
+      permission="book.messageboard.Add"
+      view="AddMessage.html" 
+      />
+
+  <editform
+      schema="book.messageboard.interfaces.IMessage"
+      for="book.messageboard.interfaces.IMessage"
+      label="Change Message"
+      fields="title body"
+      name="edit.html"
+      permission="book.messageboard.Edit"
+      menu="zmi_views" title="Edit" 
+      />
+
+  <help:register
+      id="message.edit"
+      title="Change Message"
+      parent="ui/messageboard"
+      for="book.messageboard.interfaces.IMessage"
+      view="edit.html"
+      doc_path="./help/msg_edit.rst"/>
+
+  <containerViews
+      for="book.messageboard.interfaces.IMessage"
+      index="book.messageboard.View"
+      contents="book.messageboard.Edit"
+      add="book.messageboard.Add"
+      />
+
+  <page
+      name="details.html"
+      for="book.messageboard.interfaces.IMessage"
+      class=".message.MessageDetails"
+      template="details.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Preview"/>
+
+  <defaultView
+      for="book.messageboard.interfaces.IMessage"
+      name="details.html"/>
+
+  <page
+      name="thread.html"
+      for="book.messageboard.interfaces.IMessage"
+      class=".thread.Thread"
+      template="thread.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Thread"/>
+
+  <icon
+      name="zmi_icon"
+      for="book.messageboard.interfaces.IMessage"
+      file="message.png" />
+
+
+  <zope:view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="book.messageboard.interfaces.IHTML"
+      provides="zope.app.form.interfaces.IInputWidget"
+      factory=".widgets.HTMLSourceWidget"
+      permission="zope.Public"
+      />
+
+  <pages
+      for="book.messageboard.interfaces.IMessage"
+      class=".message.MailSubscriptions"
+      permission="book.messageboard.Edit"
+      >
+    <page 
+        name="subscriptions.html" 
+        template="subscriptions.pt"
+        menu="zmi_views" title="Subscriptions" 
+        />
+    <page 
+       name="changeSubscriptions.html" 
+       attribute="change" 
+       />
+  </pages>
+
+</configure>

Added: messageboard/trunk/step10/browser/details.pt
===================================================================
--- messageboard/trunk/step10/browser/details.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/details.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Message Details</h1>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Title</div>
+            <div class="field" tal:content="context/title" />
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Author</div>
+            <div class="field" tal:content="view/author"/>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Date/Time</div>
+            <div class="field" tal:content="view/modified"/>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Parent</div>
+            <div class="field" tal:define="info view/parent_info">
+              <a href="../" 
+                  tal:condition="info"
+                  tal:content="info/title" />
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Body</div>
+            <div class="field" tal:content="structure context/body"/>
+        </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step10/browser/ftests/__init__.py
===================================================================
--- messageboard/trunk/step10/browser/ftests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/ftests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step10/browser/ftests/test_message.py
===================================================================
--- messageboard/trunk/step10/browser/ftests/test_message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/ftests/test_message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Functional Tests
+
+$Id$
+"""
+import unittest
+from zope.app.tests.functional import BrowserTestCase
+
+class MessageTest(BrowserTestCase):
+
+    def testAddMessage(self):
+        response = self.publish(
+            '/+/AddMessageBoard.html=board',
+            basic='mgr:mgrpw',
+            form={'field.description': u'Message Board',
+                  'UPDATE_SUBMIT': 'Add'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/@@contents.html')
+        response = self.publish(
+            '/board/+/AddMessage.html=msg1',
+            basic='mgr:mgrpw',
+            form={'field.title': u'Message 1',
+                  'field.body': u'Body',
+                  'UPDATE_SUBMIT': 'Add'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/board/@@contents.html')
+       
+    def testMessageDetails(self):
+        self.testAddMessage()
+        response = self.publish('/board/msg1/@@details.html',
+                                basic='mgr:mgrpw')
+        body = response.getBody()
+        self.checkForBrokenLinks(body, '/board/msg1/@@details.html',
+                                 basic='mgr:mgrpw')
+        
+        self.assert_(body.find('Message Details') > 0)
+        self.assert_(body.find('Message 1') > 0)
+        self.assert_(body.find('Body') > 0)
+        
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MessageTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step10/browser/help/board_review.rst
===================================================================
--- messageboard/trunk/step10/browser/help/board_review.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/help/board_review.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,3 @@
+This view lists all messages in the board that are pending for
+publication. Each listed method is a link that brings you to the
+message's "Workflow" view where you can initiate a transition.

Added: messageboard/trunk/step10/browser/help/msg_edit.rst
===================================================================
--- messageboard/trunk/step10/browser/help/msg_edit.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/help/msg_edit.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,9 @@
+  This screen allows you to edit the data (i.e. the subject and body) of
+  the Message object.
+  
+  title - A one line unicode text string that briefly describes the
+          purpose/subject of the message.
+  
+  body - A multiple line unicode text string that is the actual content of 
+         the message. It is accepting HTML, but restricts the user to a 
+         couple of selected tags. Feel free to type anything you wish.

Added: messageboard/trunk/step10/browser/help/package_intro.rst
===================================================================
--- messageboard/trunk/step10/browser/help/package_intro.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/help/package_intro.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,6 @@
+==========================
+Message Board Demo Package
+==========================
+
+This package demos various features of the Zope 3 Framework. If you 
+have questions or concerns, please let me know. 

Added: messageboard/trunk/step10/browser/message.png
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step10/browser/message.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step10/browser/message.py
===================================================================
--- messageboard/trunk/step10/browser/message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,70 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser Views for IMessage
+
+$Id: message.py,v 1.3 2003/12/13 17:24:36 srichter Exp $
+"""
+from zope.i18n import MessageIDFactory
+
+from zope.app import zapi
+from zope.app.dublincore.interfaces import ICMFDublinCore
+
+from book.messageboard.interfaces import IMessage
+from book.messageboard.interfaces import IMailSubscriptions
+
+_ = MessageIDFactory('messageboard')
+
+class MessageDetails:
+
+    def author(self):
+        """Get user who last modified the Message."""
+        creators = ICMFDublinCore(self.context).creators
+        if not creators:
+            return _('unknown')
+        return creators[0]
+
+    def modified(self):
+        """Get last modification date."""
+        date = ICMFDublinCore(self.context).modified
+        if date is None:
+            date = ICMFDublinCore(self.context).created
+        if date is None:
+            return ''
+        formatter = self.request.locale.dates.getFormatter('dateTime', 'short')
+        return formatter.format(date)
+
+    def parent_info(self):
+        """Get the parent of the message"""
+        parent = zapi.getParent(self.context)
+        if not IMessage.providedBy(parent):
+            return None
+        return {'name': zapi.name(parent), 'title': parent.title}
+
+
+class MailSubscriptions:
+
+    def subscriptions(self):
+        return IMailSubscriptions(self.context).getSubscriptions()
+
+    def change(self):
+        if 'ADD' in self.request:
+            emails = self.request['emails'].split('\n')
+            IMailSubscriptions(self.context).addSubscriptions(emails)
+        elif 'REMOVE' in self.request:
+            emails = self.request['remails']
+            if isinstance(emails, (str, unicode)):
+                emails = [emails]
+            IMailSubscriptions(self.context).removeSubscriptions(emails)
+
+        self.request.response.redirect('./@@subscriptions.html')

Added: messageboard/trunk/step10/browser/messageboard.png
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step10/browser/messageboard.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step10/browser/messageboard.py
===================================================================
--- messageboard/trunk/step10/browser/messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,126 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser Views for IMessageBoard
+
+$Id$
+"""
+import os
+from zope.proxy import removeAllProxies
+
+from zope.app import zapi
+from zope.app.registration.interfaces import ActiveStatus
+from zope.app.site.interfaces import ISite
+from zope.app.site.service import SiteManager, ServiceRegistration
+from zope.app.utility.utility import LocalUtilityService, UtilityRegistration
+from zope.app.workflow.interfaces import IProcessDefinitionImportHandler
+from zope.app.workflow.interfaces import IProcessInstanceContainer
+from zope.app.workflow.stateful.contentworkflow import ContentWorkflowsManager
+from zope.app.workflow.stateful.definition import StatefulProcessDefinition
+from zope.app.workflow.stateful.interfaces import IContentWorkflowsManager
+from zope.app.workflow.stateful.interfaces import IStatefulProcessDefinition
+
+import book.messageboard
+from book.messageboard.interfaces import IMessage
+
+
+class AddMessageBoard(object):
+    """Add a message board."""
+  
+    def createAndAdd(self, data):
+        content = super(AddMessageBoard, self).createAndAdd(data)
+  
+        if self.request.get('workflow'):
+            folder = removeAllProxies(zapi.getParent(content))
+            if not ISite.providedBy(folder):
+                sm = SiteManager(folder)
+                folder.setSiteManager(sm)
+            default = zapi.traverse(folder.getSiteManager(), 'default')
+ 
+            # Create Local Utility Service
+            default['Utilities'] = LocalUtilityService()
+            rm = default.getRegistrationManager()
+            registration = ServiceRegistration(zapi.servicenames.Utilities,
+                                               'Utilities', rm)
+            key = rm.addRegistration(registration)
+            zapi.traverse(rm, key).status = ActiveStatus
+
+            # Create the process definition
+            default['publish-message'] = StatefulProcessDefinition()
+            pd_path = zapi.getPath(default['publish-message'])
+            registration = UtilityRegistration(
+                'publish-message', IStatefulProcessDefinition, pd_path)
+            pd_id = rm.addRegistration(registration)
+            zapi.traverse(rm, pd_id).status = ActiveStatus
+
+            import_util = IProcessDefinitionImportHandler(
+                default['publish-message'])
+            
+            xml = os.path.join(
+                os.path.dirname(book.messageboard.__file__), 'workflow.xml')
+                
+            import_util.doImport(open(xml, mode='r').read())
+  
+            # Create Content Workflows Manager
+            default['ContentWorkflows'] = ContentWorkflowsManager()
+            cm_path = zapi.getPath(default['ContentWorkflows'])
+            registration = UtilityRegistration(
+                'wfcontentmgr', IContentWorkflowsManager, cm_path)
+            cm_id = rm.addRegistration(registration)
+            zapi.traverse(rm, cm_id).status = ActiveStatus
+
+            contentmgr = default['ContentWorkflows']
+            contentmgr.register(IMessage, 'publish-message')
+
+        return content
+
+
+class ReviewMessages:
+    """Workflow: Review all pending messages"""
+
+    def getPendingMessages(self, pmsg):
+        """Get all pending messages recursively."""
+        msgs = []
+        for name, msg in pmsg.items():
+            if IMessage.providedBy(msg):
+                if hasMessageStatus(msg, 'pending'):
+                    msgs.append(msg)
+                msgs += self.getPendingMessages(msg)
+        return msgs
+
+    def getPendingMessagesInfo(self):
+        """Get all the display info for pending messages"""
+        msg_infos = []
+        for msg in self.getPendingMessages(self.context):
+            info = {}
+            info['title'] = msg.title
+            info['url'] = zapi.getView(
+                msg, 'absolute_url', self.request)() + '/@@workflows.html'
+            msg_infos.append(info)
+        return msg_infos
+
+
+def hasMessageStatus(msg, status, workflow='publish-message'):
+    """Check whether a particular message matches a given status"""
+    adapter = IProcessInstanceContainer(msg)
+    if adapter:
+        # No workflow is defined, so the message is always shown.
+        if not adapter.keys():
+            return True
+        for item in adapter.values():
+            if item.processDefinitionName != workflow:
+                continue
+            if item.status == status:
+                return True
+
+    return False

Added: messageboard/trunk/step10/browser/messageboard_add.pt
===================================================================
--- messageboard/trunk/step10/browser/messageboard_add.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/messageboard_add.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,25 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <div metal:use-macro="views/form_macros/addform">
+
+        <div metal:fill-slot="extra_bottom" class="row">
+          <div class="field">
+            <h3><input type="checkbox" name="workflow:int" 
+                    value="1" checked=""/>
+              <span i18n:translate="">Create Workflow</span>
+            </h3>
+            <span i18n:translate="">Without the workflow you will 
+              not be able to review messages before they are 
+              published. Note that you can always modify the 
+              messageboard workflow later to make all transitions 
+              automatically.</span>
+          </div>
+        </div>
+
+      </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step10/browser/review.pt
===================================================================
--- messageboard/trunk/step10/browser/review.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/review.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,16 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Pending Messages</h1>
+
+      <div class="row" tal:repeat="msg view/getPendingMessagesInfo">
+        <div class="field">
+          <a href="" tal:attributes="href msg/url"
+              tal:content="msg/title" />
+        </div>
+      </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step10/browser/subscriptions.pt
===================================================================
--- messageboard/trunk/step10/browser/subscriptions.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/subscriptions.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,43 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <form action="changeSubscriptions.html" method="post">
+
+        <div class="row">
+            <div class="label" 
+                i18n:translate="">Current Subscriptions</div>
+            <div class="field">
+           <div tal:repeat="email view/subscriptions">
+                <input type="checkbox" name="remails:list" 
+                       value="" tal:attributes="value email">
+                <div tal:replace="email">zope3 at zope3.org</div>
+              </div>
+              <input type="submit" name="REMOVE" value="Remove" 
+                   i18n:attributes="value remove-button">
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">
+              Enter new Users (separate by 'Return')
+            </div>
+            <div class="field">
+           <textarea name="emails" cols="40" rows="10"></textarea>
+            </div>
+        </div>
+
+             <div class="row">
+               <div class="controls">
+                 <input type="submit" value="Refresh" 
+                i18n:attributes="value refresh-button" />
+                 <input type="submit" name="ADD" value="Add" 
+                     i18n:attributes="value add-button" />
+               </div>
+             </div>
+
+      </form>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step10/browser/subthread.pt
===================================================================
--- messageboard/trunk/step10/browser/subthread.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/subthread.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,8 @@
+<ul>
+  <li tal:repeat="item view/listContentInfo">
+    <a href="" 
+        tal:attributes="href item/url"
+        tal:content="item/title">Message 1</a>
+    <div tal:replace="structure item/thread"/>
+  </li>
+</ul>
\ No newline at end of file

Added: messageboard/trunk/step10/browser/tests/__init__.py
===================================================================
--- messageboard/trunk/step10/browser/tests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/tests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step10/browser/tests/test_widgets.py
===================================================================
--- messageboard/trunk/step10/browser/tests/test_widgets.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/tests/test_widgets.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,76 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""HTMLSourceWidget Tests
+
+$Id: test_widgets.py,v 1.1 2003/06/10 14:40:44 srichter Exp $
+"""
+import unittest
+from zope.app.form.browser.tests.test_textareawidget import TextAreaWidgetTest
+from book.messageboard.browser.widgets import HTMLSourceWidget
+from book.messageboard.fields import HTML
+
+class HTMLSourceWidgetTest(TextAreaWidgetTest):
+
+    _FieldFactory = HTML
+    _WidgetFactory = HTMLSourceWidget
+
+
+    def test_AllowedTagsConvert(self):
+        widget = self._widget
+        widget.context.allowed_tags=('h1','pre')
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1>Blah</h1>')) 
+        self.assertEqual(u'<pre>Blah</pre>',
+                         widget._toFieldValue(u'<pre>Blah</pre>') )
+        self.assertEqual(u'<h1><pre>Blah</pre></h1>',
+                         widget._toFieldValue(u'<h1><pre>Blah</pre></h1>')) 
+        self.assertEqual(u'<h1 attr=".">Blah</h1>',
+                         widget._toFieldValue(u'<h1 attr=".">Blah</h1>')) 
+
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2>Blah</h2>')) 
+        self.assertEqual(u'<pre>Blah</pre>',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2 a="b">Blah</h2>')) 
+
+
+    def test_ForbiddenTagsConvert(self):
+        widget = self._widget
+        widget.context.forbidden_tags=('h2','pre')
+
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1>Blah</h1>')) 
+        self.assertEqual(u'<h1 a="b">Blah</h1>',
+                         widget._toFieldValue(u'<h1 a="b">Blah</h1>')) 
+
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2>Blah</h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<pre>Blah</pre>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1><pre>Blah</pre></h1>')) 
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(HTMLSourceWidgetTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step10/browser/thread.pt
===================================================================
--- messageboard/trunk/step10/browser/thread.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/thread.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,11 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Discussion Thread</h1>
+
+      <div tal:replace="structure view/subthread" />
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step10/browser/thread.py
===================================================================
--- messageboard/trunk/step10/browser/thread.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/thread.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,45 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser View for the sub-thread of a Message or MessageBoard
+
+$Id$
+"""
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+
+from book.messageboard.interfaces import IMessage
+from messageboard import hasMessageStatus
+
+class Thread:
+
+    def __init__(self, context, request, base_url=''):
+        self.context = context
+        self.request = request
+        self.base_url = base_url
+
+    def listContentInfo(self):
+        children = []
+        for name, child in self.context.items():
+            if IMessage.providedBy(child) and \
+                   hasMessageStatus(child, 'published'):
+                info = {}
+                info['title'] = child.title
+                url = self.base_url + name + '/'
+                info['url'] = url + '@@thread.html'
+                thread = Thread(child, self.request, url)
+                info['thread'] = thread.subthread()
+                children.append(info)
+        return children
+
+    subthread = ViewPageTemplateFile('subthread.pt')
+

Added: messageboard/trunk/step10/browser/widgets.py
===================================================================
--- messageboard/trunk/step10/browser/widgets.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/browser/widgets.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Module containing custom widget definitions.
+
+$Id: widgets.py,v 1.1 2003/06/10 14:40:44 srichter Exp $
+"""
+import re
+from zope.app.form.browser import TextAreaWidget
+from book.messageboard.fields import forbidden_regex, allowed_regex
+
+class HTMLSourceWidget(TextAreaWidget):
+
+    def _toFieldValue(self, input):
+        input = super(HTMLSourceWidget, self)._toFieldValue(input)
+
+        if self.context.forbidden_tags:
+            regex = forbidden_regex %'|'.join(
+                self.context.forbidden_tags)
+            input = re.sub(regex, '', input)
+
+        if self.context.allowed_tags:
+            regex = allowed_regex %'[ />]|'.join(
+                self.context.allowed_tags)
+            input = re.sub(regex, '', input)
+
+        return input
+

Added: messageboard/trunk/step10/configure.zcml
===================================================================
--- messageboard/trunk/step10/configure.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/configure.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,174 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    xmlns:mail="http://namespaces.zope.org/mail"
+    i18n_domain="messageboard">
+
+  <permission
+      id="book.messageboard.View"
+      title="View Message Board and Messages"
+      description="View the Message Board and all its content."
+      />
+  <permission
+      id="book.messageboard.Add"
+      title="Add Message"
+      description="Add Message."
+      />
+  <permission
+      id="book.messageboard.Edit"
+      title="Edit Messages"
+      description="Edit Messages."
+      />
+  <permission
+      id="book.messageboard.Delete"
+      title="Delete Message"
+      description="Delete Message."
+      />
+
+  <permission
+      id="book.messageboard.PublishContent"
+      title="Publish Message"
+      description="Publish Message."/>
+
+  <interface 
+      interface=".interfaces.IMessageBoard" 
+      type="zope.app.content.interfaces.IContentType"
+      /> 
+
+  <content class=".messageboard.MessageBoard">
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContentContainer" 
+        />
+    <factory
+        id="book.messageboard.MessageBoard"
+        description="Message Board" 
+        />
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessageBoard"
+        />
+    <require
+        permission="book.messageboard.Edit"
+        set_schema=".interfaces.IMessageBoard"
+        />
+  </content>
+
+  <adapter
+      factory=".messageboard.PlainText"
+      provides=".interfaces.IPlainText"
+      for=".interfaces.IMessageBoard" />
+
+  <adapter
+     for=".interfaces.IMessageBoard"
+     provides="zope.app.filerepresentation.interfaces.IReadDirectory"
+     factory=".filerepresentation.ReadDirectory"
+     permission="zope.View" />
+
+  <adapter
+     for=".interfaces.IMessageBoard"
+     provides="zope.app.filerepresentation.interfaces.IDirectoryFactory"
+     factory=".filerepresentation.MessageFactory"
+     permission="zope.View" />
+
+  <interface 
+      interface=".interfaces.IMessage" 
+      type="zope.app.content.interfaces.IContentType"
+      /> 
+
+  <content class=".message.Message">
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContentContainer" 
+        />
+    <implements interface=
+        "zope.app.workflow.interfaces.IProcessInstanceContainerAdaptable"/>
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessage"
+        />
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessageContainer"
+        />
+    <require
+        permission="book.messageboard.Add"
+        set_schema=".interfaces.IMessage"
+        />
+  </content>
+
+  <adapter
+      factory=".message.MessageSized"
+      provides="zope.app.size.interfaces.ISized"
+      for=".interfaces.IMessage"
+      />
+
+  <adapter
+      factory=".message.PlainText"
+      provides=".interfaces.IPlainText"
+      for=".interfaces.IMessage" />
+
+  <adapter
+     for=".interfaces.IMessage"
+     provides="zope.app.filerepresentation.interfaces.IReadDirectory"
+     factory=".filerepresentation.ReadDirectory"
+     permission="zope.View" />
+
+  <adapter
+     for=".interfaces.IMessage"
+     provides="zope.app.filerepresentation.interfaces.IDirectoryFactory"
+     factory=".filerepresentation.MessageFactory"
+     permission="zope.View" />
+
+  <adapter
+      factory=".message.MailSubscriptions"
+      provides=".interfaces.IMailSubscriptions"
+      for=".interfaces.IMessage"
+      permission="book.messageboard.Add"      
+      trusted="true" />
+
+  <content class=".filerepresentation.VirtualContentsFile">
+  
+    <implements interface="
+        zope.app.annotation.interfaces.IAttributeAnnotatable" />
+  
+    <require
+        permission="book.messageboard.View"
+        interface="zope.app.filerepresentation.interfaces.IReadFile" />
+  
+    <require
+        permission="book.messageboard.Edit"
+        interface="zope.app.filerepresentation.interfaces.IWriteFile"
+        set_schema="zope.app.filerepresentation.interfaces.IReadFile" />
+  
+  </content>
+
+  <mail:smtpMailer name="msgboard-smtp" hostname="localhost" port="25" />
+  
+  <mail:queuedDelivery 
+      name="msgboard-delivery"
+      permission="zope.SendMail"
+      queuePath="./mail-queue"
+      mailer="msgboard-smtp" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.event.interfaces.IObjectModifiedEvent" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.container.interfaces.IObjectAddedEvent" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.container.interfaces.IObjectRemovedEvent" />
+
+  <i18n:registerTranslations directory="locales" />
+
+  <include package=".browser" />
+
+</configure>

Added: messageboard/trunk/step10/fields.py
===================================================================
--- messageboard/trunk/step10/fields.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/fields.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Module containing custom field definitions.
+
+$Id$
+"""
+import re
+
+from zope.i18n import MessageIDFactory
+from zope.schema import Text
+from zope.schema.interfaces import ValidationError
+
+_ = MessageIDFactory('messageboard')
+
+forbidden_regex = r'</?(?:%s).*?/?>'
+allowed_regex = r'</??(?!%s[ />])[a-zA-Z0-9]*? ?(?:[a-z0-9]*?=?".*?")*/??>'
+
+class ForbiddenTags(ValidationError):
+    __doc__ = _("Forbidden HTML Tags used.")
+
+
+class HTML(Text):
+  
+    allowed_tags = ()
+    forbidden_tags = ()
+
+    def __init__(self, allowed_tags=(), forbidden_tags=(), **kw):
+        self.allowed_tags = allowed_tags
+        self.forbidden_tags = forbidden_tags
+        super(HTML, self).__init__(**kw)
+
+    def _validate(self, value):
+        super(HTML, self)._validate(value)
+
+        if self.forbidden_tags:
+            regex = forbidden_regex %'|'.join(self.forbidden_tags)
+            if re.findall(regex, value):
+                raise ForbiddenTags(value, self.forbidden_tags)
+
+        if self.allowed_tags:
+            regex = allowed_regex %'[ />]|'.join(self.allowed_tags)
+            if re.findall(regex, value):
+                raise ForbiddenTags(value, self.allowed_tags)
+
+

Added: messageboard/trunk/step10/filerepresentation.py
===================================================================
--- messageboard/trunk/step10/filerepresentation.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/filerepresentation.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,98 @@
+##############################################################################
+#
+# Copyright (c) 2003, 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""FTP Views for the MessageBoard and Message component
+
+$Id$
+"""
+from zope.interface import implements
+from interfaces import IVirtualContentsFile, IPlainText
+
+from zope.app.filerepresentation.interfaces import IReadDirectory
+from zope.app.folder.filerepresentation import \
+     ReadDirectory as ReadDirectoryBase
+from zope.app.filerepresentation.interfaces import IDirectoryFactory
+
+from message import Message
+
+
+class VirtualContentsFile(object):
+
+    implements(IVirtualContentsFile)
+
+    def __init__(self, context):
+        self.context = context
+
+    def setContentType(self, contentType):
+        '''See interface IFile'''
+        pass
+
+    def getContentType(self):
+        '''See interface IFile'''
+        return u'text/plain'
+
+    contentType = property(getContentType, setContentType)
+
+    def edit(self, data, contentType=None):
+        '''See interface IFile'''
+        self.setData(data)
+
+    def getData(self):
+        '''See interface IFile'''
+        adapter = IPlainText(self.context)
+        return adapter.getText() or u''
+
+    def setData(self, data):
+        '''See interface IFile'''
+        adapter = IPlainText(self.context)
+        return adapter.setText(data)
+
+    data = property(getData, setData)
+
+    def getSize(self):
+        '''See interface IFile'''
+        return len(self.getData())
+
+    size = property(getSize)
+
+
+class ReadDirectory(ReadDirectoryBase):
+    """An special implementation of the directory."""
+    
+    implements(IReadDirectory)
+
+    def keys(self):
+        keys = self.context.keys()
+        return list(keys) + ['contents']
+
+    def get(self, key, default=None):
+        if key == 'contents':
+            return VirtualContentsFile(self.context)
+        return self.context.get(key, default)
+
+    def __len__(self):
+        l = len(self.context)
+        return l+1
+
+
+class MessageFactory(object):
+    """A simple message factory for file system representations."""
+
+    implements(IDirectoryFactory)
+
+    def __init__(self, context):
+        self.context = context
+
+    def __call__(self, name):
+        """See IDirectoryFactory interface."""
+        return Message()

Added: messageboard/trunk/step10/interfaces.py
===================================================================
--- messageboard/trunk/step10/interfaces.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/interfaces.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,136 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Interfaces
+
+Interfaces for the Zope 3 based Message Board Package
+
+$Id$
+"""
+from zope.i18n import MessageIDFactory
+from zope.interface import classImplements, Interface
+from zope.schema import Text, TextLine, Field, Tuple
+from zope.schema.interfaces import IText
+
+from zope.app.container.constraints import ContainerTypesConstraint
+from zope.app.container.constraints import ItemTypePrecondition
+from zope.app.container.interfaces import IContained, IContainer
+from zope.app.file.interfaces import IFile, IFileContent
+
+from fields import HTML
+
+_ = MessageIDFactory('messageboard')
+
+
+class IMessage(Interface):
+    """A message object."""
+
+    title = TextLine(
+        title=_("Title/Subject"),
+        description=_("Title and/or subject of the message."),
+        default=u"",
+        required=True)
+
+    body = HTML(
+        title=_("Message Body"),
+        description=_("This is the actual message. Type whatever!"),
+        default=u"",
+        allowed_tags=('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'a',
+                      'br', 'b', 'i', 'u', 'em', 'sub', 'sup',
+                      'table', 'tr', 'td', 'th', 'code', 'pre',
+                      'center', 'div', 'span', 'p', 'font', 'ol',
+                      'ul', 'li', 'q', 's', 'strong'),
+        required=False)
+
+
+class IMessageBoard(IContainer):
+    """The message board is the base object for our package. It can only
+    contain IMessage objects."""
+
+    def __setitem__(name, object):
+        """Add a IMessage object."""
+
+    __setitem__.precondition = ItemTypePrecondition(IMessage)
+
+    description = Text(
+        title=_("Description"),
+        description=_("A detailed description of the content of the board."),
+        default=u"",
+        required=False)
+
+
+class IMessageContained(IContained):
+    """Interface that specifies the type of objects that can contain
+    messages."""
+    __parent__ = Field(
+        constraint = ContainerTypesConstraint(IMessageBoard, IMessage))
+
+
+class IMessageContainer(IContainer):
+    """We also want to make the message object a container that can contain
+    responses (other messages) and attachments (files and images)."""
+
+    def __setitem__(name, object):
+        """Add a IMessage object."""
+
+    __setitem__.precondition = ItemTypePrecondition(IMessage, IFile)
+
+
+class IHTML(IText):
+    """A text field that handles HTML input."""
+
+    allowed_tags = Tuple(
+        title=_("Allowed HTML Tags"),
+        description=_("""\
+        Only listed tags can be used in the value of the field.
+        """),
+        required=False)
+
+    forbidden_tags = Tuple(
+        title=_("Forbidden HTML Tags"),
+        description=_("""\
+        Listed tags cannot be used in the value of the field.
+        """),
+        required=False)
+
+classImplements(HTML, IHTML)
+
+
+class IMailSubscriptions(Interface):
+    """This interface allows you to retrieve a list of E-mails for
+    mailings. In our context these are messages."""
+
+    def getSubscriptions():
+        """Return a list of E-mails."""
+
+    def addSubscriptions(emails):
+        """Add a bunch of subscriptions; one would be okay too."""
+
+    def removeSubscriptions(emails):
+        """Remove a set of subscriptions."""
+
+
+class IPlainText(Interface):
+    """This interface allows you to represent an object's content in plain
+    text."""
+
+    def getText():
+        """Get a pure text representation of the object's content."""
+
+    def setText(text):
+        """Write the text to the object."""
+
+
+class IVirtualContentsFile(IFile, IFileContent):
+    """Marker Interface to mark special Message and Message Board 
+    Contents files in FS representations."""

Added: messageboard/trunk/step10/locales/de/LC_MESSAGES/messageboard.mo
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step10/locales/de/LC_MESSAGES/messageboard.mo
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step10/locales/de/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step10/locales/de/LC_MESSAGES/messageboard.po	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/locales/de/LC_MESSAGES/messageboard.po	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,295 @@
+# translation of messageboard.po to German
+# translation of messageboard.po to English US
+# Copyright (C) YEAR ORGANIZATION.
+# Stephan Richter <stephan.richter at tufts.edu>, 2003.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"POT-Creation-Date: Mon Dec 15 09:36:12 2003\n"
+"PO-Revision-Date: 2003-12-15 00:14-0500\n"
+"Last-Translator: Stephan Richter <stephan.richter at tufts.edu>\n"
+"Language-Team: German <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.4\n"
+"X-Generator: KBabel 1.3\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+msgid "add-button"
+msgstr "  Add  "
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr "Add Content"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:109
+msgid "Menu of objects to be added to Messages."
+msgstr "Menu of objects to be added to Messages."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+#: src/zopeproducts/messageboard/browser/configure.zcml:140
+msgid "Change Message"
+msgstr "Change Message"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:148
+msgid "Preview"
+msgstr "Preview"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:156
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr "Message Details"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:16
+msgid "Menu of objects to be added to Message Boards."
+msgstr "Menu of objects to be added to Message Boards."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:184
+msgid "Subscriptions"
+msgstr "Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:189
+msgid "Message Mail Subscriptions"
+msgstr "Message Mail Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:20
+#: src/zopeproducts/messageboard/browser/configure.zcml:113
+msgid "Add"
+msgstr "Add"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:203
+msgid "Image"
+msgstr "Image"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:209
+msgid "File"
+msgstr "File"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Add Message Board"
+msgstr "Add Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+msgid "Edit"
+msgstr "Edit"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:50
+msgid "Change Message Board"
+msgstr "Change Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:58
+#: src/zopeproducts/messageboard/browser/configure.zcml:164
+msgid "Thread"
+msgstr "Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:66
+#: src/zopeproducts/messageboard/browser/configure.zcml:172
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr "Discussion Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:74
+#: src/zopeproducts/messageboard/browser/configure.zcml:215
+msgid "Contents"
+msgstr "Contents"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:8
+msgid "Message Board Help"
+msgstr "Message Board Help"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:82
+msgid "Review Messages"
+msgstr "Review Messages"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:90
+msgid "Publication Review"
+msgstr "Publication Review"
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr "Author"
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr "Date/Time"
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr "Parent"
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr "Body"
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr "Title"
+
+# 8/browser/messageboard_add.pt:19
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr "Workflow erstellen"
+
+# 8/browser/messageboard_add.pt:21
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+msgstr ""
+"Ohne dem Workflow wirst Du nicht in der Lage sein Anzeigen vor der "
+"Veröffentlichung zu prüfen. Es ist zu erwähnen das Du jederzeit den Workflow "
+"später ändern kannst, so dass all Übergänge automatisch ausgeführt werden."
+
+# 7/browser/subscriptions.pt:35
+# Default: "Refresh"
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+msgid "refresh-button"
+msgstr "refresh-button"
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+msgid "submit-button"
+msgstr "Abschicken"
+
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr "Schwebende Anzeigen"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr "Current Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+msgid "remove-button"
+msgstr "   Add  "
+
+# 7/browser/subscriptions.pt:25
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr "Enter new Users (separate by 'Return')"
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Users that actually use the Message Board."
+msgstr "Users that actually use the Message Board."
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Message Board User"
+msgstr "Message Board User"
+
+#: src/zopeproducts/messageboard/configure.zcml:108
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+#: src/zopeproducts/messageboard/browser/configure.zcml:197
+msgid "Message"
+msgstr "Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "Message Board Editor"
+msgstr "Message Board Editor"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "The Editor can edit and delete Messages."
+msgstr "The Editor can edit and delete Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View the Message Board and all its content."
+msgstr "View the Message Board and all its content."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View Message Board and Messages"
+msgstr "View Message Board and Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+msgid "Add Message."
+msgstr "Add Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+msgid "Add Message"
+msgstr "Add Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages."
+msgstr "Edit Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages"
+msgstr "Edit Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message."
+msgstr "Delete Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message"
+msgstr "Delete Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message"
+msgstr "Publish Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message."
+msgstr "Publish Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:77
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Message Board"
+msgstr "Message Board"
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr "Forbidden HTML Tags used."
+
+#: src/zopeproducts/messageboard/interfaces.py:112
+msgid "Allowed HTML Tags"
+msgstr "Allowed HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:113
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:119
+msgid "Forbidden HTML Tags"
+msgstr "Forbidden HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:120
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "Description"
+msgstr "Description"
+
+#: src/zopeproducts/messageboard/interfaces.py:40
+msgid "A detailed description of the content of the board."
+msgstr "A detailed description of the content of the board."
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title/Subject"
+msgstr "Title/Subject"
+
+#: src/zopeproducts/messageboard/interfaces.py:50
+msgid "Title and/or subject of the message."
+msgstr "Title and/or subject of the message."
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "Message Body"
+msgstr "Message Body"
+
+#: src/zopeproducts/messageboard/interfaces.py:56
+msgid "This is the actual message. Type whatever!"
+msgstr "This is the actual message. Type whatever!"

Added: messageboard/trunk/step10/locales/en/LC_MESSAGES/messageboard.mo
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step10/locales/en/LC_MESSAGES/messageboard.mo
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step10/locales/en/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step10/locales/en/LC_MESSAGES/messageboard.po	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/locales/en/LC_MESSAGES/messageboard.po	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,297 @@
+# translation of messageboard.po to German
+# translation of messageboard.po to English US
+# Copyright (C) YEAR ORGANIZATION.
+# Stephan Richter <stephan.richter at tufts.edu>, 2003.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"POT-Creation-Date: Mon Dec 15 09:36:12 2003\n"
+"PO-Revision-Date: 2003-12-15 00:16-0500\n"
+"Last-Translator: Stephan Richter <stephan.richter at tufts.edu>\n"
+"Language-Team: German <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.4\n"
+"X-Generator: KBabel 1.3\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+msgid "add-button"
+msgstr "  Add  "
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr "Add Content"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:109
+msgid "Menu of objects to be added to Messages."
+msgstr "Menu of objects to be added to Messages."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+#: src/zopeproducts/messageboard/browser/configure.zcml:140
+msgid "Change Message"
+msgstr "Change Message"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:148
+msgid "Preview"
+msgstr "Preview"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:156
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr "Message Details"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:16
+msgid "Menu of objects to be added to Message Boards."
+msgstr "Menu of objects to be added to Message Boards."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:184
+msgid "Subscriptions"
+msgstr "Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:189
+msgid "Message Mail Subscriptions"
+msgstr "Message Mail Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:20
+#: src/zopeproducts/messageboard/browser/configure.zcml:113
+msgid "Add"
+msgstr "Add"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:203
+msgid "Image"
+msgstr "Image"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:209
+msgid "File"
+msgstr "File"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Add Message Board"
+msgstr "Add Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+msgid "Edit"
+msgstr "Edit"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:50
+msgid "Change Message Board"
+msgstr "Change Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:58
+#: src/zopeproducts/messageboard/browser/configure.zcml:164
+msgid "Thread"
+msgstr "Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:66
+#: src/zopeproducts/messageboard/browser/configure.zcml:172
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr "Discussion Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:74
+#: src/zopeproducts/messageboard/browser/configure.zcml:215
+msgid "Contents"
+msgstr "Contents"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:8
+msgid "Message Board Help"
+msgstr "Message Board Help"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:82
+msgid "Review Messages"
+msgstr "Review Messages"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:90
+msgid "Publication Review"
+msgstr "Publication Review"
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr "Author"
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr "Date/Time"
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr "Parent"
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr "Body"
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr "Title"
+
+# 8/browser/messageboard_add.pt:19
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr "Create Workflow"
+
+# 8/browser/messageboard_add.pt:21
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+msgstr ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+
+# 8/browser/messageboard_add.pt:30
+# 8/browser/subscriptions.pt:35
+# Default: "Refresh"
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+msgid "refresh-button"
+msgstr "Refresh"
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+msgid "submit-button"
+msgstr "Submit"
+
+# 8/browser/review.pt:5
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr "Pending Messages"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr "Current Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+msgid "remove-button"
+msgstr "Remove"
+
+# 8/browser/subscriptions.pt:25
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr "Enter new Users (separate by 'Return')"
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Users that actually use the Message Board."
+msgstr "Users that actually use the Message Board."
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Message Board User"
+msgstr "Message Board User"
+
+#: src/zopeproducts/messageboard/configure.zcml:108
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+#: src/zopeproducts/messageboard/browser/configure.zcml:197
+msgid "Message"
+msgstr "Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "Message Board Editor"
+msgstr "Message Board Editor"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "The Editor can edit and delete Messages."
+msgstr "The Editor can edit and delete Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View the Message Board and all its content."
+msgstr "View the Message Board and all its content."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View Message Board and Messages"
+msgstr "View Message Board and Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+msgid "Add Message."
+msgstr "Add Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+msgid "Add Message"
+msgstr "Add Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages."
+msgstr "Edit Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages"
+msgstr "Edit Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message."
+msgstr "Delete Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message"
+msgstr "Delete Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message"
+msgstr "Publish Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message."
+msgstr "Publish Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:77
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Message Board"
+msgstr "Message Board"
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr "Forbidden HTML Tags used."
+
+#: src/zopeproducts/messageboard/interfaces.py:112
+msgid "Allowed HTML Tags"
+msgstr "Allowed HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:113
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:119
+msgid "Forbidden HTML Tags"
+msgstr "Forbidden HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:120
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "Description"
+msgstr "Description"
+
+#: src/zopeproducts/messageboard/interfaces.py:40
+msgid "A detailed description of the content of the board."
+msgstr "A detailed description of the content of the board."
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title/Subject"
+msgstr "Title/Subject"
+
+#: src/zopeproducts/messageboard/interfaces.py:50
+msgid "Title and/or subject of the message."
+msgstr "Title and/or subject of the message."
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "Message Body"
+msgstr "Message Body"
+
+#: src/zopeproducts/messageboard/interfaces.py:56
+msgid "This is the actual message. Type whatever!"
+msgstr "This is the actual message. Type whatever!"

Added: messageboard/trunk/step10/locales/messageboard.pot
===================================================================
--- messageboard/trunk/step10/locales/messageboard.pot	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/locales/messageboard.pot	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,292 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: Zope X3 Pre-M4\n"
+"POT-Creation-Date: Mon Dec 15 09:36:12 2003\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: Zope 3 Developers <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: ENCODING\n"
+"Generated-By: zope/app/translation_files/extract.py\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+# Default: "Add"
+msgid "add-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:109
+msgid "Menu of objects to be added to Messages."
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+#: src/zopeproducts/messageboard/browser/configure.zcml:140
+msgid "Change Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:148
+msgid "Preview"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:156
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:16
+msgid "Menu of objects to be added to Message Boards."
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:184
+msgid "Subscriptions"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:189
+msgid "Message Mail Subscriptions"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:20
+#: src/zopeproducts/messageboard/browser/configure.zcml:113
+msgid "Add"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:203
+msgid "Image"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:209
+msgid "File"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Add Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+msgid "Edit"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:50
+msgid "Change Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:58
+#: src/zopeproducts/messageboard/browser/configure.zcml:164
+msgid "Thread"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:66
+#: src/zopeproducts/messageboard/browser/configure.zcml:172
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:74
+#: src/zopeproducts/messageboard/browser/configure.zcml:215
+msgid "Contents"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:8
+msgid "Message Board Help"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:82
+msgid "Review Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:90
+msgid "Publication Review"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid "Without the workflow you will not be able to review messages before they are published. Note that you can always modify the messageboard workflow later to make all transitions automatically."
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+# Default: "Refresh"
+msgid "refresh-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+# Default: "Submit"
+msgid "submit-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+# Default: "Remove"
+msgid "remove-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Users that actually use the Message Board."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Message Board User"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:108
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+#: src/zopeproducts/messageboard/browser/configure.zcml:197
+msgid "Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "Message Board Editor"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "The Editor can edit and delete Messages."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View the Message Board and all its content."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View Message Board and Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+msgid "Add Message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+msgid "Add Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:77
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:112
+msgid "Allowed HTML Tags"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:113
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:119
+msgid "Forbidden HTML Tags"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:120
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "Description"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:40
+msgid "A detailed description of the content of the board."
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title/Subject"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:50
+msgid "Title and/or subject of the message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "Message Body"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:56
+msgid "This is the actual message. Type whatever!"
+msgstr ""
+

Added: messageboard/trunk/step10/message.py
===================================================================
--- messageboard/trunk/step10/message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,381 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Implementation
+
+An implementation of the Message using BTreeContainers as base.
+
+$Id: message.py,v 1.1 2003/06/07 11:24:48 srichter Exp $
+"""
+from zope.i18n import MessageIDFactory
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.container.btree import BTreeContainer
+from zope.app.container.interfaces import IObjectAddedEvent
+from zope.app.container.interfaces import IObjectRemovedEvent
+from zope.app.event.interfaces import IObjectModifiedEvent
+from zope.app.mail.interfaces import IMailDelivery
+from zope.app.size.interfaces import ISized
+
+from book.messageboard.interfaces import IMessage
+from book.messageboard.interfaces import IMessageContained, IMessageContainer
+from book.messageboard.interfaces import IMailSubscriptions
+from book.messageboard.interfaces import IPlainText
+
+_ = MessageIDFactory('messageboard')
+
+
+class Message(BTreeContainer):
+    """A simple implementation of a message.
+
+    Make sure that the ``Message`` implements the ``IMessage`` interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMessage, Message)
+    True
+
+    Here is an example of changing the title and description of the message:
+
+    >>> message = Message()
+    >>> message.title
+    u''
+    >>> message.body
+    u''
+    >>> message.title = u'Message Title'
+    >>> message.body = u'Message Body'
+    >>> message.title
+    u'Message Title'
+    >>> message.body
+    u'Message Body'
+    """
+    implements(IMessage, IMessageContained, IMessageContainer)
+
+    # See book.messageboard.interfaces.IMessage
+    title = u''
+
+    # See book.messageboard.interfaces.IMessage
+    body = u''
+
+  
+class MessageSized(object):
+
+    implements(ISized)
+    __used_for__ = IMessage
+
+    def __init__(self, message):
+        self._message = message
+
+    def sizeForSorting(self):
+        """See ISized
+
+        Create the adapter first.
+
+        >>> size = MessageSized(Message())
+
+        Here are some examples of the expected output.
+
+        >>> size.sizeForSorting()
+        ('item', 0)
+        >>> size._message['msg1'] = Message()
+        >>> size.sizeForSorting()
+        ('item', 1)
+        >>> size._message['att1'] = object()
+        >>> size.sizeForSorting()
+        ('item', 2)
+        """
+        return ('item', len(self._message))
+
+    def sizeForDisplay(self):
+        """See ISized
+
+        Creater the adapter first.
+
+        >>> size = MessageSized(Message())
+
+        Here are some examples of the expected output.
+
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 0, atts: 0'
+        >>> size._message['msg1'] = Message()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'1 reply, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 1, atts: 0'
+        >>> size._message['att1'] = object()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'1 reply, 1 attachment'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 1, atts: 1'
+        >>> size._message['msg2'] =  Message()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, 1 attachment'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 2, atts: 1'
+        >>> size._message['att2'] = object()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 2, atts: 2'
+        """
+        messages = 0
+        for obj in self._message.values():
+            if IMessage.providedBy(obj):
+                messages += 1
+
+        attachments = len(self._message)-messages
+
+        if messages == 1 and attachments == 1: 
+            size = _('1 reply, 1 attachment')
+        elif messages == 1 and attachments != 1:
+            size = _('1 reply, ${attachments} attachments')
+        elif messages != 1 and attachments == 1:
+            size = _('${messages} replies, 1 attachment')
+        else: 
+            size = _('${messages} replies, ${attachments} attachments')
+  
+        size.mapping = {'messages': `messages`, 'attachments': `attachments`}
+
+        return size
+
+
+SubscriberKey='http://www.zope.org/messageboard#1.0/MailSubscriptions/emails'
+
+
+class MailSubscriptions:
+    """Message Mail Subscriptions.
+
+    Verify the interface implementation
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMailSubscriptions, MailSubscriptions)
+    True
+
+    Create asubscription instance of a message
+
+    >>> msg = Message()
+    >>> sub = MailSubscriptions(msg)
+
+    Verify that we have initially no subscriptions and then add some.
+
+    >>> sub.getSubscriptions()
+    ()
+    >>> sub.addSubscriptions(('foo at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com',)
+    >>> sub.addSubscriptions(('blah at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com', 'blah at bar.com')
+    >>> sub.addSubscriptions(('doh at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com', 'blah at bar.com', 'doh at bar.com')
+
+    Now let's also check that we can remove entries.
+
+    >>> sub.removeSubscriptions(('foo at bar.com',))
+    >>> sub.getSubscriptions()
+    ('blah at bar.com', 'doh at bar.com')
+
+    When we construct a new mail subscription adapter instance, the values
+    should still be there.
+
+    >>> sub1 = MailSubscriptions(msg)
+    >>> sub1.getSubscriptions()
+    ('blah at bar.com', 'doh at bar.com')
+    """
+    implements(IMailSubscriptions)
+    __used_for__ = IMessage
+
+    def __init__(self, context):
+        self.context = context
+        self._annotations = IAnnotations(context)
+        if not self._annotations.get(SubscriberKey):
+            self._annotations[SubscriberKey] = ()
+
+    def getSubscriptions(self):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        return self._annotations[SubscriberKey]
+        
+    def addSubscriptions(self, emails):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        subscribers = list(self._annotations[SubscriberKey])
+        for email in emails:
+            if email not in subscribers:
+                subscribers.append(email.strip())
+        self._annotations[SubscriberKey] = tuple(subscribers)
+                
+    def removeSubscriptions(self, emails):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        subscribers = list(self._annotations[SubscriberKey])
+        for email in emails:
+            if email in subscribers:
+                subscribers.remove(email)
+        self._annotations[SubscriberKey] = tuple(subscribers)
+
+
+class MessageMailer:
+    """Class to handle all outgoing mail."""
+  
+    def __call__(self, event):
+        r"""Called by the event system.
+
+        Here is a demonstration on how the notification process and mail
+        sending works.
+
+        Before we can test this method, we have to create a mail delivery
+        object for testing.
+
+        >>> mail_result = [] 
+
+        >>> from zope.interface import implements
+        >>> from zope.app.mail.interfaces import IMailDelivery
+        
+        >>> class MailDeliveryStub(object):
+        ...     implements(IMailDelivery)
+        ... 
+        ...     def send(self, fromaddr, toaddrs, message):
+        ...         mail_result.append((fromaddr, toaddrs, message))
+
+        >>> from zope.app.tests import ztapi
+        >>> ztapi.provideUtility(IMailDelivery, MailDeliveryStub(),
+        ...                      name='msgboard-delivery')
+
+        Create a message.
+
+        >>> from zope.interface import directlyProvides
+        >>> from zope.app.traversing.interfaces import IContainmentRoot
+
+        >>> msg = Message()
+        >>> directlyProvides(msg, IContainmentRoot)
+        >>> msg.__name__ = 'msg'
+        >>> msg.__parent__ = None
+        >>> msg.title = 'Hello'
+        >>> msg.body = 'Hello World!'
+
+        Add a subscription to message.
+
+        >>> msg_sub = MailSubscriptions(msg)
+        >>> msg_sub.context.__annotations__[SubscriberKey] = ('foo at bar.com',)
+
+        Now, create an event and send it to the message mailer object.
+
+        >>> from zope.app.event.objectevent import ObjectModifiedEvent
+        >>> event = ObjectModifiedEvent(msg)
+        >>> mailer(event)
+
+        >>> from pprint import pprint
+        >>> pprint(mail_result)
+        [('mailer at messageboard.org',
+          ('foo at bar.com',),
+          'Subject: Modified: msg\n\n\nHello World!')]
+        """
+        if IMessage.providedBy(event.object):
+            if IObjectAddedEvent.providedBy(event):
+                self.handleAdded(event.object)
+            elif IObjectModifiedEvent.providedBy(event):
+                self.handleModified(event.object)
+            elif IObjectRemovedEvent.providedBy(event):
+                self.handleRemoved(event.object)
+  
+    def handleAdded(self, object):
+        subject = 'Added: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = object.body
+        self.mail(emails, subject, body)        
+  
+    def handleModified(self, object):
+        subject = 'Modified: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = object.body
+        self.mail(emails, subject, body)
+  
+    def handleRemoved(self, object):
+        subject = 'Removed: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = subject
+        self.mail(emails, subject, body)
+  
+    def getAllSubscribers(self, object):
+        """Retrieves all email subscribers.
+
+        Here a small demonstration of retrieving all subscribers.
+
+        >>> from zope.interface import directlyProvides
+        >>> from zope.app.traversing.interfaces import IContainmentRoot
+
+        Create a parent message as it would be located in the message
+        board. Also add a subscriber to the message.
+
+        >>> msg1 = Message()
+        >>> directlyProvides(msg1, IContainmentRoot)
+        >>> msg1.__name__ = 'msg1'
+        >>> msg1.__parent__ = None
+        >>> msg1_sub = MailSubscriptions(msg1)
+        >>> msg1_sub.context.__annotations__[SubscriberKey] = ('foo at bar.com',)
+
+        Create a reply to the first message and also give it a subscriber.
+       
+        >>> msg2 = Message()
+        >>> msg2_sub = MailSubscriptions(msg2)
+        >>> msg2_sub.context.__annotations__[SubscriberKey] = ('blah at bar.com',)
+        >>> msg1['msg2'] = msg2
+
+        When asking for all subscriptions of message 2, we should get the
+        subscriber from message 1 as well.
+
+        >>> mailer.getAllSubscribers(msg2)
+        ('blah at bar.com', 'foo at bar.com')
+        """
+        emails = ()
+        msg = object
+        while IMessage.providedBy(msg):
+            emails += tuple(IMailSubscriptions(msg).getSubscriptions())
+            msg = zapi.getParent(msg)
+        return emails
+  
+    def mail(self, toaddrs, subject, body):
+        """Mail out the Message Board change message."""
+        if not toaddrs:
+            return
+        msg = 'Subject: %s\n\n\n%s' %(subject, body)
+        mail_utility = zapi.getUtility(IMailDelivery, 'msgboard-delivery')
+        mail_utility.send('mailer at messageboard.org' , toaddrs, msg)
+  
+mailer = MessageMailer()
+
+
+class PlainText:
+
+    implements(IPlainText)
+
+    def __init__(self, context):
+        self.context = context
+
+    def getText(self):
+        return 'Title: %s\n\n%s' %(self.context.title, 
+                                   self.context.body)
+
+    def setText(self, text):
+        if text.startswith('Title: '):
+            title, text = text.split('\n', 1)
+            self.context.title = title[7:]
+
+        self.context.body = text.strip()

Added: messageboard/trunk/step10/messageboard.py
===================================================================
--- messageboard/trunk/step10/messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,62 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Implementation
+
+An implementation of the Message Board using BTreeContainers as base.
+
+$Id$
+"""
+from zope.interface import implements
+from zope.app.container.btree import BTreeContainer
+
+from book.messageboard.interfaces import IMessageBoard
+from book.messageboard.interfaces import IPlainText
+
+class MessageBoard(BTreeContainer):
+    """A very simple implementation of a message board using B-Tree Containers
+
+    Make sure that the ``MessageBoard`` implements the ``IMessageBoard``
+    interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMessageBoard, MessageBoard)
+    True
+    
+    Here is an example of changing the description of the board:
+
+    >>> board = MessageBoard()
+    >>> board.description
+    u''
+    >>> board.description = u'Message Board Description'
+    >>> board.description
+    u'Message Board Description'
+    """
+    implements(IMessageBoard)
+
+    # See book.messageboard.interfaces.IMessageBoard
+    description = u''
+
+
+class PlainText:
+
+    implements(IPlainText)
+
+    def __init__(self, context):
+        self.context = context
+
+    def getText(self):
+        return self.context.description
+
+    def setText(self, text):
+        self.context.description = unicode(text)

Added: messageboard/trunk/step10/security.zcml
===================================================================
--- messageboard/trunk/step10/security.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/security.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,54 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="messageboard">
+
+  <role
+      id="book.messageboard.User"
+      title="Message Board User"
+      description="Users that actually use the Message Board."
+      />
+  <grant
+      permission="book.messageboard.View"
+      role="book.messageboard.User"
+      />
+  <grant
+      permission="book.messageboard.Add"
+      role="book.messageboard.User"
+      />
+
+  <role
+      id="book.messageboard.Editor"
+      title="Message Board Editor"
+      description="The Editor can edit and delete Messages."
+      />
+  <grant
+      permission="book.messageboard.Edit"
+      role="book.messageboard.Editor"
+      />
+  <grant
+      permission="book.messageboard.Delete"
+      role="book.messageboard.Editor"
+      />
+  <grant
+      permission="book.messageboard.PublishContent"
+      role="book.messageboard.Editor"/>
+
+
+  <grant
+      permission="book.messageboard.View"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Add"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Edit"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Delete"
+      role="zope.Manager"
+      /> 
+
+</configure>

Added: messageboard/trunk/step10/tests/__init__.py
===================================================================
--- messageboard/trunk/step10/tests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/tests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step10/tests/test_fields.py
===================================================================
--- messageboard/trunk/step10/tests/test_fields.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/tests/test_fields.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_fields.py,v 1.1 2003/06/10 14:40:45 srichter Exp $
+"""
+import unittest
+from zope.schema.tests.test_strfield import TextTest
+
+from book.messageboard.fields import HTML, ForbiddenTags
+
+class HTMLTest(TextTest):
+
+    _Field_Factory = HTML
+
+    def test_AllowedTagsHTMLValidate(self):
+        html = self._Field_Factory(allowed_tags=('h1','pre'))
+        html.validate(u'<h1>Blah</h1>') 
+        html.validate(u'<pre>Blah</pre>') 
+        html.validate(u'<h1><pre>Blah</pre></h1>') 
+        html.validate(u'<h1 style="..."><pre>Blah</pre></h1>') 
+        html.validate(u'<h1 style="..."><pre f="">Blah</pre></h1>') 
+
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2>Foo</h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2><pre>Foo</pre></h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2 attr="blah">Foo</h2>')
+
+
+    def test_ForbiddenTagsHTMLValidate(self):
+        html = self._Field_Factory(forbidden_tags=('h2','pre'))
+        html.validate(u'<h1>Blah</h1>') 
+        html.validate(u'<h1 style="...">Blah</h1>') 
+        html.validate(u'<h1 style="..."><div>Blah</div></h1>') 
+        html.validate(u'<h1 style="..."><div f="">Blah</div></h1>') 
+
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2>Foo</h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2><div>Foo</div></h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2 attr="blah">Foo</h2>')
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(HTMLTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step10/tests/test_filerepresentation.py
===================================================================
--- messageboard/trunk/step10/tests/test_filerepresentation.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/tests/test_filerepresentation.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,178 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""FTP Views for the MessageBoard and Message component
+
+$Id$
+"""
+import unittest
+from zope.interface.verify import verifyObject
+from zope.app import zapi
+from zope.app.tests import ztapi
+from zope.app.tests.placelesssetup import PlacelessSetup
+
+from book.messageboard.interfaces import \
+     IVirtualContentsFile, IPlainText, IMessage, IMessageBoard
+from book.messageboard.message import \
+     Message, PlainText as MessagePlainText
+from book.messageboard.messageboard import \
+     MessageBoard, PlainText as MessageBoardPlainText
+from book.messageboard.filerepresentation import VirtualContentsFile
+from book.messageboard.filerepresentation import ReadDirectory
+
+class VirtualContentsFileTestBase(PlacelessSetup):
+
+    def _makeFile(self):
+        raise NotImplemented
+
+    def _registerPlainTextAdapter(self):
+        raise NotImplemented
+
+    def setUp(self):
+        PlacelessSetup.setUp(self)
+        self._registerPlainTextAdapter()
+
+    def testContentType(self):
+        file = self._makeFile()
+        self.assertEqual(file.getContentType(), 'text/plain')
+        file.setContentType('text/html')
+        self.assertEqual(file.getContentType(), 'text/plain')
+        self.assertEqual(file.contentType, 'text/plain')
+
+    def testData(self):
+        file = self._makeFile()
+
+        file.setData('Foobar')
+        self.assert_(file.getData().find('Foobar') >= 0)
+        self.assert_(file.data.find('Foobar') >= 0)
+
+        file.edit('Blah', 'text/html')
+        self.assertEqual(file.contentType, 'text/plain')
+        self.assert_(file.data.find('Blah') >= 0)
+
+    def testInterface(self):
+        file = self._makeFile()
+        self.failUnless(IVirtualContentsFile.providedBy(file))
+        self.failUnless(verifyObject(IVirtualContentsFile, file))
+
+
+class MessageVirtualContentsFileTest(VirtualContentsFileTestBase,
+                                     unittest.TestCase):
+    
+    def _makeFile(self):
+        return VirtualContentsFile(Message())
+
+    def _registerPlainTextAdapter(self):
+        ztapi.provideAdapter(IMessage, IPlainText, MessagePlainText)
+
+    def testMessageSpecifics(self):
+        file = self._makeFile()
+        self.assertEqual(file.context.title, '')
+        self.assertEqual(file.context.body, '')
+        file.data = 'Title: Hello\n\nWorld'
+        self.assertEqual(file.context.title, 'Hello')
+        self.assertEqual(file.context.body, 'World')
+        file.data = 'World 2'
+        self.assertEqual(file.context.body, 'World 2')
+
+
+class MessageBoardVirtualContentsFileTest(
+      VirtualContentsFileTestBase, unittest.TestCase):
+    
+    def _makeFile(self):
+        return VirtualContentsFile(MessageBoard())
+
+    def _registerPlainTextAdapter(self):
+        ztapi.provideAdapter(IMessageBoard, IPlainText, 
+                             MessageBoardPlainText)
+
+    def testMessageBoardSpecifics(self):
+        file = self._makeFile()
+        self.assertEqual(file.context.description, '')
+        file.data = 'Title: Hello\n\nWorld'
+        self.assertEqual(file.context.description, 
+                         'Title: Hello\n\nWorld')
+        file.data = 'World 2'
+        self.assertEqual(file.context.description, 'World 2')
+
+
+class ReadDirectoryTestBase(PlacelessSetup):
+
+    def _makeDirectoryObject(self):
+        raise NotImplemented
+
+    def _makeTree(self):
+        base = self._makeDirectoryObject()
+        msg1 = Message()
+        msg1.title = 'Message 1'
+        msg1.description = 'This is Message 1.'
+        msg11 = Message()
+        msg11.title = 'Message 1-1'
+        msg11.description = 'This is Message 1-1.'
+        msg2 = Message()
+        msg2.title = 'Message 1'
+        msg2.description = 'This is Message 1.'
+        msg1['msg11'] = msg11
+        base['msg1'] = msg1
+        base['msg2'] = msg2
+        return ReadDirectory(base)
+
+    def testKeys(self):
+        tree = self._makeTree()
+        keys = list(tree.keys())
+        keys.sort()
+        self.assertEqual(keys, ['contents', 'msg1', 'msg2'])
+        keys = list(ReadDirectory(tree['msg1']).keys())
+        keys.sort()
+        self.assertEqual(keys, ['contents', 'msg11'])
+
+    def testGet(self):
+        tree = self._makeTree()
+        self.assertEqual(tree.get('msg1'), tree.context['msg1'])
+        self.assertEqual(tree.get('msg3'), None)
+        default = object()
+        self.assertEqual(tree.get('msg3', default), default)
+        self.assertEqual(tree.get('contents').__class__, 
+                         VirtualContentsFile)
+
+    def testLen(self):
+        tree = self._makeTree()
+        self.assertEqual(len(tree), 3)
+        self.assertEqual(len(ReadDirectory(tree['msg1'])), 2)
+        self.assertEqual(len(ReadDirectory(tree['msg2'])), 1)
+        
+
+class MessageReadDirectoryTest(ReadDirectoryTestBase, 
+                               unittest.TestCase):
+
+    def _makeDirectoryObject(self):
+        return Message()
+
+
+class MessageBoardReadDirectoryTest(ReadDirectoryTestBase, 
+                                    unittest.TestCase):
+
+    def _makeDirectoryObject(self):
+        return MessageBoard()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MessageVirtualContentsFileTest),
+        unittest.makeSuite(MessageBoardVirtualContentsFileTest),
+        unittest.makeSuite(MessageReadDirectoryTest),
+        unittest.makeSuite(MessageBoardReadDirectoryTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step10/tests/test_message.py
===================================================================
--- messageboard/trunk/step10/tests/test_message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/tests/test_message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_message.py,v 1.2 2003/08/20 17:07:46 srichter Exp $
+"""
+import unittest
+from zope.interface import classImplements 
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.annotation.interfaces import IAttributeAnnotatable
+from zope.app.annotation.attribute import AttributeAnnotations
+from zope.app.container.tests.test_icontainer import TestSampleContainer
+from zope.app.location.traversing import LocationPhysicallyLocatable
+from zope.app.location.interfaces import ILocation
+from zope.app.tests import placelesssetup
+from zope.app.tests import ztapi
+from zope.app.traversing.interfaces import IPhysicallyLocatable
+
+from book.messageboard.interfaces import IMailSubscriptions
+from book.messageboard.interfaces import IMessage
+from book.messageboard.message import MailSubscriptions
+from book.messageboard.message import Message
+
+
+class Test(TestSampleContainer):
+
+    def makeTestObject(self):
+        return Message()
+
+
+def setUp():
+    placelesssetup.setUp()
+    classImplements(Message, IAttributeAnnotatable)
+    ztapi.provideAdapter(IAttributeAnnotatable, IAnnotations,
+                         AttributeAnnotations)
+    ztapi.provideAdapter(ILocation, IPhysicallyLocatable,
+                         LocationPhysicallyLocatable)
+    ztapi.provideAdapter(IMessage, IMailSubscriptions, MailSubscriptions)
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite('book.messageboard.message',
+                     setUp=setUp, tearDown=placelesssetup.tearDown),
+        unittest.makeSuite(Test),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step10/tests/test_messageboard.py
===================================================================
--- messageboard/trunk/step10/tests/test_messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/tests/test_messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_messageboard.py,v 1.2 2003/08/20 17:07:46 srichter Exp $
+"""
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.container.tests.test_icontainer import TestSampleContainer
+
+from book.messageboard.messageboard import MessageBoard
+
+
+class Test(TestSampleContainer):
+
+    def makeTestObject(self):
+        return MessageBoard()
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite('book.messageboard.messageboard'),
+        unittest.makeSuite(Test),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step10/workflow.xml
===================================================================
--- messageboard/trunk/step10/workflow.xml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step10/workflow.xml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<workflow type="StatefulWorkflow" title="Message Publication Review">
+  <schema name=""/>
+  <states>
+    <state name="INITIAL" title="initial" />
+    <state name="private" title="Private" />
+    <state name="pending" title="Pending Publication" />
+    <state name="published" title="Public" />
+  </states>
+  <transitions>
+     
+    <transition 
+        sourceState="published"
+        destinationState="private"
+        name="published_private"
+        title="Unpublish Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="private"
+        destinationState="pending"
+        name="private_pending"
+        title="Submit Message"
+        permission="book.messageboard.Edit"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="INITIAL" 
+        destinationState="private"
+        name="initial_private"
+        title="Make Private"
+        triggerMode="Automatic" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="published"
+        name="pending_published"
+        title="Publish Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="private"
+        name="pending_private"
+        title="Retract Message"
+        permission="book.messageboard.Edit"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="private"
+        name="pending_private_reject"
+        title="Reject Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+    
+  </transitions>
+  
+</workflow>

Added: messageboard/trunk/step11/__init__.py
===================================================================
--- messageboard/trunk/step11/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step11/browser/__init__.py
===================================================================
--- messageboard/trunk/step11/browser/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step11/browser/configure.zcml
===================================================================
--- messageboard/trunk/step11/browser/configure.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/configure.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,173 @@
+<configure
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:help="http://namespaces.zope.org/help"
+    xmlns:zope="http://namespaces.zope.org/zope">
+
+  <help:register
+      id="messageboard"
+      title="Message Board Help"
+      parent="ui"
+      for="book.messageboard.interfaces.IMessageBoard"
+      doc_path="./help/package_intro.rst"/>
+
+  <addform
+      label="Add Message Board"
+      name="AddMessageBoard.html"
+      template="messageboard_add.pt"
+      class=".messageboard.AddMessageBoard"
+      schema="book.messageboard.interfaces.IMessageBoard"
+      content_factory="book.messageboard.messageboard.MessageBoard"
+      fields="description"
+      permission="zope.ManageContent"
+      />
+
+  <addMenuItem
+      class="book.messageboard.messageboard.MessageBoard"
+      title="Message Board"
+      description="A Message Board"
+      permission="zope.ManageContent"
+      view="AddMessageBoard.html" 
+      />
+
+  <editform
+      schema="book.messageboard.interfaces.IMessageBoard"
+      for="book.messageboard.interfaces.IMessageBoard"
+      label="Change Message Board"
+      name="edit.html"
+      permission="zope.ManageContent"
+      menu="zmi_views" title="Edit" 
+      />
+
+  <containerViews
+      for="book.messageboard.interfaces.IMessageBoard"
+      index="book.messageboard.View"
+      contents="book.messageboard.Edit"
+      add="book.messageboard.Add"
+      />
+
+  <page
+      name="thread.html"
+      for="book.messageboard.interfaces.IMessageBoard"
+      class=".thread.Thread"
+      template="thread.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Thread"/>
+
+  <defaultView
+      for="book.messageboard.interfaces.IMessageBoard"
+      name="thread.html"/>
+
+  <icon
+      name="zmi_icon"
+      for="book.messageboard.interfaces.IMessageBoard"
+      file="messageboard.png" />
+
+  <page
+      name="review.html"
+      for="book.messageboard.interfaces.IMessageBoard"
+      class=".messageboard.ReviewMessages"
+      permission="book.messageboard.PublishContent"
+      template="review.pt"
+      menu="zmi_views" title="Review Messages"/>
+
+  <help:register
+      id="board.review"
+      title="Publication Review"
+      parent="ui/messageboard"
+      for="book.messageboard.interfaces.IMessageBoard"
+      view="review.html"
+      doc_path="./help/board_review.rst"/>
+
+  <addform
+      label="Add Message"
+      name="AddMessage.html"
+      schema="book.messageboard.interfaces.IMessage"
+      content_factory="book.messageboard.message.Message"
+      fields="title body"
+      permission="book.messageboard.Add"
+      />
+
+  <addMenuItem
+      class="book.messageboard.message.Message"
+      title="Message"
+      description="A Message"
+      permission="book.messageboard.Add"
+      view="AddMessage.html" 
+      />
+
+  <editform
+      schema="book.messageboard.interfaces.IMessage"
+      for="book.messageboard.interfaces.IMessage"
+      label="Change Message"
+      fields="title body"
+      name="edit.html"
+      permission="book.messageboard.Edit"
+      menu="zmi_views" title="Edit" 
+      />
+
+  <help:register
+      id="message.edit"
+      title="Change Message"
+      parent="ui/messageboard"
+      for="book.messageboard.interfaces.IMessage"
+      view="edit.html"
+      doc_path="./help/msg_edit.rst"/>
+
+  <containerViews
+      for="book.messageboard.interfaces.IMessage"
+      index="book.messageboard.View"
+      contents="book.messageboard.Edit"
+      add="book.messageboard.Add"
+      />
+
+  <page
+      name="details.html"
+      for="book.messageboard.interfaces.IMessage"
+      class=".message.MessageDetails"
+      template="details.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Preview"/>
+
+  <defaultView
+      for="book.messageboard.interfaces.IMessage"
+      name="details.html"/>
+
+  <page
+      name="thread.html"
+      for="book.messageboard.interfaces.IMessage"
+      class=".thread.Thread"
+      template="thread.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Thread"/>
+
+  <icon
+      name="zmi_icon"
+      for="book.messageboard.interfaces.IMessage"
+      file="message.png" />
+
+
+  <zope:view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="book.messageboard.interfaces.IHTML"
+      provides="zope.app.form.interfaces.IInputWidget"
+      factory=".widgets.HTMLSourceWidget"
+      permission="zope.Public"
+      />
+
+  <pages
+      for="book.messageboard.interfaces.IMessage"
+      class=".message.MailSubscriptions"
+      permission="book.messageboard.Edit"
+      >
+    <page 
+        name="subscriptions.html" 
+        template="subscriptions.pt"
+        menu="zmi_views" title="Subscriptions" 
+        />
+    <page 
+       name="changeSubscriptions.html" 
+       attribute="change" 
+       />
+  </pages>
+
+</configure>

Added: messageboard/trunk/step11/browser/details.pt
===================================================================
--- messageboard/trunk/step11/browser/details.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/details.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Message Details</h1>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Title</div>
+            <div class="field" tal:content="context/title" />
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Author</div>
+            <div class="field" tal:content="view/author"/>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Date/Time</div>
+            <div class="field" tal:content="view/modified"/>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Parent</div>
+            <div class="field" tal:define="info view/parent_info">
+              <a href="../" 
+                  tal:condition="info"
+                  tal:content="info/title" />
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Body</div>
+            <div class="field" tal:content="structure context/body"/>
+        </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step11/browser/ftests/__init__.py
===================================================================
--- messageboard/trunk/step11/browser/ftests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/ftests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step11/browser/ftests/test_message.py
===================================================================
--- messageboard/trunk/step11/browser/ftests/test_message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/ftests/test_message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Functional Tests
+
+$Id$
+"""
+import unittest
+from zope.app.tests.functional import BrowserTestCase
+
+class MessageTest(BrowserTestCase):
+
+    def testAddMessage(self):
+        response = self.publish(
+            '/+/AddMessageBoard.html=board',
+            basic='mgr:mgrpw',
+            form={'field.description': u'Message Board',
+                  'UPDATE_SUBMIT': 'Add'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/@@contents.html')
+        response = self.publish(
+            '/board/+/AddMessage.html=msg1',
+            basic='mgr:mgrpw',
+            form={'field.title': u'Message 1',
+                  'field.body': u'Body',
+                  'UPDATE_SUBMIT': 'Add'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/board/@@contents.html')
+       
+    def testMessageDetails(self):
+        self.testAddMessage()
+        response = self.publish('/board/msg1/@@details.html',
+                                basic='mgr:mgrpw')
+        body = response.getBody()
+        self.checkForBrokenLinks(body, '/board/msg1/@@details.html',
+                                 basic='mgr:mgrpw')
+        
+        self.assert_(body.find('Message Details') > 0)
+        self.assert_(body.find('Message 1') > 0)
+        self.assert_(body.find('Body') > 0)
+        
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MessageTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step11/browser/help/board_review.rst
===================================================================
--- messageboard/trunk/step11/browser/help/board_review.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/help/board_review.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,3 @@
+This view lists all messages in the board that are pending for
+publication. Each listed method is a link that brings you to the
+message's "Workflow" view where you can initiate a transition.

Added: messageboard/trunk/step11/browser/help/msg_edit.rst
===================================================================
--- messageboard/trunk/step11/browser/help/msg_edit.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/help/msg_edit.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,9 @@
+  This screen allows you to edit the data (i.e. the subject and body) of
+  the Message object.
+  
+  title - A one line unicode text string that briefly describes the
+          purpose/subject of the message.
+  
+  body - A multiple line unicode text string that is the actual content of 
+         the message. It is accepting HTML, but restricts the user to a 
+         couple of selected tags. Feel free to type anything you wish.

Added: messageboard/trunk/step11/browser/help/package_intro.rst
===================================================================
--- messageboard/trunk/step11/browser/help/package_intro.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/help/package_intro.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,6 @@
+==========================
+Message Board Demo Package
+==========================
+
+This package demos various features of the Zope 3 Framework. If you 
+have questions or concerns, please let me know. 

Added: messageboard/trunk/step11/browser/message.png
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step11/browser/message.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step11/browser/message.py
===================================================================
--- messageboard/trunk/step11/browser/message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,70 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser Views for IMessage
+
+$Id: message.py,v 1.3 2003/12/13 17:24:36 srichter Exp $
+"""
+from zope.i18n import MessageIDFactory
+
+from zope.app import zapi
+from zope.app.dublincore.interfaces import ICMFDublinCore
+
+from book.messageboard.interfaces import IMessage
+from book.messageboard.interfaces import IMailSubscriptions
+
+_ = MessageIDFactory('messageboard')
+
+class MessageDetails:
+
+    def author(self):
+        """Get user who last modified the Message."""
+        creators = ICMFDublinCore(self.context).creators
+        if not creators:
+            return _('unknown')
+        return creators[0]
+
+    def modified(self):
+        """Get last modification date."""
+        date = ICMFDublinCore(self.context).modified
+        if date is None:
+            date = ICMFDublinCore(self.context).created
+        if date is None:
+            return ''
+        formatter = self.request.locale.dates.getFormatter('dateTime', 'short')
+        return formatter.format(date)
+
+    def parent_info(self):
+        """Get the parent of the message"""
+        parent = zapi.getParent(self.context)
+        if not IMessage.providedBy(parent):
+            return None
+        return {'name': zapi.name(parent), 'title': parent.title}
+
+
+class MailSubscriptions:
+
+    def subscriptions(self):
+        return IMailSubscriptions(self.context).getSubscriptions()
+
+    def change(self):
+        if 'ADD' in self.request:
+            emails = self.request['emails'].split('\n')
+            IMailSubscriptions(self.context).addSubscriptions(emails)
+        elif 'REMOVE' in self.request:
+            emails = self.request['remails']
+            if isinstance(emails, (str, unicode)):
+                emails = [emails]
+            IMailSubscriptions(self.context).removeSubscriptions(emails)
+
+        self.request.response.redirect('./@@subscriptions.html')

Added: messageboard/trunk/step11/browser/messageboard.png
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step11/browser/messageboard.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step11/browser/messageboard.py
===================================================================
--- messageboard/trunk/step11/browser/messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,126 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser Views for IMessageBoard
+
+$Id$
+"""
+import os
+from zope.proxy import removeAllProxies
+
+from zope.app import zapi
+from zope.app.registration.interfaces import ActiveStatus
+from zope.app.site.interfaces import ISite
+from zope.app.site.service import SiteManager, ServiceRegistration
+from zope.app.utility.utility import LocalUtilityService, UtilityRegistration
+from zope.app.workflow.interfaces import IProcessDefinitionImportHandler
+from zope.app.workflow.interfaces import IProcessInstanceContainer
+from zope.app.workflow.stateful.contentworkflow import ContentWorkflowsManager
+from zope.app.workflow.stateful.definition import StatefulProcessDefinition
+from zope.app.workflow.stateful.interfaces import IContentWorkflowsManager
+from zope.app.workflow.stateful.interfaces import IStatefulProcessDefinition
+
+import book.messageboard
+from book.messageboard.interfaces import IMessage
+
+
+class AddMessageBoard(object):
+    """Add a message board."""
+  
+    def createAndAdd(self, data):
+        content = super(AddMessageBoard, self).createAndAdd(data)
+  
+        if self.request.get('workflow'):
+            folder = removeAllProxies(zapi.getParent(content))
+            if not ISite.providedBy(folder):
+                sm = SiteManager(folder)
+                folder.setSiteManager(sm)
+            default = zapi.traverse(folder.getSiteManager(), 'default')
+ 
+            # Create Local Utility Service
+            default['Utilities'] = LocalUtilityService()
+            rm = default.getRegistrationManager()
+            registration = ServiceRegistration(zapi.servicenames.Utilities,
+                                               'Utilities', rm)
+            key = rm.addRegistration(registration)
+            zapi.traverse(rm, key).status = ActiveStatus
+
+            # Create the process definition
+            default['publish-message'] = StatefulProcessDefinition()
+            pd_path = zapi.getPath(default['publish-message'])
+            registration = UtilityRegistration(
+                'publish-message', IStatefulProcessDefinition, pd_path)
+            pd_id = rm.addRegistration(registration)
+            zapi.traverse(rm, pd_id).status = ActiveStatus
+
+            import_util = IProcessDefinitionImportHandler(
+                default['publish-message'])
+            
+            xml = os.path.join(
+                os.path.dirname(book.messageboard.__file__), 'workflow.xml')
+                
+            import_util.doImport(open(xml, mode='r').read())
+  
+            # Create Content Workflows Manager
+            default['ContentWorkflows'] = ContentWorkflowsManager()
+            cm_path = zapi.getPath(default['ContentWorkflows'])
+            registration = UtilityRegistration(
+                'wfcontentmgr', IContentWorkflowsManager, cm_path)
+            cm_id = rm.addRegistration(registration)
+            zapi.traverse(rm, cm_id).status = ActiveStatus
+
+            contentmgr = default['ContentWorkflows']
+            contentmgr.register(IMessage, 'publish-message')
+
+        return content
+
+
+class ReviewMessages:
+    """Workflow: Review all pending messages"""
+
+    def getPendingMessages(self, pmsg):
+        """Get all pending messages recursively."""
+        msgs = []
+        for name, msg in pmsg.items():
+            if IMessage.providedBy(msg):
+                if hasMessageStatus(msg, 'pending'):
+                    msgs.append(msg)
+                msgs += self.getPendingMessages(msg)
+        return msgs
+
+    def getPendingMessagesInfo(self):
+        """Get all the display info for pending messages"""
+        msg_infos = []
+        for msg in self.getPendingMessages(self.context):
+            info = {}
+            info['title'] = msg.title
+            info['url'] = zapi.getView(
+                msg, 'absolute_url', self.request)() + '/@@workflows.html'
+            msg_infos.append(info)
+        return msg_infos
+
+
+def hasMessageStatus(msg, status, workflow='publish-message'):
+    """Check whether a particular message matches a given status"""
+    adapter = IProcessInstanceContainer(msg)
+    if adapter:
+        # No workflow is defined, so the message is always shown.
+        if not adapter.keys():
+            return True
+        for item in adapter.values():
+            if item.processDefinitionName != workflow:
+                continue
+            if item.status == status:
+                return True
+
+    return False

Added: messageboard/trunk/step11/browser/messageboard_add.pt
===================================================================
--- messageboard/trunk/step11/browser/messageboard_add.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/messageboard_add.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,25 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <div metal:use-macro="views/form_macros/addform">
+
+        <div metal:fill-slot="extra_bottom" class="row">
+          <div class="field">
+            <h3><input type="checkbox" name="workflow:int" 
+                    value="1" checked=""/>
+              <span i18n:translate="">Create Workflow</span>
+            </h3>
+            <span i18n:translate="">Without the workflow you will 
+              not be able to review messages before they are 
+              published. Note that you can always modify the 
+              messageboard workflow later to make all transitions 
+              automatically.</span>
+          </div>
+        </div>
+
+      </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step11/browser/review.pt
===================================================================
--- messageboard/trunk/step11/browser/review.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/review.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,16 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Pending Messages</h1>
+
+      <div class="row" tal:repeat="msg view/getPendingMessagesInfo">
+        <div class="field">
+          <a href="" tal:attributes="href msg/url"
+              tal:content="msg/title" />
+        </div>
+      </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step11/browser/subscriptions.pt
===================================================================
--- messageboard/trunk/step11/browser/subscriptions.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/subscriptions.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,43 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <form action="changeSubscriptions.html" method="post">
+
+        <div class="row">
+            <div class="label" 
+                i18n:translate="">Current Subscriptions</div>
+            <div class="field">
+           <div tal:repeat="email view/subscriptions">
+                <input type="checkbox" name="remails:list" 
+                       value="" tal:attributes="value email">
+                <div tal:replace="email">zope3 at zope3.org</div>
+              </div>
+              <input type="submit" name="REMOVE" value="Remove" 
+                   i18n:attributes="value remove-button">
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">
+              Enter new Users (separate by 'Return')
+            </div>
+            <div class="field">
+           <textarea name="emails" cols="40" rows="10"></textarea>
+            </div>
+        </div>
+
+             <div class="row">
+               <div class="controls">
+                 <input type="submit" value="Refresh" 
+                i18n:attributes="value refresh-button" />
+                 <input type="submit" name="ADD" value="Add" 
+                     i18n:attributes="value add-button" />
+               </div>
+             </div>
+
+      </form>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step11/browser/subthread.pt
===================================================================
--- messageboard/trunk/step11/browser/subthread.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/subthread.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,8 @@
+<ul>
+  <li tal:repeat="item view/listContentInfo">
+    <a href="" 
+        tal:attributes="href item/url"
+        tal:content="item/title">Message 1</a>
+    <div tal:replace="structure item/thread"/>
+  </li>
+</ul>
\ No newline at end of file

Added: messageboard/trunk/step11/browser/tests/__init__.py
===================================================================
--- messageboard/trunk/step11/browser/tests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/tests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step11/browser/tests/test_widgets.py
===================================================================
--- messageboard/trunk/step11/browser/tests/test_widgets.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/tests/test_widgets.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,76 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""HTMLSourceWidget Tests
+
+$Id: test_widgets.py,v 1.1 2003/06/10 14:40:44 srichter Exp $
+"""
+import unittest
+from zope.app.form.browser.tests.test_textareawidget import TextAreaWidgetTest
+from book.messageboard.browser.widgets import HTMLSourceWidget
+from book.messageboard.fields import HTML
+
+class HTMLSourceWidgetTest(TextAreaWidgetTest):
+
+    _FieldFactory = HTML
+    _WidgetFactory = HTMLSourceWidget
+
+
+    def test_AllowedTagsConvert(self):
+        widget = self._widget
+        widget.context.allowed_tags=('h1','pre')
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1>Blah</h1>')) 
+        self.assertEqual(u'<pre>Blah</pre>',
+                         widget._toFieldValue(u'<pre>Blah</pre>') )
+        self.assertEqual(u'<h1><pre>Blah</pre></h1>',
+                         widget._toFieldValue(u'<h1><pre>Blah</pre></h1>')) 
+        self.assertEqual(u'<h1 attr=".">Blah</h1>',
+                         widget._toFieldValue(u'<h1 attr=".">Blah</h1>')) 
+
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2>Blah</h2>')) 
+        self.assertEqual(u'<pre>Blah</pre>',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2 a="b">Blah</h2>')) 
+
+
+    def test_ForbiddenTagsConvert(self):
+        widget = self._widget
+        widget.context.forbidden_tags=('h2','pre')
+
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1>Blah</h1>')) 
+        self.assertEqual(u'<h1 a="b">Blah</h1>',
+                         widget._toFieldValue(u'<h1 a="b">Blah</h1>')) 
+
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2>Blah</h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<pre>Blah</pre>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1><pre>Blah</pre></h1>')) 
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(HTMLSourceWidgetTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step11/browser/thread.pt
===================================================================
--- messageboard/trunk/step11/browser/thread.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/thread.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,11 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Discussion Thread</h1>
+
+      <div tal:replace="structure view/subthread" />
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step11/browser/thread.py
===================================================================
--- messageboard/trunk/step11/browser/thread.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/thread.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,45 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser View for the sub-thread of a Message or MessageBoard
+
+$Id$
+"""
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+
+from book.messageboard.interfaces import IMessage
+from messageboard import hasMessageStatus
+
+class Thread:
+
+    def __init__(self, context, request, base_url=''):
+        self.context = context
+        self.request = request
+        self.base_url = base_url
+
+    def listContentInfo(self):
+        children = []
+        for name, child in self.context.items():
+            if IMessage.providedBy(child) and \
+                   hasMessageStatus(child, 'published'):
+                info = {}
+                info['title'] = child.title
+                url = self.base_url + name + '/'
+                info['url'] = url + '@@thread.html'
+                thread = Thread(child, self.request, url)
+                info['thread'] = thread.subthread()
+                children.append(info)
+        return children
+
+    subthread = ViewPageTemplateFile('subthread.pt')
+

Added: messageboard/trunk/step11/browser/widgets.py
===================================================================
--- messageboard/trunk/step11/browser/widgets.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/browser/widgets.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Module containing custom widget definitions.
+
+$Id: widgets.py,v 1.1 2003/06/10 14:40:44 srichter Exp $
+"""
+import re
+from zope.app.form.browser import TextAreaWidget
+from book.messageboard.fields import forbidden_regex, allowed_regex
+
+class HTMLSourceWidget(TextAreaWidget):
+
+    def _toFieldValue(self, input):
+        input = super(HTMLSourceWidget, self)._toFieldValue(input)
+
+        if self.context.forbidden_tags:
+            regex = forbidden_regex %'|'.join(
+                self.context.forbidden_tags)
+            input = re.sub(regex, '', input)
+
+        if self.context.allowed_tags:
+            regex = allowed_regex %'[ />]|'.join(
+                self.context.allowed_tags)
+            input = re.sub(regex, '', input)
+
+        return input
+

Added: messageboard/trunk/step11/configure.zcml
===================================================================
--- messageboard/trunk/step11/configure.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/configure.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,199 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    xmlns:mail="http://namespaces.zope.org/mail"
+    i18n_domain="messageboard">
+
+  <permission
+      id="book.messageboard.View"
+      title="View Message Board and Messages"
+      description="View the Message Board and all its content."
+      />
+  <permission
+      id="book.messageboard.Add"
+      title="Add Message"
+      description="Add Message."
+      />
+  <permission
+      id="book.messageboard.Edit"
+      title="Edit Messages"
+      description="Edit Messages."
+      />
+  <permission
+      id="book.messageboard.Delete"
+      title="Delete Message"
+      description="Delete Message."
+      />
+
+  <permission
+      id="book.messageboard.PublishContent"
+      title="Publish Message"
+      description="Publish Message."/>
+
+  <interface 
+      interface=".interfaces.IMessageBoard" 
+      type="zope.app.content.interfaces.IContentType"
+      /> 
+
+  <content class=".messageboard.MessageBoard">
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContentContainer" 
+        />
+    <factory
+        id="book.messageboard.MessageBoard"
+        description="Message Board" 
+        />
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessageBoard"
+        />
+    <require
+        permission="book.messageboard.Edit"
+        set_schema=".interfaces.IMessageBoard"
+        />
+  </content>
+
+  <adapter
+      factory=".messageboard.PlainText"
+      provides=".interfaces.IPlainText"
+      for=".interfaces.IMessageBoard" />
+
+  <adapter
+     for=".interfaces.IMessageBoard"
+     provides="zope.app.filerepresentation.interfaces.IReadDirectory"
+     factory=".filerepresentation.ReadDirectory"
+     permission="zope.View" />
+
+  <adapter
+     for=".interfaces.IMessageBoard"
+     provides="zope.app.filerepresentation.interfaces.IDirectoryFactory"
+     factory=".filerepresentation.MessageFactory"
+     permission="zope.View" />
+
+  <interface 
+      interface=".interfaces.IMessage" 
+      type="zope.app.content.interfaces.IContentType"
+      /> 
+
+  <content class=".message.Message">
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContentContainer" 
+        />
+    <implements interface=
+        "zope.app.workflow.interfaces.IProcessInstanceContainerAdaptable"/>
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessage"
+        />
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessageContainer"
+        />
+    <require
+        permission="book.messageboard.Add"
+        set_schema=".interfaces.IMessage"
+        />
+  </content>
+
+  <adapter
+      factory=".message.MessageSized"
+      provides="zope.app.size.interfaces.ISized"
+      for=".interfaces.IMessage"
+      />
+
+  <adapter
+      factory=".message.PlainText"
+      provides=".interfaces.IPlainText"
+      for=".interfaces.IMessage" />
+
+  <adapter
+     for=".interfaces.IMessage"
+     provides="zope.app.filerepresentation.interfaces.IReadDirectory"
+     factory=".filerepresentation.ReadDirectory"
+     permission="zope.View" />
+
+  <adapter
+     for=".interfaces.IMessage"
+     provides="zope.app.filerepresentation.interfaces.IDirectoryFactory"
+     factory=".filerepresentation.MessageFactory"
+     permission="zope.View" />
+
+  <adapter
+      factory=".message.MailSubscriptions"
+      provides=".interfaces.IMailSubscriptions"
+      for=".interfaces.IMessage"
+      permission="book.messageboard.Add"      
+      trusted="true" />
+
+  <content class=".filerepresentation.VirtualContentsFile">
+  
+    <implements interface="
+        zope.app.annotation.interfaces.IAttributeAnnotatable" />
+  
+    <require
+        permission="book.messageboard.View"
+        interface="zope.app.filerepresentation.interfaces.IReadFile" />
+  
+    <require
+        permission="book.messageboard.Edit"
+        interface="zope.app.filerepresentation.interfaces.IWriteFile"
+        set_schema="zope.app.filerepresentation.interfaces.IReadFile" />
+  
+  </content>
+
+  <xmlrpc:view
+      name="methods"
+      for=".interfaces.IMessageBoard"
+      permission="book.messageboard.Edit" 
+      allowed_attributes="getMessageNames addMessage deleteMessage 
+                          getDescription setDescription"
+      class=".xmlrpc.MessageBoardMethods" />
+
+  <xmlrpc:defaultView
+      for=".interfaces.IMessageBoard"
+      name="methods" />
+      
+  <xmlrpc:view
+      name="methods"
+      for=".interfaces.IMessage"
+      permission="book.messageboard.Edit" 
+      allowed_attributes="getMessageNames addMessage deleteMessage 
+                          getTitle setTitle getBody setBody"
+      class=".xmlrpc.MessageMethods" />
+
+  <xmlrpc:defaultView
+      for=".interfaces.IMessage"
+      name="methods" />
+
+  <mail:smtpMailer name="msgboard-smtp" hostname="localhost" port="25" />
+  
+  <mail:queuedDelivery 
+      name="msgboard-delivery"
+      permission="zope.SendMail"
+      queuePath="./mail-queue"
+      mailer="msgboard-smtp" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.event.interfaces.IObjectModifiedEvent" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.container.interfaces.IObjectAddedEvent" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.container.interfaces.IObjectRemovedEvent" />
+
+  <i18n:registerTranslations directory="locales" />
+
+  <include package=".browser" />
+
+</configure>

Added: messageboard/trunk/step11/fields.py
===================================================================
--- messageboard/trunk/step11/fields.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/fields.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Module containing custom field definitions.
+
+$Id$
+"""
+import re
+
+from zope.i18n import MessageIDFactory
+from zope.schema import Text
+from zope.schema.interfaces import ValidationError
+
+_ = MessageIDFactory('messageboard')
+
+forbidden_regex = r'</?(?:%s).*?/?>'
+allowed_regex = r'</??(?!%s[ />])[a-zA-Z0-9]*? ?(?:[a-z0-9]*?=?".*?")*/??>'
+
+class ForbiddenTags(ValidationError):
+    __doc__ = _("Forbidden HTML Tags used.")
+
+
+class HTML(Text):
+  
+    allowed_tags = ()
+    forbidden_tags = ()
+
+    def __init__(self, allowed_tags=(), forbidden_tags=(), **kw):
+        self.allowed_tags = allowed_tags
+        self.forbidden_tags = forbidden_tags
+        super(HTML, self).__init__(**kw)
+
+    def _validate(self, value):
+        super(HTML, self)._validate(value)
+
+        if self.forbidden_tags:
+            regex = forbidden_regex %'|'.join(self.forbidden_tags)
+            if re.findall(regex, value):
+                raise ForbiddenTags(value, self.forbidden_tags)
+
+        if self.allowed_tags:
+            regex = allowed_regex %'[ />]|'.join(self.allowed_tags)
+            if re.findall(regex, value):
+                raise ForbiddenTags(value, self.allowed_tags)
+
+

Added: messageboard/trunk/step11/filerepresentation.py
===================================================================
--- messageboard/trunk/step11/filerepresentation.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/filerepresentation.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,98 @@
+##############################################################################
+#
+# Copyright (c) 2003, 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""FTP Views for the MessageBoard and Message component
+
+$Id$
+"""
+from zope.interface import implements
+from interfaces import IVirtualContentsFile, IPlainText
+
+from zope.app.filerepresentation.interfaces import IReadDirectory
+from zope.app.folder.filerepresentation import \
+     ReadDirectory as ReadDirectoryBase
+from zope.app.filerepresentation.interfaces import IDirectoryFactory
+
+from message import Message
+
+
+class VirtualContentsFile(object):
+
+    implements(IVirtualContentsFile)
+
+    def __init__(self, context):
+        self.context = context
+
+    def setContentType(self, contentType):
+        '''See interface IFile'''
+        pass
+
+    def getContentType(self):
+        '''See interface IFile'''
+        return u'text/plain'
+
+    contentType = property(getContentType, setContentType)
+
+    def edit(self, data, contentType=None):
+        '''See interface IFile'''
+        self.setData(data)
+
+    def getData(self):
+        '''See interface IFile'''
+        adapter = IPlainText(self.context)
+        return adapter.getText() or u''
+
+    def setData(self, data):
+        '''See interface IFile'''
+        adapter = IPlainText(self.context)
+        return adapter.setText(data)
+
+    data = property(getData, setData)
+
+    def getSize(self):
+        '''See interface IFile'''
+        return len(self.getData())
+
+    size = property(getSize)
+
+
+class ReadDirectory(ReadDirectoryBase):
+    """An special implementation of the directory."""
+    
+    implements(IReadDirectory)
+
+    def keys(self):
+        keys = self.context.keys()
+        return list(keys) + ['contents']
+
+    def get(self, key, default=None):
+        if key == 'contents':
+            return VirtualContentsFile(self.context)
+        return self.context.get(key, default)
+
+    def __len__(self):
+        l = len(self.context)
+        return l+1
+
+
+class MessageFactory(object):
+    """A simple message factory for file system representations."""
+
+    implements(IDirectoryFactory)
+
+    def __init__(self, context):
+        self.context = context
+
+    def __call__(self, name):
+        """See IDirectoryFactory interface."""
+        return Message()

Added: messageboard/trunk/step11/interfaces.py
===================================================================
--- messageboard/trunk/step11/interfaces.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/interfaces.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,136 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Interfaces
+
+Interfaces for the Zope 3 based Message Board Package
+
+$Id$
+"""
+from zope.i18n import MessageIDFactory
+from zope.interface import classImplements, Interface
+from zope.schema import Text, TextLine, Field, Tuple
+from zope.schema.interfaces import IText
+
+from zope.app.container.constraints import ContainerTypesConstraint
+from zope.app.container.constraints import ItemTypePrecondition
+from zope.app.container.interfaces import IContained, IContainer
+from zope.app.file.interfaces import IFile, IFileContent
+
+from fields import HTML
+
+_ = MessageIDFactory('messageboard')
+
+
+class IMessage(Interface):
+    """A message object."""
+
+    title = TextLine(
+        title=_("Title/Subject"),
+        description=_("Title and/or subject of the message."),
+        default=u"",
+        required=True)
+
+    body = HTML(
+        title=_("Message Body"),
+        description=_("This is the actual message. Type whatever!"),
+        default=u"",
+        allowed_tags=('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'a',
+                      'br', 'b', 'i', 'u', 'em', 'sub', 'sup',
+                      'table', 'tr', 'td', 'th', 'code', 'pre',
+                      'center', 'div', 'span', 'p', 'font', 'ol',
+                      'ul', 'li', 'q', 's', 'strong'),
+        required=False)
+
+
+class IMessageBoard(IContainer):
+    """The message board is the base object for our package. It can only
+    contain IMessage objects."""
+
+    def __setitem__(name, object):
+        """Add a IMessage object."""
+
+    __setitem__.precondition = ItemTypePrecondition(IMessage)
+
+    description = Text(
+        title=_("Description"),
+        description=_("A detailed description of the content of the board."),
+        default=u"",
+        required=False)
+
+
+class IMessageContained(IContained):
+    """Interface that specifies the type of objects that can contain
+    messages."""
+    __parent__ = Field(
+        constraint = ContainerTypesConstraint(IMessageBoard, IMessage))
+
+
+class IMessageContainer(IContainer):
+    """We also want to make the message object a container that can contain
+    responses (other messages) and attachments (files and images)."""
+
+    def __setitem__(name, object):
+        """Add a IMessage object."""
+
+    __setitem__.precondition = ItemTypePrecondition(IMessage, IFile)
+
+
+class IHTML(IText):
+    """A text field that handles HTML input."""
+
+    allowed_tags = Tuple(
+        title=_("Allowed HTML Tags"),
+        description=_("""\
+        Only listed tags can be used in the value of the field.
+        """),
+        required=False)
+
+    forbidden_tags = Tuple(
+        title=_("Forbidden HTML Tags"),
+        description=_("""\
+        Listed tags cannot be used in the value of the field.
+        """),
+        required=False)
+
+classImplements(HTML, IHTML)
+
+
+class IMailSubscriptions(Interface):
+    """This interface allows you to retrieve a list of E-mails for
+    mailings. In our context these are messages."""
+
+    def getSubscriptions():
+        """Return a list of E-mails."""
+
+    def addSubscriptions(emails):
+        """Add a bunch of subscriptions; one would be okay too."""
+
+    def removeSubscriptions(emails):
+        """Remove a set of subscriptions."""
+
+
+class IPlainText(Interface):
+    """This interface allows you to represent an object's content in plain
+    text."""
+
+    def getText():
+        """Get a pure text representation of the object's content."""
+
+    def setText(text):
+        """Write the text to the object."""
+
+
+class IVirtualContentsFile(IFile, IFileContent):
+    """Marker Interface to mark special Message and Message Board 
+    Contents files in FS representations."""

Added: messageboard/trunk/step11/locales/de/LC_MESSAGES/messageboard.mo
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step11/locales/de/LC_MESSAGES/messageboard.mo
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step11/locales/de/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step11/locales/de/LC_MESSAGES/messageboard.po	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/locales/de/LC_MESSAGES/messageboard.po	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,295 @@
+# translation of messageboard.po to German
+# translation of messageboard.po to English US
+# Copyright (C) YEAR ORGANIZATION.
+# Stephan Richter <stephan.richter at tufts.edu>, 2003.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"POT-Creation-Date: Mon Dec 15 09:36:12 2003\n"
+"PO-Revision-Date: 2003-12-15 00:14-0500\n"
+"Last-Translator: Stephan Richter <stephan.richter at tufts.edu>\n"
+"Language-Team: German <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.4\n"
+"X-Generator: KBabel 1.3\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+msgid "add-button"
+msgstr "  Add  "
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr "Add Content"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:109
+msgid "Menu of objects to be added to Messages."
+msgstr "Menu of objects to be added to Messages."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+#: src/zopeproducts/messageboard/browser/configure.zcml:140
+msgid "Change Message"
+msgstr "Change Message"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:148
+msgid "Preview"
+msgstr "Preview"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:156
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr "Message Details"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:16
+msgid "Menu of objects to be added to Message Boards."
+msgstr "Menu of objects to be added to Message Boards."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:184
+msgid "Subscriptions"
+msgstr "Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:189
+msgid "Message Mail Subscriptions"
+msgstr "Message Mail Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:20
+#: src/zopeproducts/messageboard/browser/configure.zcml:113
+msgid "Add"
+msgstr "Add"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:203
+msgid "Image"
+msgstr "Image"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:209
+msgid "File"
+msgstr "File"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Add Message Board"
+msgstr "Add Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+msgid "Edit"
+msgstr "Edit"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:50
+msgid "Change Message Board"
+msgstr "Change Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:58
+#: src/zopeproducts/messageboard/browser/configure.zcml:164
+msgid "Thread"
+msgstr "Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:66
+#: src/zopeproducts/messageboard/browser/configure.zcml:172
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr "Discussion Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:74
+#: src/zopeproducts/messageboard/browser/configure.zcml:215
+msgid "Contents"
+msgstr "Contents"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:8
+msgid "Message Board Help"
+msgstr "Message Board Help"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:82
+msgid "Review Messages"
+msgstr "Review Messages"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:90
+msgid "Publication Review"
+msgstr "Publication Review"
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr "Author"
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr "Date/Time"
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr "Parent"
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr "Body"
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr "Title"
+
+# 8/browser/messageboard_add.pt:19
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr "Workflow erstellen"
+
+# 8/browser/messageboard_add.pt:21
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+msgstr ""
+"Ohne dem Workflow wirst Du nicht in der Lage sein Anzeigen vor der "
+"Veröffentlichung zu prüfen. Es ist zu erwähnen das Du jederzeit den Workflow "
+"später ändern kannst, so dass all Übergänge automatisch ausgeführt werden."
+
+# 7/browser/subscriptions.pt:35
+# Default: "Refresh"
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+msgid "refresh-button"
+msgstr "refresh-button"
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+msgid "submit-button"
+msgstr "Abschicken"
+
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr "Schwebende Anzeigen"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr "Current Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+msgid "remove-button"
+msgstr "   Add  "
+
+# 7/browser/subscriptions.pt:25
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr "Enter new Users (separate by 'Return')"
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Users that actually use the Message Board."
+msgstr "Users that actually use the Message Board."
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Message Board User"
+msgstr "Message Board User"
+
+#: src/zopeproducts/messageboard/configure.zcml:108
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+#: src/zopeproducts/messageboard/browser/configure.zcml:197
+msgid "Message"
+msgstr "Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "Message Board Editor"
+msgstr "Message Board Editor"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "The Editor can edit and delete Messages."
+msgstr "The Editor can edit and delete Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View the Message Board and all its content."
+msgstr "View the Message Board and all its content."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View Message Board and Messages"
+msgstr "View Message Board and Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+msgid "Add Message."
+msgstr "Add Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+msgid "Add Message"
+msgstr "Add Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages."
+msgstr "Edit Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages"
+msgstr "Edit Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message."
+msgstr "Delete Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message"
+msgstr "Delete Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message"
+msgstr "Publish Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message."
+msgstr "Publish Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:77
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Message Board"
+msgstr "Message Board"
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr "Forbidden HTML Tags used."
+
+#: src/zopeproducts/messageboard/interfaces.py:112
+msgid "Allowed HTML Tags"
+msgstr "Allowed HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:113
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:119
+msgid "Forbidden HTML Tags"
+msgstr "Forbidden HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:120
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "Description"
+msgstr "Description"
+
+#: src/zopeproducts/messageboard/interfaces.py:40
+msgid "A detailed description of the content of the board."
+msgstr "A detailed description of the content of the board."
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title/Subject"
+msgstr "Title/Subject"
+
+#: src/zopeproducts/messageboard/interfaces.py:50
+msgid "Title and/or subject of the message."
+msgstr "Title and/or subject of the message."
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "Message Body"
+msgstr "Message Body"
+
+#: src/zopeproducts/messageboard/interfaces.py:56
+msgid "This is the actual message. Type whatever!"
+msgstr "This is the actual message. Type whatever!"

Added: messageboard/trunk/step11/locales/en/LC_MESSAGES/messageboard.mo
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step11/locales/en/LC_MESSAGES/messageboard.mo
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step11/locales/en/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step11/locales/en/LC_MESSAGES/messageboard.po	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/locales/en/LC_MESSAGES/messageboard.po	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,297 @@
+# translation of messageboard.po to German
+# translation of messageboard.po to English US
+# Copyright (C) YEAR ORGANIZATION.
+# Stephan Richter <stephan.richter at tufts.edu>, 2003.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"POT-Creation-Date: Mon Dec 15 09:36:12 2003\n"
+"PO-Revision-Date: 2003-12-15 00:16-0500\n"
+"Last-Translator: Stephan Richter <stephan.richter at tufts.edu>\n"
+"Language-Team: German <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.4\n"
+"X-Generator: KBabel 1.3\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+msgid "add-button"
+msgstr "  Add  "
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr "Add Content"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:109
+msgid "Menu of objects to be added to Messages."
+msgstr "Menu of objects to be added to Messages."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+#: src/zopeproducts/messageboard/browser/configure.zcml:140
+msgid "Change Message"
+msgstr "Change Message"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:148
+msgid "Preview"
+msgstr "Preview"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:156
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr "Message Details"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:16
+msgid "Menu of objects to be added to Message Boards."
+msgstr "Menu of objects to be added to Message Boards."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:184
+msgid "Subscriptions"
+msgstr "Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:189
+msgid "Message Mail Subscriptions"
+msgstr "Message Mail Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:20
+#: src/zopeproducts/messageboard/browser/configure.zcml:113
+msgid "Add"
+msgstr "Add"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:203
+msgid "Image"
+msgstr "Image"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:209
+msgid "File"
+msgstr "File"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Add Message Board"
+msgstr "Add Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:132
+msgid "Edit"
+msgstr "Edit"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:42
+#: src/zopeproducts/messageboard/browser/configure.zcml:50
+msgid "Change Message Board"
+msgstr "Change Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:58
+#: src/zopeproducts/messageboard/browser/configure.zcml:164
+msgid "Thread"
+msgstr "Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:66
+#: src/zopeproducts/messageboard/browser/configure.zcml:172
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr "Discussion Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:74
+#: src/zopeproducts/messageboard/browser/configure.zcml:215
+msgid "Contents"
+msgstr "Contents"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:8
+msgid "Message Board Help"
+msgstr "Message Board Help"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:82
+msgid "Review Messages"
+msgstr "Review Messages"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:90
+msgid "Publication Review"
+msgstr "Publication Review"
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr "Author"
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr "Date/Time"
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr "Parent"
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr "Body"
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr "Title"
+
+# 8/browser/messageboard_add.pt:19
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr "Create Workflow"
+
+# 8/browser/messageboard_add.pt:21
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+msgstr ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+
+# 8/browser/messageboard_add.pt:30
+# 8/browser/subscriptions.pt:35
+# Default: "Refresh"
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+msgid "refresh-button"
+msgstr "Refresh"
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+msgid "submit-button"
+msgstr "Submit"
+
+# 8/browser/review.pt:5
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr "Pending Messages"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr "Current Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+msgid "remove-button"
+msgstr "Remove"
+
+# 8/browser/subscriptions.pt:25
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr "Enter new Users (separate by 'Return')"
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Users that actually use the Message Board."
+msgstr "Users that actually use the Message Board."
+
+#: src/zopeproducts/messageboard/configure.zcml:10
+msgid "Message Board User"
+msgstr "Message Board User"
+
+#: src/zopeproducts/messageboard/configure.zcml:108
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+#: src/zopeproducts/messageboard/browser/configure.zcml:197
+msgid "Message"
+msgstr "Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "Message Board Editor"
+msgstr "Message Board Editor"
+
+#: src/zopeproducts/messageboard/configure.zcml:15
+msgid "The Editor can edit and delete Messages."
+msgstr "The Editor can edit and delete Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View the Message Board and all its content."
+msgstr "View the Message Board and all its content."
+
+#: src/zopeproducts/messageboard/configure.zcml:20
+msgid "View Message Board and Messages"
+msgstr "View Message Board and Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+msgid "Add Message."
+msgstr "Add Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:29
+#: src/zopeproducts/messageboard/browser/configure.zcml:124
+msgid "Add Message"
+msgstr "Add Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages."
+msgstr "Edit Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:38
+msgid "Edit Messages"
+msgstr "Edit Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message."
+msgstr "Delete Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:47
+msgid "Delete Message"
+msgstr "Delete Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message"
+msgstr "Publish Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:57
+msgid "Publish Message."
+msgstr "Publish Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:77
+#: src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Message Board"
+msgstr "Message Board"
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr "Forbidden HTML Tags used."
+
+#: src/zopeproducts/messageboard/interfaces.py:112
+msgid "Allowed HTML Tags"
+msgstr "Allowed HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:113
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:119
+msgid "Forbidden HTML Tags"
+msgstr "Forbidden HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:120
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "Description"
+msgstr "Description"
+
+#: src/zopeproducts/messageboard/interfaces.py:40
+msgid "A detailed description of the content of the board."
+msgstr "A detailed description of the content of the board."
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title/Subject"
+msgstr "Title/Subject"
+
+#: src/zopeproducts/messageboard/interfaces.py:50
+msgid "Title and/or subject of the message."
+msgstr "Title and/or subject of the message."
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "Message Body"
+msgstr "Message Body"
+
+#: src/zopeproducts/messageboard/interfaces.py:56
+msgid "This is the actual message. Type whatever!"
+msgstr "This is the actual message. Type whatever!"

Added: messageboard/trunk/step11/locales/messageboard.pot
===================================================================
--- messageboard/trunk/step11/locales/messageboard.pot	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/locales/messageboard.pot	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,292 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: Zope X3 Pre-M4\n"
+"POT-Creation-Date: Mon Dec 15 09:48:46 2003\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: Zope 3 Developers <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: ENCODING\n"
+"Generated-By: zope/app/translation_files/extract.py\n"
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:109
+msgid "Menu of objects to be added to Messages."
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:132
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:140
+msgid "Change Message"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:148
+msgid "Preview"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:156
+#: 1/browser/details.pt:5
+msgid "Message Details"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:16
+msgid "Menu of objects to be added to Message Boards."
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:184
+msgid "Subscriptions"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:189
+msgid "Message Mail Subscriptions"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:20
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:113
+msgid "Add"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:203
+msgid "Image"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:209
+msgid "File"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Add Message Board"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:42
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:132
+msgid "Edit"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:42
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:50
+msgid "Change Message Board"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:58
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:164
+msgid "Thread"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:66
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:172
+#: 1/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:74
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:215
+msgid "Contents"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:8
+msgid "Message Board Help"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:82
+msgid "Review Messages"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:90
+msgid "Publication Review"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:109
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:124
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:197
+msgid "Message"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:11
+msgid "Users that actually use the Message Board."
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:11
+msgid "Message Board User"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:16
+msgid "Message Board Editor"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:16
+msgid "The Editor can edit and delete Messages."
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:21
+msgid "View the Message Board and all its content."
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:21
+msgid "View Message Board and Messages"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:30
+msgid "Add Message."
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:30
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:124
+msgid "Add Message"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:39
+msgid "Edit Messages."
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:39
+msgid "Edit Messages"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:48
+msgid "Delete Message."
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:48
+msgid "Delete Message"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:58
+msgid "Publish Message"
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:58
+msgid "Publish Message."
+msgstr ""
+
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/configure.zcml:78
+#: /opt/zope/Zope3/Zope3-Cookbook/src/zopeproducts/messageboard/browser/configure.zcml:32
+msgid "Message Board"
+msgstr ""
+
+#: 1/browser/add.pt:65
+#: 1/browser/subscriptions.pt:37
+# Default: "Add"
+msgid "add-button"
+msgstr ""
+
+#: 1/browser/add.pt:8
+msgid "Add Content"
+msgstr ""
+
+#: 1/browser/details.pt:13
+msgid "Author"
+msgstr ""
+
+#: 1/browser/details.pt:18
+msgid "Date/Time"
+msgstr ""
+
+#: 1/browser/details.pt:23
+msgid "Parent"
+msgstr ""
+
+#: 1/browser/details.pt:32
+msgid "Body"
+msgstr ""
+
+#: 1/browser/details.pt:8
+msgid "Title"
+msgstr ""
+
+#: 1/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr ""
+
+#: 1/browser/messageboard_add.pt:21
+msgid "Without the workflow you will not be able to review messages before they are published. Note that you can always modify the messageboard workflow later to make all transitions automatically."
+msgstr ""
+
+#: 1/browser/messageboard_add.pt:30
+#: 1/browser/subscriptions.pt:35
+# Default: "Refresh"
+msgid "refresh-button"
+msgstr ""
+
+#: 1/browser/messageboard_add.pt:32
+# Default: "Submit"
+msgid "submit-button"
+msgstr ""
+
+#: 1/browser/review.pt:5
+msgid "Pending Messages"
+msgstr ""
+
+#: 1/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr ""
+
+#: 1/browser/subscriptions.pt:19
+# Default: "Remove"
+msgid "remove-button"
+msgstr ""
+
+#: 1/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr ""
+
+#: 1/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr ""
+
+#: 1/interfaces.py:112
+msgid "Allowed HTML Tags"
+msgstr ""
+
+#: 1/interfaces.py:113
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+
+#: 1/interfaces.py:119
+msgid "Forbidden HTML Tags"
+msgstr ""
+
+#: 1/interfaces.py:120
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+
+#: 1/interfaces.py:39
+msgid "Description"
+msgstr ""
+
+#: 1/interfaces.py:40
+msgid "A detailed description of the content of the board."
+msgstr ""
+
+#: 1/interfaces.py:49
+msgid "Title/Subject"
+msgstr ""
+
+#: 1/interfaces.py:50
+msgid "Title and/or subject of the message."
+msgstr ""
+
+#: 1/interfaces.py:55
+msgid "Message Body"
+msgstr ""
+
+#: 1/interfaces.py:56
+msgid "This is the actual message. Type whatever!"
+msgstr ""
+

Added: messageboard/trunk/step11/message.py
===================================================================
--- messageboard/trunk/step11/message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,381 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Implementation
+
+An implementation of the Message using BTreeContainers as base.
+
+$Id: message.py,v 1.1 2003/06/07 11:24:48 srichter Exp $
+"""
+from zope.i18n import MessageIDFactory
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.container.btree import BTreeContainer
+from zope.app.container.interfaces import IObjectAddedEvent
+from zope.app.container.interfaces import IObjectRemovedEvent
+from zope.app.event.interfaces import IObjectModifiedEvent
+from zope.app.mail.interfaces import IMailDelivery
+from zope.app.size.interfaces import ISized
+
+from book.messageboard.interfaces import IMessage
+from book.messageboard.interfaces import IMessageContained, IMessageContainer
+from book.messageboard.interfaces import IMailSubscriptions
+from book.messageboard.interfaces import IPlainText
+
+_ = MessageIDFactory('messageboard')
+
+
+class Message(BTreeContainer):
+    """A simple implementation of a message.
+
+    Make sure that the ``Message`` implements the ``IMessage`` interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMessage, Message)
+    True
+
+    Here is an example of changing the title and description of the message:
+
+    >>> message = Message()
+    >>> message.title
+    u''
+    >>> message.body
+    u''
+    >>> message.title = u'Message Title'
+    >>> message.body = u'Message Body'
+    >>> message.title
+    u'Message Title'
+    >>> message.body
+    u'Message Body'
+    """
+    implements(IMessage, IMessageContained, IMessageContainer)
+
+    # See book.messageboard.interfaces.IMessage
+    title = u''
+
+    # See book.messageboard.interfaces.IMessage
+    body = u''
+
+  
+class MessageSized(object):
+
+    implements(ISized)
+    __used_for__ = IMessage
+
+    def __init__(self, message):
+        self._message = message
+
+    def sizeForSorting(self):
+        """See ISized
+
+        Create the adapter first.
+
+        >>> size = MessageSized(Message())
+
+        Here are some examples of the expected output.
+
+        >>> size.sizeForSorting()
+        ('item', 0)
+        >>> size._message['msg1'] = Message()
+        >>> size.sizeForSorting()
+        ('item', 1)
+        >>> size._message['att1'] = object()
+        >>> size.sizeForSorting()
+        ('item', 2)
+        """
+        return ('item', len(self._message))
+
+    def sizeForDisplay(self):
+        """See ISized
+
+        Creater the adapter first.
+
+        >>> size = MessageSized(Message())
+
+        Here are some examples of the expected output.
+
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 0, atts: 0'
+        >>> size._message['msg1'] = Message()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'1 reply, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 1, atts: 0'
+        >>> size._message['att1'] = object()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'1 reply, 1 attachment'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 1, atts: 1'
+        >>> size._message['msg2'] =  Message()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, 1 attachment'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 2, atts: 1'
+        >>> size._message['att2'] = object()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 2, atts: 2'
+        """
+        messages = 0
+        for obj in self._message.values():
+            if IMessage.providedBy(obj):
+                messages += 1
+
+        attachments = len(self._message)-messages
+
+        if messages == 1 and attachments == 1: 
+            size = _('1 reply, 1 attachment')
+        elif messages == 1 and attachments != 1:
+            size = _('1 reply, ${attachments} attachments')
+        elif messages != 1 and attachments == 1:
+            size = _('${messages} replies, 1 attachment')
+        else: 
+            size = _('${messages} replies, ${attachments} attachments')
+  
+        size.mapping = {'messages': `messages`, 'attachments': `attachments`}
+
+        return size
+
+
+SubscriberKey='http://www.zope.org/messageboard#1.0/MailSubscriptions/emails'
+
+
+class MailSubscriptions:
+    """Message Mail Subscriptions.
+
+    Verify the interface implementation
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMailSubscriptions, MailSubscriptions)
+    True
+
+    Create asubscription instance of a message
+
+    >>> msg = Message()
+    >>> sub = MailSubscriptions(msg)
+
+    Verify that we have initially no subscriptions and then add some.
+
+    >>> sub.getSubscriptions()
+    ()
+    >>> sub.addSubscriptions(('foo at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com',)
+    >>> sub.addSubscriptions(('blah at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com', 'blah at bar.com')
+    >>> sub.addSubscriptions(('doh at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com', 'blah at bar.com', 'doh at bar.com')
+
+    Now let's also check that we can remove entries.
+
+    >>> sub.removeSubscriptions(('foo at bar.com',))
+    >>> sub.getSubscriptions()
+    ('blah at bar.com', 'doh at bar.com')
+
+    When we construct a new mail subscription adapter instance, the values
+    should still be there.
+
+    >>> sub1 = MailSubscriptions(msg)
+    >>> sub1.getSubscriptions()
+    ('blah at bar.com', 'doh at bar.com')
+    """
+    implements(IMailSubscriptions)
+    __used_for__ = IMessage
+
+    def __init__(self, context):
+        self.context = context
+        self._annotations = IAnnotations(context)
+        if not self._annotations.get(SubscriberKey):
+            self._annotations[SubscriberKey] = ()
+
+    def getSubscriptions(self):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        return self._annotations[SubscriberKey]
+        
+    def addSubscriptions(self, emails):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        subscribers = list(self._annotations[SubscriberKey])
+        for email in emails:
+            if email not in subscribers:
+                subscribers.append(email.strip())
+        self._annotations[SubscriberKey] = tuple(subscribers)
+                
+    def removeSubscriptions(self, emails):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        subscribers = list(self._annotations[SubscriberKey])
+        for email in emails:
+            if email in subscribers:
+                subscribers.remove(email)
+        self._annotations[SubscriberKey] = tuple(subscribers)
+
+
+class MessageMailer:
+    """Class to handle all outgoing mail."""
+  
+    def __call__(self, event):
+        r"""Called by the event system.
+
+        Here is a demonstration on how the notification process and mail
+        sending works.
+
+        Before we can test this method, we have to create a mail delivery
+        object for testing.
+
+        >>> mail_result = [] 
+
+        >>> from zope.interface import implements
+        >>> from zope.app.mail.interfaces import IMailDelivery
+        
+        >>> class MailDeliveryStub(object):
+        ...     implements(IMailDelivery)
+        ... 
+        ...     def send(self, fromaddr, toaddrs, message):
+        ...         mail_result.append((fromaddr, toaddrs, message))
+
+        >>> from zope.app.tests import ztapi
+        >>> ztapi.provideUtility(IMailDelivery, MailDeliveryStub(),
+        ...                      name='msgboard-delivery')
+
+        Create a message.
+
+        >>> from zope.interface import directlyProvides
+        >>> from zope.app.traversing.interfaces import IContainmentRoot
+
+        >>> msg = Message()
+        >>> directlyProvides(msg, IContainmentRoot)
+        >>> msg.__name__ = 'msg'
+        >>> msg.__parent__ = None
+        >>> msg.title = 'Hello'
+        >>> msg.body = 'Hello World!'
+
+        Add a subscription to message.
+
+        >>> msg_sub = MailSubscriptions(msg)
+        >>> msg_sub.context.__annotations__[SubscriberKey] = ('foo at bar.com',)
+
+        Now, create an event and send it to the message mailer object.
+
+        >>> from zope.app.event.objectevent import ObjectModifiedEvent
+        >>> event = ObjectModifiedEvent(msg)
+        >>> mailer(event)
+
+        >>> from pprint import pprint
+        >>> pprint(mail_result)
+        [('mailer at messageboard.org',
+          ('foo at bar.com',),
+          'Subject: Modified: msg\n\n\nHello World!')]
+        """
+        if IMessage.providedBy(event.object):
+            if IObjectAddedEvent.providedBy(event):
+                self.handleAdded(event.object)
+            elif IObjectModifiedEvent.providedBy(event):
+                self.handleModified(event.object)
+            elif IObjectRemovedEvent.providedBy(event):
+                self.handleRemoved(event.object)
+  
+    def handleAdded(self, object):
+        subject = 'Added: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = object.body
+        self.mail(emails, subject, body)        
+  
+    def handleModified(self, object):
+        subject = 'Modified: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = object.body
+        self.mail(emails, subject, body)
+  
+    def handleRemoved(self, object):
+        subject = 'Removed: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = subject
+        self.mail(emails, subject, body)
+  
+    def getAllSubscribers(self, object):
+        """Retrieves all email subscribers.
+
+        Here a small demonstration of retrieving all subscribers.
+
+        >>> from zope.interface import directlyProvides
+        >>> from zope.app.traversing.interfaces import IContainmentRoot
+
+        Create a parent message as it would be located in the message
+        board. Also add a subscriber to the message.
+
+        >>> msg1 = Message()
+        >>> directlyProvides(msg1, IContainmentRoot)
+        >>> msg1.__name__ = 'msg1'
+        >>> msg1.__parent__ = None
+        >>> msg1_sub = MailSubscriptions(msg1)
+        >>> msg1_sub.context.__annotations__[SubscriberKey] = ('foo at bar.com',)
+
+        Create a reply to the first message and also give it a subscriber.
+       
+        >>> msg2 = Message()
+        >>> msg2_sub = MailSubscriptions(msg2)
+        >>> msg2_sub.context.__annotations__[SubscriberKey] = ('blah at bar.com',)
+        >>> msg1['msg2'] = msg2
+
+        When asking for all subscriptions of message 2, we should get the
+        subscriber from message 1 as well.
+
+        >>> mailer.getAllSubscribers(msg2)
+        ('blah at bar.com', 'foo at bar.com')
+        """
+        emails = ()
+        msg = object
+        while IMessage.providedBy(msg):
+            emails += tuple(IMailSubscriptions(msg).getSubscriptions())
+            msg = zapi.getParent(msg)
+        return emails
+  
+    def mail(self, toaddrs, subject, body):
+        """Mail out the Message Board change message."""
+        if not toaddrs:
+            return
+        msg = 'Subject: %s\n\n\n%s' %(subject, body)
+        mail_utility = zapi.getUtility(IMailDelivery, 'msgboard-delivery')
+        mail_utility.send('mailer at messageboard.org' , toaddrs, msg)
+  
+mailer = MessageMailer()
+
+
+class PlainText:
+
+    implements(IPlainText)
+
+    def __init__(self, context):
+        self.context = context
+
+    def getText(self):
+        return 'Title: %s\n\n%s' %(self.context.title, 
+                                   self.context.body)
+
+    def setText(self, text):
+        if text.startswith('Title: '):
+            title, text = text.split('\n', 1)
+            self.context.title = title[7:]
+
+        self.context.body = text.strip()

Added: messageboard/trunk/step11/messageboard.py
===================================================================
--- messageboard/trunk/step11/messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,62 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Implementation
+
+An implementation of the Message Board using BTreeContainers as base.
+
+$Id$
+"""
+from zope.interface import implements
+from zope.app.container.btree import BTreeContainer
+
+from book.messageboard.interfaces import IMessageBoard
+from book.messageboard.interfaces import IPlainText
+
+class MessageBoard(BTreeContainer):
+    """A very simple implementation of a message board using B-Tree Containers
+
+    Make sure that the ``MessageBoard`` implements the ``IMessageBoard``
+    interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMessageBoard, MessageBoard)
+    True
+    
+    Here is an example of changing the description of the board:
+
+    >>> board = MessageBoard()
+    >>> board.description
+    u''
+    >>> board.description = u'Message Board Description'
+    >>> board.description
+    u'Message Board Description'
+    """
+    implements(IMessageBoard)
+
+    # See book.messageboard.interfaces.IMessageBoard
+    description = u''
+
+
+class PlainText:
+
+    implements(IPlainText)
+
+    def __init__(self, context):
+        self.context = context
+
+    def getText(self):
+        return self.context.description
+
+    def setText(self, text):
+        self.context.description = unicode(text)

Added: messageboard/trunk/step11/security.zcml
===================================================================
--- messageboard/trunk/step11/security.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/security.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,54 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="messageboard">
+
+  <role
+      id="book.messageboard.User"
+      title="Message Board User"
+      description="Users that actually use the Message Board."
+      />
+  <grant
+      permission="book.messageboard.View"
+      role="book.messageboard.User"
+      />
+  <grant
+      permission="book.messageboard.Add"
+      role="book.messageboard.User"
+      />
+
+  <role
+      id="book.messageboard.Editor"
+      title="Message Board Editor"
+      description="The Editor can edit and delete Messages."
+      />
+  <grant
+      permission="book.messageboard.Edit"
+      role="book.messageboard.Editor"
+      />
+  <grant
+      permission="book.messageboard.Delete"
+      role="book.messageboard.Editor"
+      />
+  <grant
+      permission="book.messageboard.PublishContent"
+      role="book.messageboard.Editor"/>
+
+
+  <grant
+      permission="book.messageboard.View"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Add"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Edit"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Delete"
+      role="zope.Manager"
+      /> 
+
+</configure>

Added: messageboard/trunk/step11/tests/__init__.py
===================================================================
--- messageboard/trunk/step11/tests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/tests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step11/tests/test_fields.py
===================================================================
--- messageboard/trunk/step11/tests/test_fields.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/tests/test_fields.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_fields.py,v 1.1 2003/06/10 14:40:45 srichter Exp $
+"""
+import unittest
+from zope.schema.tests.test_strfield import TextTest
+
+from book.messageboard.fields import HTML, ForbiddenTags
+
+class HTMLTest(TextTest):
+
+    _Field_Factory = HTML
+
+    def test_AllowedTagsHTMLValidate(self):
+        html = self._Field_Factory(allowed_tags=('h1','pre'))
+        html.validate(u'<h1>Blah</h1>') 
+        html.validate(u'<pre>Blah</pre>') 
+        html.validate(u'<h1><pre>Blah</pre></h1>') 
+        html.validate(u'<h1 style="..."><pre>Blah</pre></h1>') 
+        html.validate(u'<h1 style="..."><pre f="">Blah</pre></h1>') 
+
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2>Foo</h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2><pre>Foo</pre></h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2 attr="blah">Foo</h2>')
+
+
+    def test_ForbiddenTagsHTMLValidate(self):
+        html = self._Field_Factory(forbidden_tags=('h2','pre'))
+        html.validate(u'<h1>Blah</h1>') 
+        html.validate(u'<h1 style="...">Blah</h1>') 
+        html.validate(u'<h1 style="..."><div>Blah</div></h1>') 
+        html.validate(u'<h1 style="..."><div f="">Blah</div></h1>') 
+
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2>Foo</h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2><div>Foo</div></h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2 attr="blah">Foo</h2>')
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(HTMLTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step11/tests/test_filerepresentation.py
===================================================================
--- messageboard/trunk/step11/tests/test_filerepresentation.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/tests/test_filerepresentation.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,178 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""FTP Views for the MessageBoard and Message component
+
+$Id$
+"""
+import unittest
+from zope.interface.verify import verifyObject
+from zope.app import zapi
+from zope.app.tests import ztapi
+from zope.app.tests.placelesssetup import PlacelessSetup
+
+from book.messageboard.interfaces import \
+     IVirtualContentsFile, IPlainText, IMessage, IMessageBoard
+from book.messageboard.message import \
+     Message, PlainText as MessagePlainText
+from book.messageboard.messageboard import \
+     MessageBoard, PlainText as MessageBoardPlainText
+from book.messageboard.filerepresentation import VirtualContentsFile
+from book.messageboard.filerepresentation import ReadDirectory
+
+class VirtualContentsFileTestBase(PlacelessSetup):
+
+    def _makeFile(self):
+        raise NotImplemented
+
+    def _registerPlainTextAdapter(self):
+        raise NotImplemented
+
+    def setUp(self):
+        PlacelessSetup.setUp(self)
+        self._registerPlainTextAdapter()
+
+    def testContentType(self):
+        file = self._makeFile()
+        self.assertEqual(file.getContentType(), 'text/plain')
+        file.setContentType('text/html')
+        self.assertEqual(file.getContentType(), 'text/plain')
+        self.assertEqual(file.contentType, 'text/plain')
+
+    def testData(self):
+        file = self._makeFile()
+
+        file.setData('Foobar')
+        self.assert_(file.getData().find('Foobar') >= 0)
+        self.assert_(file.data.find('Foobar') >= 0)
+
+        file.edit('Blah', 'text/html')
+        self.assertEqual(file.contentType, 'text/plain')
+        self.assert_(file.data.find('Blah') >= 0)
+
+    def testInterface(self):
+        file = self._makeFile()
+        self.failUnless(IVirtualContentsFile.providedBy(file))
+        self.failUnless(verifyObject(IVirtualContentsFile, file))
+
+
+class MessageVirtualContentsFileTest(VirtualContentsFileTestBase,
+                                     unittest.TestCase):
+    
+    def _makeFile(self):
+        return VirtualContentsFile(Message())
+
+    def _registerPlainTextAdapter(self):
+        ztapi.provideAdapter(IMessage, IPlainText, MessagePlainText)
+
+    def testMessageSpecifics(self):
+        file = self._makeFile()
+        self.assertEqual(file.context.title, '')
+        self.assertEqual(file.context.body, '')
+        file.data = 'Title: Hello\n\nWorld'
+        self.assertEqual(file.context.title, 'Hello')
+        self.assertEqual(file.context.body, 'World')
+        file.data = 'World 2'
+        self.assertEqual(file.context.body, 'World 2')
+
+
+class MessageBoardVirtualContentsFileTest(
+      VirtualContentsFileTestBase, unittest.TestCase):
+    
+    def _makeFile(self):
+        return VirtualContentsFile(MessageBoard())
+
+    def _registerPlainTextAdapter(self):
+        ztapi.provideAdapter(IMessageBoard, IPlainText, 
+                             MessageBoardPlainText)
+
+    def testMessageBoardSpecifics(self):
+        file = self._makeFile()
+        self.assertEqual(file.context.description, '')
+        file.data = 'Title: Hello\n\nWorld'
+        self.assertEqual(file.context.description, 
+                         'Title: Hello\n\nWorld')
+        file.data = 'World 2'
+        self.assertEqual(file.context.description, 'World 2')
+
+
+class ReadDirectoryTestBase(PlacelessSetup):
+
+    def _makeDirectoryObject(self):
+        raise NotImplemented
+
+    def _makeTree(self):
+        base = self._makeDirectoryObject()
+        msg1 = Message()
+        msg1.title = 'Message 1'
+        msg1.description = 'This is Message 1.'
+        msg11 = Message()
+        msg11.title = 'Message 1-1'
+        msg11.description = 'This is Message 1-1.'
+        msg2 = Message()
+        msg2.title = 'Message 1'
+        msg2.description = 'This is Message 1.'
+        msg1['msg11'] = msg11
+        base['msg1'] = msg1
+        base['msg2'] = msg2
+        return ReadDirectory(base)
+
+    def testKeys(self):
+        tree = self._makeTree()
+        keys = list(tree.keys())
+        keys.sort()
+        self.assertEqual(keys, ['contents', 'msg1', 'msg2'])
+        keys = list(ReadDirectory(tree['msg1']).keys())
+        keys.sort()
+        self.assertEqual(keys, ['contents', 'msg11'])
+
+    def testGet(self):
+        tree = self._makeTree()
+        self.assertEqual(tree.get('msg1'), tree.context['msg1'])
+        self.assertEqual(tree.get('msg3'), None)
+        default = object()
+        self.assertEqual(tree.get('msg3', default), default)
+        self.assertEqual(tree.get('contents').__class__, 
+                         VirtualContentsFile)
+
+    def testLen(self):
+        tree = self._makeTree()
+        self.assertEqual(len(tree), 3)
+        self.assertEqual(len(ReadDirectory(tree['msg1'])), 2)
+        self.assertEqual(len(ReadDirectory(tree['msg2'])), 1)
+        
+
+class MessageReadDirectoryTest(ReadDirectoryTestBase, 
+                               unittest.TestCase):
+
+    def _makeDirectoryObject(self):
+        return Message()
+
+
+class MessageBoardReadDirectoryTest(ReadDirectoryTestBase, 
+                                    unittest.TestCase):
+
+    def _makeDirectoryObject(self):
+        return MessageBoard()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MessageVirtualContentsFileTest),
+        unittest.makeSuite(MessageBoardVirtualContentsFileTest),
+        unittest.makeSuite(MessageReadDirectoryTest),
+        unittest.makeSuite(MessageBoardReadDirectoryTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step11/tests/test_message.py
===================================================================
--- messageboard/trunk/step11/tests/test_message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/tests/test_message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_message.py,v 1.2 2003/08/20 17:07:46 srichter Exp $
+"""
+import unittest
+from zope.interface import classImplements 
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.annotation.interfaces import IAttributeAnnotatable
+from zope.app.annotation.attribute import AttributeAnnotations
+from zope.app.container.tests.test_icontainer import TestSampleContainer
+from zope.app.location.traversing import LocationPhysicallyLocatable
+from zope.app.location.interfaces import ILocation
+from zope.app.tests import placelesssetup
+from zope.app.tests import ztapi
+from zope.app.traversing.interfaces import IPhysicallyLocatable
+
+from book.messageboard.interfaces import IMailSubscriptions
+from book.messageboard.interfaces import IMessage
+from book.messageboard.message import MailSubscriptions
+from book.messageboard.message import Message
+
+
+class Test(TestSampleContainer):
+
+    def makeTestObject(self):
+        return Message()
+
+
+def setUp():
+    placelesssetup.setUp()
+    classImplements(Message, IAttributeAnnotatable)
+    ztapi.provideAdapter(IAttributeAnnotatable, IAnnotations,
+                         AttributeAnnotations)
+    ztapi.provideAdapter(ILocation, IPhysicallyLocatable,
+                         LocationPhysicallyLocatable)
+    ztapi.provideAdapter(IMessage, IMailSubscriptions, MailSubscriptions)
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite('book.messageboard.message',
+                     setUp=setUp, tearDown=placelesssetup.tearDown),
+        unittest.makeSuite(Test),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step11/tests/test_messageboard.py
===================================================================
--- messageboard/trunk/step11/tests/test_messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/tests/test_messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_messageboard.py,v 1.2 2003/08/20 17:07:46 srichter Exp $
+"""
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.container.tests.test_icontainer import TestSampleContainer
+
+from book.messageboard.messageboard import MessageBoard
+
+
+class Test(TestSampleContainer):
+
+    def makeTestObject(self):
+        return MessageBoard()
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite('book.messageboard.messageboard'),
+        unittest.makeSuite(Test),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step11/tests/test_xmlrpc.py
===================================================================
--- messageboard/trunk/step11/tests/test_xmlrpc.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/tests/test_xmlrpc.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,101 @@
+##############################################################################
+#
+# Copyright (c) 2003, 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""XML-RPC Representation Tests
+
+$Id$
+"""
+import unittest
+
+from zope.publisher.xmlrpc import TestRequest
+
+from zope.app.tests.placelesssetup import PlacelessSetup
+from zope.app import zapi
+
+from book.messageboard.message import Message
+from book.messageboard.messageboard import MessageBoard
+from book.messageboard.xmlrpc import MessageBoardMethods, MessageMethods
+
+class MessageContainerTest(PlacelessSetup):
+
+    def _makeMethodObject(self):
+        return NotImplemented
+
+    def _makeTree(self):
+        methods = self._makeMethodObject()
+        msg1 = Message()
+        msg1.title = 'Message 1'
+        msg1.description = 'This is Message 1.'
+        msg2 = Message()
+        msg2.title = 'Message 1'
+        msg2.description = 'This is Message 1.'
+        methods.context['msg1'] = msg1
+        methods.context['msg2'] = msg2
+        return methods
+
+    def test_getMessageNames(self):
+        methods = self._makeTree()
+        self.assert_(isinstance(methods.getMessageNames(), list))
+        self.assertEqual(list(methods.context.keys()),
+                         methods.getMessageNames())
+
+    def test_addMessage(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.addMessage('msg3', 'M3', 'MB3'), 'msg3')
+        self.assertEqual(methods.context['msg3'].title, 'M3')
+        self.assertEqual(methods.context['msg3'].body, 'MB3')
+
+    def test_deleteMessage(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.deleteMessage('msg2'), True)
+        self.assertEqual(list(methods.context.keys()), ['msg1'])
+
+
+class MessageBoardMethodsTest(MessageContainerTest, unittest.TestCase):
+
+    def _makeMethodObject(self):
+        return MessageBoardMethods(MessageBoard(), TestRequest())
+
+    def test_description(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.getDescription(), '')
+        self.assertEqual(methods.setDescription('Board 1') , True)
+        self.assertEqual(methods.getDescription(), 'Board 1')
+
+
+class MessageMethodsTest(MessageContainerTest, unittest.TestCase):
+
+    def _makeMethodObject(self):
+        return MessageMethods(Message(), TestRequest())
+
+    def test_title(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.getTitle(), '')
+        self.assertEqual(methods.setTitle('Message 1') , True)
+        self.assertEqual(methods.getTitle(), 'Message 1')
+
+    def test_body(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.getBody(), '')
+        self.assertEqual(methods.setBody('Body 1') , True)
+        self.assertEqual(methods.getBody(), 'Body 1')
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MessageBoardMethodsTest),
+        unittest.makeSuite(MessageMethodsTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step11/workflow.xml
===================================================================
--- messageboard/trunk/step11/workflow.xml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/workflow.xml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<workflow type="StatefulWorkflow" title="Message Publication Review">
+  <schema name=""/>
+  <states>
+    <state name="INITIAL" title="initial" />
+    <state name="private" title="Private" />
+    <state name="pending" title="Pending Publication" />
+    <state name="published" title="Public" />
+  </states>
+  <transitions>
+     
+    <transition 
+        sourceState="published"
+        destinationState="private"
+        name="published_private"
+        title="Unpublish Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="private"
+        destinationState="pending"
+        name="private_pending"
+        title="Submit Message"
+        permission="book.messageboard.Edit"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="INITIAL" 
+        destinationState="private"
+        name="initial_private"
+        title="Make Private"
+        triggerMode="Automatic" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="published"
+        name="pending_published"
+        title="Publish Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="private"
+        name="pending_private"
+        title="Retract Message"
+        permission="book.messageboard.Edit"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="private"
+        name="pending_private_reject"
+        title="Reject Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+    
+  </transitions>
+  
+</workflow>

Added: messageboard/trunk/step11/xmlrpc.py
===================================================================
--- messageboard/trunk/step11/xmlrpc.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/xmlrpc.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,74 @@
+##############################################################################
+#
+# Copyright (c) 2003, 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""XML-RPC Representations
+
+$Id$
+"""
+from zope.event import notify
+from zope.publisher.xmlrpc import MethodPublisher
+
+from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
+
+from book.messageboard.message import Message
+
+
+class MessageContainerMethods(MethodPublisher):
+
+  def getMessageNames(self):
+      """Get a list of all messages."""
+      return list(self.context.keys())
+
+  def addMessage(self, name, title, body):
+      """Add a message."""
+      msg = Message()
+      msg.title = title
+      msg.body = body
+      notify(ObjectCreatedEvent(msg))
+      self.context[name] = msg
+      return name
+
+  def deleteMessage(self, name):
+      """Delete a message. Return True, if successful."""
+      self.context.__delitem__(name)
+      return True 
+
+
+class MessageMethods(MessageContainerMethods):
+
+    def getTitle(self):
+        return self.context.title
+
+    def setTitle(self, title):
+        self.context.title = title
+        notify(ObjectModifiedEvent(self.context))
+        return True
+
+    def getBody(self):
+        return self.context.body
+
+    def setBody(self, body):
+        self.context.body = body
+        notify(ObjectModifiedEvent(self.context))
+        return True
+
+
+class MessageBoardMethods(MessageContainerMethods):
+
+    def getDescription(self):
+        return self.context.description
+
+    def setDescription(self, description):
+        self.context.description = description
+        notify(ObjectModifiedEvent(self.context))
+        return True

Added: messageboard/trunk/step11/xmlrpc_client.py
===================================================================
--- messageboard/trunk/step11/xmlrpc_client.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step11/xmlrpc_client.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,77 @@
+#!/usr/bin/env python2.3
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""XML-RPC Client
+
+$Id: xmlrpc_client.py,v 1.1 2003/07/18 11:55:34 srichter Exp $
+"""
+import httplib
+import xmlrpclib
+from code import InteractiveConsole
+from base64 import encodestring
+
+class BasicAuthTransport(xmlrpclib.Transport):
+    def __init__(self, username=None, password=None, verbose=0):
+        self.username=username
+        self.password=password
+        self.verbose=verbose
+
+    def request(self, host, handler, request_body, verbose=0):
+        # issue XML-RPC request
+
+        self.verbose = verbose
+
+        h = httplib.HTTP(host)
+        h.putrequest("POST", handler)
+
+        # required by HTTP/1.1
+        h.putheader("Host", host)
+
+        # required by XML-RPC
+        h.putheader("User-Agent", self.user_agent)
+        h.putheader("Content-Type", "text/xml")
+        h.putheader("Content-Length", str(len(request_body)))
+
+        # basic auth
+        if self.username is not None and self.password is not None:
+            h.putheader("AUTHORIZATION", "Basic %s" %
+                        encodestring("%s:%s" % (self.username, self.password)
+                                     ).replace("\012", ""))
+        h.endheaders()
+
+        if request_body:
+            h.send(request_body)
+
+        errcode, errmsg, headers = h.getreply()
+
+        if errcode != 200:
+            raise xmlrpclib.ProtocolError(host + handler,
+                                          errcode, errmsg, headers)
+
+        return self.parse_response(h.getfile())
+
+
+if __name__ == '__main__':
+    url = raw_input('Message Board URL [http://localhost:8080/board/]: ')
+    if url == '':
+        url = 'http://localhost:8080/board/'
+    username = raw_input('Username: ')
+    password = raw_input('Password: ')
+    
+    board = xmlrpclib.Server(
+        url, transport = BasicAuthTransport(username, password))
+
+    print "The message board is available as 'board'"
+    console = InteractiveConsole(locals={'board': board})
+    console.interact()


Property changes on: messageboard/trunk/step11/xmlrpc_client.py
___________________________________________________________________
Name: svn:executable
   + *

Added: messageboard/trunk/step12/__init__.py
===================================================================
--- messageboard/trunk/step12/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step12/browser/__init__.py
===================================================================
--- messageboard/trunk/step12/browser/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step12/browser/configure.zcml
===================================================================
--- messageboard/trunk/step12/browser/configure.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/configure.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,175 @@
+<configure
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:help="http://namespaces.zope.org/help"
+    xmlns:zope="http://namespaces.zope.org/zope">
+
+  <help:register
+      id="messageboard"
+      title="Message Board Help"
+      parent="ui"
+      for="book.messageboard.interfaces.IMessageBoard"
+      doc_path="./help/package_intro.rst"/>
+
+  <addform
+      label="Add Message Board"
+      name="AddMessageBoard.html"
+      template="messageboard_add.pt"
+      class=".messageboard.AddMessageBoard"
+      schema="book.messageboard.interfaces.IMessageBoard"
+      content_factory="book.messageboard.messageboard.MessageBoard"
+      fields="description"
+      permission="zope.ManageContent"
+      />
+
+  <addMenuItem
+      class="book.messageboard.messageboard.MessageBoard"
+      title="Message Board"
+      description="A Message Board"
+      permission="zope.ManageContent"
+      view="AddMessageBoard.html" 
+      />
+
+  <editform
+      schema="book.messageboard.interfaces.IMessageBoard"
+      for="book.messageboard.interfaces.IMessageBoard"
+      label="Change Message Board"
+      name="edit.html"
+      permission="zope.ManageContent"
+      menu="zmi_views" title="Edit" 
+      />
+
+  <containerViews
+      for="book.messageboard.interfaces.IMessageBoard"
+      index="book.messageboard.View"
+      contents="book.messageboard.Edit"
+      add="book.messageboard.Add"
+      />
+
+  <page
+      name="thread.html"
+      for="book.messageboard.interfaces.IMessageBoard"
+      class=".thread.Thread"
+      template="thread.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Thread"/>
+
+  <defaultView
+      for="book.messageboard.interfaces.IMessageBoard"
+      name="thread.html"/>
+
+  <icon
+      name="zmi_icon"
+      for="book.messageboard.interfaces.IMessageBoard"
+      file="messageboard.png" />
+
+  <page
+      name="review.html"
+      for="book.messageboard.interfaces.IMessageBoard"
+      class=".messageboard.ReviewMessages"
+      permission="book.messageboard.PublishContent"
+      template="review.pt"
+      menu="zmi_views" title="Review Messages"/>
+
+  <help:register
+      id="board.review"
+      title="Publication Review"
+      parent="ui/messageboard"
+      for="book.messageboard.interfaces.IMessageBoard"
+      view="review.html"
+      doc_path="./help/board_review.rst"/>
+
+  <addform
+      label="Add Message"
+      name="AddMessage.html"
+      schema="book.messageboard.interfaces.IMessage"
+      content_factory="book.messageboard.message.Message"
+      fields="title body"
+      permission="book.messageboard.Add"
+      />
+
+  <addMenuItem
+      class="book.messageboard.message.Message"
+      title="Message"
+      description="A Message"
+      permission="book.messageboard.Add"
+      view="AddMessage.html" 
+      />
+
+  <editform
+      schema="book.messageboard.interfaces.IMessage"
+      for="book.messageboard.interfaces.IMessage"
+      label="Change Message"
+      fields="title body"
+      name="edit.html"
+      permission="book.messageboard.Edit"
+      menu="zmi_views" title="Edit" 
+      />
+
+  <help:register
+      id="message.edit"
+      title="Change Message"
+      parent="ui/messageboard"
+      for="book.messageboard.interfaces.IMessage"
+      view="edit.html"
+      doc_path="./help/msg_edit.rst"/>
+
+  <containerViews
+      for="book.messageboard.interfaces.IMessage"
+      index="book.messageboard.View"
+      contents="book.messageboard.Edit"
+      add="book.messageboard.Add"
+      />
+
+  <page
+      name="details.html"
+      for="book.messageboard.interfaces.IMessage"
+      class=".message.MessageDetails"
+      template="details.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Preview"/>
+
+  <defaultView
+      for="book.messageboard.interfaces.IMessage"
+      name="details.html"/>
+
+  <page
+      name="thread.html"
+      for="book.messageboard.interfaces.IMessage"
+      class=".thread.Thread"
+      template="thread.pt"
+      permission="book.messageboard.View"
+      menu="zmi_views" title="Thread"/>
+
+  <icon
+      name="zmi_icon"
+      for="book.messageboard.interfaces.IMessage"
+      file="message.png" />
+
+
+  <zope:view
+      type="zope.publisher.interfaces.browser.IBrowserRequest"
+      for="book.messageboard.interfaces.IHTML"
+      provides="zope.app.form.interfaces.IInputWidget"
+      factory=".widgets.HTMLSourceWidget"
+      permission="zope.Public"
+      />
+
+  <pages
+      for="book.messageboard.interfaces.IMessage"
+      class=".message.MailSubscriptions"
+      permission="book.messageboard.Edit"
+      >
+    <page 
+        name="subscriptions.html" 
+        template="subscriptions.pt"
+        menu="zmi_views" title="Subscriptions" 
+        />
+    <page 
+       name="changeSubscriptions.html" 
+       attribute="change" 
+       />
+  </pages>
+
+  <include package=".skin" />
+
+</configure>

Added: messageboard/trunk/step12/browser/details.pt
===================================================================
--- messageboard/trunk/step12/browser/details.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/details.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Message Details</h1>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Title</div>
+            <div class="field" tal:content="context/title" />
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Author</div>
+            <div class="field" tal:content="view/author"/>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Date/Time</div>
+            <div class="field" tal:content="view/modified"/>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Parent</div>
+            <div class="field" tal:define="info view/parent_info">
+              <a href="../" 
+                  tal:condition="info"
+                  tal:content="info/title" />
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">Body</div>
+            <div class="field" tal:content="structure context/body"/>
+        </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step12/browser/ftests/__init__.py
===================================================================
--- messageboard/trunk/step12/browser/ftests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/ftests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step12/browser/ftests/test_message.py
===================================================================
--- messageboard/trunk/step12/browser/ftests/test_message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/ftests/test_message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Functional Tests
+
+$Id$
+"""
+import unittest
+from zope.app.tests.functional import BrowserTestCase
+
+class MessageTest(BrowserTestCase):
+
+    def testAddMessage(self):
+        response = self.publish(
+            '/+/AddMessageBoard.html=board',
+            basic='mgr:mgrpw',
+            form={'field.description': u'Message Board',
+                  'UPDATE_SUBMIT': 'Add'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/@@contents.html')
+        response = self.publish(
+            '/board/+/AddMessage.html=msg1',
+            basic='mgr:mgrpw',
+            form={'field.title': u'Message 1',
+                  'field.body': u'Body',
+                  'UPDATE_SUBMIT': 'Add'})
+        self.assertEqual(response.getStatus(), 302)
+        self.assertEqual(response.getHeader('Location'),
+                         'http://localhost/board/@@contents.html')
+       
+    def testMessageDetails(self):
+        self.testAddMessage()
+        response = self.publish('/board/msg1/@@details.html',
+                                basic='mgr:mgrpw')
+        body = response.getBody()
+        self.checkForBrokenLinks(body, '/board/msg1/@@details.html',
+                                 basic='mgr:mgrpw')
+        
+        self.assert_(body.find('Message Details') > 0)
+        self.assert_(body.find('Message 1') > 0)
+        self.assert_(body.find('Body') > 0)
+        
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MessageTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step12/browser/help/board_review.rst
===================================================================
--- messageboard/trunk/step12/browser/help/board_review.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/help/board_review.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,3 @@
+This view lists all messages in the board that are pending for
+publication. Each listed method is a link that brings you to the
+message's "Workflow" view where you can initiate a transition.

Added: messageboard/trunk/step12/browser/help/msg_edit.rst
===================================================================
--- messageboard/trunk/step12/browser/help/msg_edit.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/help/msg_edit.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,9 @@
+  This screen allows you to edit the data (i.e. the subject and body) of
+  the Message object.
+  
+  title - A one line unicode text string that briefly describes the
+          purpose/subject of the message.
+  
+  body - A multiple line unicode text string that is the actual content of 
+         the message. It is accepting HTML, but restricts the user to a 
+         couple of selected tags. Feel free to type anything you wish.

Added: messageboard/trunk/step12/browser/help/package_intro.rst
===================================================================
--- messageboard/trunk/step12/browser/help/package_intro.rst	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/help/package_intro.rst	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,6 @@
+==========================
+Message Board Demo Package
+==========================
+
+This package demos various features of the Zope 3 Framework. If you 
+have questions or concerns, please let me know. 

Added: messageboard/trunk/step12/browser/message.png
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step12/browser/message.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step12/browser/message.py
===================================================================
--- messageboard/trunk/step12/browser/message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,70 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser Views for IMessage
+
+$Id: message.py,v 1.3 2003/12/13 17:24:36 srichter Exp $
+"""
+from zope.i18n import MessageIDFactory
+
+from zope.app import zapi
+from zope.app.dublincore.interfaces import ICMFDublinCore
+
+from book.messageboard.interfaces import IMessage
+from book.messageboard.interfaces import IMailSubscriptions
+
+_ = MessageIDFactory('messageboard')
+
+class MessageDetails:
+
+    def author(self):
+        """Get user who last modified the Message."""
+        creators = ICMFDublinCore(self.context).creators
+        if not creators:
+            return _('unknown')
+        return creators[0]
+
+    def modified(self):
+        """Get last modification date."""
+        date = ICMFDublinCore(self.context).modified
+        if date is None:
+            date = ICMFDublinCore(self.context).created
+        if date is None:
+            return ''
+        formatter = self.request.locale.dates.getFormatter('dateTime', 'short')
+        return formatter.format(date)
+
+    def parent_info(self):
+        """Get the parent of the message"""
+        parent = zapi.getParent(self.context)
+        if not IMessage.providedBy(parent):
+            return None
+        return {'name': zapi.name(parent), 'title': parent.title}
+
+
+class MailSubscriptions:
+
+    def subscriptions(self):
+        return IMailSubscriptions(self.context).getSubscriptions()
+
+    def change(self):
+        if 'ADD' in self.request:
+            emails = self.request['emails'].split('\n')
+            IMailSubscriptions(self.context).addSubscriptions(emails)
+        elif 'REMOVE' in self.request:
+            emails = self.request['remails']
+            if isinstance(emails, (str, unicode)):
+                emails = [emails]
+            IMailSubscriptions(self.context).removeSubscriptions(emails)
+
+        self.request.response.redirect('./@@subscriptions.html')

Added: messageboard/trunk/step12/browser/messageboard.png
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step12/browser/messageboard.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step12/browser/messageboard.py
===================================================================
--- messageboard/trunk/step12/browser/messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,126 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser Views for IMessageBoard
+
+$Id$
+"""
+import os
+from zope.proxy import removeAllProxies
+
+from zope.app import zapi
+from zope.app.registration.interfaces import ActiveStatus
+from zope.app.site.interfaces import ISite
+from zope.app.site.service import SiteManager, ServiceRegistration
+from zope.app.utility.utility import LocalUtilityService, UtilityRegistration
+from zope.app.workflow.interfaces import IProcessDefinitionImportHandler
+from zope.app.workflow.interfaces import IProcessInstanceContainer
+from zope.app.workflow.stateful.contentworkflow import ContentWorkflowsManager
+from zope.app.workflow.stateful.definition import StatefulProcessDefinition
+from zope.app.workflow.stateful.interfaces import IContentWorkflowsManager
+from zope.app.workflow.stateful.interfaces import IStatefulProcessDefinition
+
+import book.messageboard
+from book.messageboard.interfaces import IMessage
+
+
+class AddMessageBoard(object):
+    """Add a message board."""
+  
+    def createAndAdd(self, data):
+        content = super(AddMessageBoard, self).createAndAdd(data)
+  
+        if self.request.get('workflow'):
+            folder = removeAllProxies(zapi.getParent(content))
+            if not ISite.providedBy(folder):
+                sm = SiteManager(folder)
+                folder.setSiteManager(sm)
+            default = zapi.traverse(folder.getSiteManager(), 'default')
+ 
+            # Create Local Utility Service
+            default['Utilities'] = LocalUtilityService()
+            rm = default.getRegistrationManager()
+            registration = ServiceRegistration(zapi.servicenames.Utilities,
+                                               'Utilities', rm)
+            key = rm.addRegistration(registration)
+            zapi.traverse(rm, key).status = ActiveStatus
+
+            # Create the process definition
+            default['publish-message'] = StatefulProcessDefinition()
+            pd_path = zapi.getPath(default['publish-message'])
+            registration = UtilityRegistration(
+                'publish-message', IStatefulProcessDefinition, pd_path)
+            pd_id = rm.addRegistration(registration)
+            zapi.traverse(rm, pd_id).status = ActiveStatus
+
+            import_util = IProcessDefinitionImportHandler(
+                default['publish-message'])
+            
+            xml = os.path.join(
+                os.path.dirname(book.messageboard.__file__), 'workflow.xml')
+                
+            import_util.doImport(open(xml, mode='r').read())
+  
+            # Create Content Workflows Manager
+            default['ContentWorkflows'] = ContentWorkflowsManager()
+            cm_path = zapi.getPath(default['ContentWorkflows'])
+            registration = UtilityRegistration(
+                'wfcontentmgr', IContentWorkflowsManager, cm_path)
+            cm_id = rm.addRegistration(registration)
+            zapi.traverse(rm, cm_id).status = ActiveStatus
+
+            contentmgr = default['ContentWorkflows']
+            contentmgr.register(IMessage, 'publish-message')
+
+        return content
+
+
+class ReviewMessages:
+    """Workflow: Review all pending messages"""
+
+    def getPendingMessages(self, pmsg):
+        """Get all pending messages recursively."""
+        msgs = []
+        for name, msg in pmsg.items():
+            if IMessage.providedBy(msg):
+                if hasMessageStatus(msg, 'pending'):
+                    msgs.append(msg)
+                msgs += self.getPendingMessages(msg)
+        return msgs
+
+    def getPendingMessagesInfo(self):
+        """Get all the display info for pending messages"""
+        msg_infos = []
+        for msg in self.getPendingMessages(self.context):
+            info = {}
+            info['title'] = msg.title
+            info['url'] = zapi.getView(
+                msg, 'absolute_url', self.request)() + '/@@workflows.html'
+            msg_infos.append(info)
+        return msg_infos
+
+
+def hasMessageStatus(msg, status, workflow='publish-message'):
+    """Check whether a particular message matches a given status"""
+    adapter = IProcessInstanceContainer(msg)
+    if adapter:
+        # No workflow is defined, so the message is always shown.
+        if not adapter.keys():
+            return True
+        for item in adapter.values():
+            if item.processDefinitionName != workflow:
+                continue
+            if item.status == status:
+                return True
+
+    return False

Added: messageboard/trunk/step12/browser/messageboard_add.pt
===================================================================
--- messageboard/trunk/step12/browser/messageboard_add.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/messageboard_add.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,25 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <div metal:use-macro="views/form_macros/addform">
+
+        <div metal:fill-slot="extra_bottom" class="row">
+          <div class="field">
+            <h3><input type="checkbox" name="workflow:int" 
+                    value="1" checked=""/>
+              <span i18n:translate="">Create Workflow</span>
+            </h3>
+            <span i18n:translate="">Without the workflow you will 
+              not be able to review messages before they are 
+              published. Note that you can always modify the 
+              messageboard workflow later to make all transitions 
+              automatically.</span>
+          </div>
+        </div>
+
+      </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step12/browser/review.pt
===================================================================
--- messageboard/trunk/step12/browser/review.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/review.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,16 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Pending Messages</h1>
+
+      <div class="row" tal:repeat="msg view/getPendingMessagesInfo">
+        <div class="field">
+          <a href="" tal:attributes="href msg/url"
+              tal:content="msg/title" />
+        </div>
+      </div>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step12/browser/skin/__init__.py
===================================================================
--- messageboard/trunk/step12/browser/skin/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# make this directory a package

Added: messageboard/trunk/step12/browser/skin/board.css
===================================================================
--- messageboard/trunk/step12/browser/skin/board.css	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/board.css	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,187 @@
+/*******************************************************************************
+*
+* Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+* All Rights Reserved.
+*
+* This software is subject to the provisions of the Zope Public License,
+* Version 2.0 (ZPL).  A copy of the ZPL 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.
+*
+*******************************************************************************/
+/*
+  CSS 2 compatible stylesheet for the board skin
+
+  $Id: board.css,v 1.1 2003/08/23 01:18:46 srichter Exp $
+*/
+
+body {
+    font-family: Verdana, Arial, Helvetica, sans-serif;
+    background: white;
+    color: black;
+    margin: 0;
+    padding: 0pt;
+}
+
+h1, h2, h3, h4, h5, h6 { 
+    font-weight: bold; 
+    color: black; 
+}
+
+/* Different headers are used for the same purpose, so make them all equal. */
+
+h1, h2, h3 { 
+    font-size: 20pt;
+    margin-top: 0px; 
+    margin-bottom: .8em;
+    border-bottom: solid 1px #1E5ADB;
+}
+
+p {
+    margin-top: 0.4em; 
+    margin-bottom: 0.4em;  
+}
+
+a {
+    color: #1E5ADB;
+}
+
+a:visited {
+    color: #000066;
+}
+
+textarea {
+  font-family: monospace;
+}
+
+
+/* Header Stuff */
+
+#board_header {
+    background: #EEEEEE;
+    border: solid 1px #AAAAAA;
+    padding: 3pt;
+    clear: both;
+}
+
+#board_logo {
+    float: left;
+    height: 63px;
+}
+
+#board_greeting {
+    font-size: 30pt;
+    padding-top: 5pt;
+    padding-bottom: 5pt;
+    font-weight: bold;
+    color: #1E5ADB;
+}
+
+/* Footer stuff */
+
+#footer {
+    background: #EEEEEE;
+    border: solid 1px #AAAAAA;
+    padding: 0.5em;
+    font-size: 85%;
+}
+
+#actions {
+    float: left;
+    vertical-align: middle;
+}
+
+#credits {
+    font-size: 10pt;
+    text-align: right;
+}
+
+/* Generic Content */
+
+#content {
+    padding: 1em 2em;
+}
+
+/* Intro */
+
+p.board_description {
+    background: #EEEEEE;
+    border: solid 1px #1E5ADB;
+    padding: 0.5em;
+}
+
+#login_link {
+    padding: 1em 0em;
+    text-decoration: underline;
+    font-weight: bold;
+    text-align: center;
+}
+
+/* Posts */
+
+#summary {
+    padding-bottom: 1em;
+}
+
+#summary_title {
+    font-weight: bold;
+}
+
+#summary_body {
+    font-size: 80%;
+}
+
+#summary_metadata {
+    font-size: 70%;
+    color: #444444;
+}
+
+/* Review */
+
+#message_line {
+    padding: 0.2em 0.2em;
+}
+
+/* Details */
+
+#details_title {
+    margin-bottom: 2pt;
+}
+
+#details_body {
+    background: #EEEEEE;
+    padding: 0.5em;
+    margin-bottom: 20pt;
+}
+
+#details_metadata {
+    font-size: 70%;
+    padding-bottom: 15pt;
+}
+
+#details_thread {
+    font-size: 70%;
+}
+
+/* Structural Elements */
+
+div.row {
+    clear: both;
+    padding-top: 10px;
+}
+
+div.row div.label {
+    float: left;
+    font-size: 80%;
+    width: 100px;
+    text-align: right;
+    margin-right: 0.8em;
+}
+
+div.row div.field {
+    float: left;
+    font-size: 80%;
+    text-align: left;
+}

Added: messageboard/trunk/step12/browser/skin/board_intro.pt
===================================================================
--- messageboard/trunk/step12/browser/skin/board_intro.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/board_intro.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,18 @@
+  <html metal:use-macro="views/standard_macros/page">
+    <body>
+      <div id="content" metal:fill-slot="body" 
+          i18n:domain="messageboard">
+  
+        <h2 i18n:translate="">Welcome to the Message Board</h2>
+  
+        <p class="board_description" tal:content="context/description">
+          Description of the Message Board goes here.
+        </p>
+  
+        <div id="login_link">
+          <a href="./posts.html">Click here to enter the board.</a>
+        </div>
+  
+      </div>
+    </body>
+  </html>
\ No newline at end of file

Added: messageboard/trunk/step12/browser/skin/board_posts.pt
===================================================================
--- messageboard/trunk/step12/browser/skin/board_posts.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/board_posts.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,33 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h2 i18n:translate="">Posts</h2>
+
+      <div id="summary" tal:repeat="post view/getPosts">
+        <div id="summary_title" >
+          <a href="" tal:attributes="href post/url"
+             tal:content="post/title">Message Title</a>
+        </div>
+
+        <div id="summary_body" tal:content="post/body">
+          Message Description Sneak Preview goes here...
+        </div>
+
+        <div id="summary_metadata">
+          Posted by <b tal:content="post/creator">Creator</b> 
+          on <b tal:replace="post/created">2003/01/01</b>
+          - <b tal:replace="post/replies">3</b> replies
+        </div>
+      </div>
+
+    </div>
+    <div id="actions" metal:fill-slot="actions">
+      <a href="" tal:attributes="href string:+/AddMessage.html="
+        i18n:translate="">Create new Post</a> 
+      |
+      <a href="./@@review.html"
+        i18n:translate="">Review Messages</a>
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step12/browser/skin/board_review.pt
===================================================================
--- messageboard/trunk/step12/browser/skin/board_review.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/board_review.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,31 @@
+  <html metal:use-macro="views/standard_macros/page">
+    <body>
+      <div metal:fill-slot="body" i18n:domain="messageboard">
+  
+        <h2 i18n:translate="">Review Pending Posts</h2>
+  
+        <form action="updateStatus.html" method="POST">
+  
+          <div id="message_line" 
+              tal:repeat="post view/getPendingMessagesInfo">
+            <input type="checkbox" name="messages" value=""
+              tal:attributes="value post/path" />
+            <a href="" tal:attributes="href post/url"
+              tal:content="post/title">Message Title</a>
+            <div style="font-size: 70%">
+              (Posted by <b tal:content="post/creator">Creator</b> 
+              on <b tal:replace="post/created">2003/01/01</b>)
+            </div>
+          </div>
+          <br />
+          <input type="submit" value="Publish" 
+              i18n:attributes="value" />
+  
+        </form>
+  
+      </div>
+      <div id="actions" metal:fill-slot="actions">
+        <a href="posts.html" i18n:translate="">View Posts</a>
+      </div>
+    </body>
+  </html>

Added: messageboard/trunk/step12/browser/skin/configure.zcml
===================================================================
--- messageboard/trunk/step12/browser/skin/configure.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/configure.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,78 @@
+<configure
+    xmlns="http://namespaces.zope.org/browser">
+
+  <layer name="board"/>
+
+  <skin name="board" layers="board rotterdam default" />
+  
+  <resource 
+      name="board.css" file="board.css" layer="board" />
+  
+  <resource 
+      name="logo.png" file="logo.png" layer="board" />
+
+  <page 
+      for="*"
+      name="skin_macros"
+      permission="zope.View"
+      layer="board"
+      template="template.pt" />
+  
+  <page
+      for="*"
+      name="dialog_macros"
+      permission="zope.View"
+      layer="board"
+      template="dialog_macros.pt" />
+
+  <page 
+      for="book.messageboard.interfaces.IMessageBoard"
+      name="intro.html"
+      permission="book.messageboard.View"
+      layer="board"
+      template="board_intro.pt" />
+
+  <page 
+      for="book.messageboard.interfaces.IMessageBoard"
+      name="posts.html"
+      permission="book.messageboard.View"
+      layer="board"
+      class=".views.Posts"
+      template="board_posts.pt" />
+
+  <addform
+      label="Add Message"
+      name="AddMessage.html"
+      schema="book.messageboard.interfaces.IMessage"
+      content_factory="book.messageboard.message.Message"
+      permission="book.messageboard.Add"
+      class=".views.AddMessage"
+      layer="board"/>
+
+  <pages
+      for="book.messageboard.interfaces.IMessageBoard"
+      class=".views.Review"
+      permission="book.messageboard.PublishContent"
+      layer="board">
+      <page name="review.html" template="board_review.pt"/>
+      <page name="updateStatus.html" attribute="updateStatus"/>
+  </pages>
+
+  <page
+      for="book.messageboard.interfaces.IMessage"
+      name="details.html"
+      permission="book.messageboard.View"
+      layer="board"
+      class=".views.Details"
+      template="msg_details.pt" />
+
+  <addform
+      label="Reply to Message"
+      name="ReplyMessage"
+      schema="book.messageboard.interfaces.IMessage"
+      content_factory="book.messageboard.message.Message"
+      permission="book.messageboard.Add"
+      class=".views.ReplyMessage"
+      layer="board"/>
+
+</configure>
\ No newline at end of file

Added: messageboard/trunk/step12/browser/skin/dialog_macros.pt
===================================================================
--- messageboard/trunk/step12/browser/skin/dialog_macros.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/dialog_macros.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,62 @@
+<metal:block define-macro="dialog">
+  <metal:block define-slot="doctype">
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+  </metal:block>
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+  <head>
+    <title metal:define-slot="title">Message Board for Zope 3</title>
+
+
+    <style type="text/css" media="all"
+        tal:content="string: @import url(${context/++resource++board.css});">
+      @import url(board.css);
+    </style>
+        
+    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+
+    <link rel="icon" type="image/png"
+          tal:attributes="href context/++resource++favicon.png" />
+  </head>
+
+  <body>
+
+    <div id="board_header" i18n:domain="messageboard">
+      <img id="board_logo" tal:attributes="src context/++resource++logo.png" />
+      <div id="board_greeting">&nbsp;
+        <span i18n:translate="">Zope 3 Message Board</span>
+      </div>
+    </div>
+
+    <div id="workspace">
+
+      <div metal:define-slot="message" id="message"></div>
+      
+      <div id="content">
+        <metal:block define-slot="body">
+	  This is the content.
+        </metal:block>
+      </div>
+
+    </div>
+
+    <div id="footer">
+
+      <div id="actions">
+        <metal:block define-slot="actions">
+        </metal:block>
+      </div>
+      <div id="credits" i18n:domain="messageboard">
+        Powered by Zope 3.<br />
+        Stephan Richter in 2003
+      </div>
+    </div>
+
+  </body>
+
+</html>
+
+</metal:block>
+

Added: messageboard/trunk/step12/browser/skin/logo.png
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step12/browser/skin/logo.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step12/browser/skin/msg_details.pt
===================================================================
--- messageboard/trunk/step12/browser/skin/msg_details.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/msg_details.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,31 @@
+<html metal:use-macro="views/standard_macros/page">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h2 id="details_title" tal:content="context/title" 
+        i18n:translate="">Post Title</h2>
+
+      <div id="details_metadata" tal:define="md view/metadata">
+        Posted by <b tal:content="md/creator">Creator</b> 
+        on <b tal:replace="md/created">2003/01/01</b>
+      </div>
+
+      <div id="details_body" tal:content="structure view/htmlBody">
+        Message Body goes here
+      </div>
+
+      <b>Replies:</b>
+      <div id="details_thread">
+        <div tal:replace="structure view/subthread" />
+      </div>
+
+    </div>
+    <div id="actions" metal:fill-slot="actions">
+      <a href="" tal:attributes="href view/getParentURL"
+        i18n:translate="">Back to Parent</a>
+      |
+      <a href="+/ReplyMessage="
+        i18n:translate="">Reply to Post</a>
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step12/browser/skin/template.pt
===================================================================
--- messageboard/trunk/step12/browser/skin/template.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/template.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,62 @@
+  <metal:block define-macro="page">
+    <metal:block define-slot="doctype">
+      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+    </metal:block>
+  
+  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+  
+    <head>
+      <title metal:define-slot="title">Message Board for Zope 3</title>
+  
+      <style type="text/css" media="all"
+         tal:content=
+         "string: @import url(${context/++resource++board.css});">
+        @import url(board.css);
+      </style>
+          
+      <meta http-equiv="Content-Type" 
+          content="text/html;charset=utf-8" />
+  
+      <link rel="icon" type="image/png"
+            tal:attributes="href context/++resource++favicon.png" />
+    </head>
+  
+    <body>
+  
+      <div id="board_header" i18n:domain="messageboard">
+        <img id="board_logo" 
+            tal:attributes="src context/++resource++logo.png" />
+        <div id="board_greeting">&nbsp;
+           <span i18n:translate="">Zope 3 Message Board</span>
+        </div>
+      </div>
+  
+      <div id="workspace">
+  
+        <div metal:define-slot="message" id="message"></div>
+        
+        <div id="content">
+          <metal:block define-slot="body">
+         This is the content.
+          </metal:block>
+        </div>
+  
+      </div>
+  
+      <div id="footer">
+  
+        <div id="actions">
+          <metal:block define-slot="actions" />
+        </div>
+        <div id="credits" i18n:domain="messageboard">
+          Powered by Zope 3.<br>
+          Stephan Richter in 2003
+        </div>
+      </div>
+  
+    </body>
+  
+  </html>
+  
+  </metal:block>
\ No newline at end of file

Added: messageboard/trunk/step12/browser/skin/views.py
===================================================================
--- messageboard/trunk/step12/browser/skin/views.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/skin/views.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,165 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Skin for the message board
+
+$Id$
+"""
+import random, time, cgi
+
+from zope.interface import implements
+from zope.app import zapi
+from zope.app.dublincore.interfaces import ICMFDublinCore
+from zope.app.form.interfaces import IInputWidget
+from zope.app.form.utility import setUpWidgets
+from zope.app.workflow.interfaces import IProcessInstanceContainer
+
+from book.messageboard.interfaces import IMessage
+from book.messageboard.browser.messageboard import hasMessageStatus
+from book.messageboard.browser.messageboard import ReviewMessages
+from book.messageboard.browser.thread import Thread
+
+class Posts(object):
+
+    def getPosts(self):
+        """Get first level post infos as ZPT-friendly dict."""
+        posts = []
+        for name, post in self.context.items():
+            if hasMessageStatus(post, 'published'):
+                dc = ICMFDublinCore(post)
+                info = {}
+                info['title'] = post.title
+                if len(post.body) > 100:
+                    body = post.body[:100] + '...'
+                else:
+                    body = post.body
+                info['body'] = body
+                
+                replies = 0
+                for obj in post.values():
+                    if IMessage.providedBy(obj):
+                        replies += 1
+                info['replies'] = replies
+                
+                info['creator'] = dc.creators[0]
+                formatter = self.request.locale.dates.getFormatter(
+                    'dateTime', 'short')
+                info['created'] = formatter.format(dc.created)
+                info['url'] = './' + name + '/@@details.html'
+                posts.append(info)
+        return posts
+  
+class AddMessage(object):
+    """Add-Form supporting class."""
+
+    def nextURL(self):
+        return '../@@posts.html'
+
+    def namesAccepted(self):
+        return False
+
+  
+class Review(ReviewMessages):
+    """Review messages for publication."""
+    
+    def getPendingMessagesInfo(self):
+        """Get all the display info for pending messages"""
+        msg_infos = []
+        for msg in self.getPendingMessages(self.context):
+            dc = ICMFDublinCore(msg)
+            info = {}
+            info['path'] = zapi.getPath(msg)
+            info['title'] = msg.title
+            info['creator'] = dc.creators[0]
+            formatter = self.request.locale.dates.getFormatter(
+                'dateTime', 'medium')
+            info['created'] = formatter.format(dc.created)
+            info['url'] = zapi.getView(
+                msg, 'absolute_url', self.request)() + \
+                '/@@details.html'
+            msg_infos.append(info)
+        return msg_infos
+  
+    def updateStatus(self, messages):
+        """Upgrade the stati from 'pending' to 'published'."""
+        if not isinstance(messages, (list, tuple)):
+            messages = [messages]
+            
+        for path in messages:
+            msg = zapi.traverse(self.context, path)
+  
+            adapter = IProcessInstanceContainer(msg)
+            adapter['publish-message'].fireTransition('pending_published')
+  
+        return self.request.response.redirect('@@review.html')
+
+class Details(Thread, object):
+    """Message Details view class."""
+
+    def metadata(self):
+        """Get the relevant metadata from the message."""
+        dc = ICMFDublinCore(self.context)
+        info = {}
+        info['creator'] = dc.creators[0]
+        formatter = self.request.locale.dates.getFormatter('dateTime', 'medium')
+        info['created'] = formatter.format(dc.created)
+        return info
+
+    def htmlBody(self):
+        """Make the body HTML-savy."""
+        body = cgi.escape(self.context.body)
+        return body.replace('\n', '<br/>\n')
+
+    def getParentURL(self):
+        """Get the best view URL for the parent.
+
+        Since messages can be contained by message boards and messages, we
+        have different URLs based on the parent type.
+        """
+        parent = zapi.getParent(self.context)
+        if IMessage.providedBy(parent):
+            return '../@@details.html'
+        else:
+            return '../@@posts.html'
+
+    def listContentInfo(self):
+        """Override the thread listContentInfo() to get an appropriate URL."""
+        info = super(Details, self).listContentInfo()
+        for item in info:
+            item['url'] = item['url'].replace('@@thread.html', '@@details.html')
+        return info
+
+
+class ReplyMessage:
+    """Add-Form supporting class."""
+  
+    def nextURL(self):
+        return '../@@details.html'
+  
+    def _setUpWidgets(self):
+        """Alllow addforms to also have default values."""
+        parent = self.context.context
+        title = parent.title
+        if not title.startswith('Re:'):
+            title = 'Re: ' + parent.title
+  
+        dc = ICMFDublinCore(parent)
+        formatter = self.request.locale.dates.getFormatter(
+              'dateTime', 'medium')
+        body = '%s on %s wrote:\n' %(dc.creators[0], 
+                                    formatter.format(dc.created))
+        body += '> ' + parent.body.replace('\n', '\n> ')
+          
+        setUpWidgets(self, self.schema, IInputWidget, 
+                     initial={'title': title, 'body': body},
+                     names=self.fieldNames)

Added: messageboard/trunk/step12/browser/subscriptions.pt
===================================================================
--- messageboard/trunk/step12/browser/subscriptions.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/subscriptions.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,43 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <form action="changeSubscriptions.html" method="post">
+
+        <div class="row">
+            <div class="label" 
+                i18n:translate="">Current Subscriptions</div>
+            <div class="field">
+           <div tal:repeat="email view/subscriptions">
+                <input type="checkbox" name="remails:list" 
+                       value="" tal:attributes="value email">
+                <div tal:replace="email">zope3 at zope3.org</div>
+              </div>
+              <input type="submit" name="REMOVE" value="Remove" 
+                   i18n:attributes="value remove-button">
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="label" i18n:translate="">
+              Enter new Users (separate by 'Return')
+            </div>
+            <div class="field">
+           <textarea name="emails" cols="40" rows="10"></textarea>
+            </div>
+        </div>
+
+             <div class="row">
+               <div class="controls">
+                 <input type="submit" value="Refresh" 
+                i18n:attributes="value refresh-button" />
+                 <input type="submit" name="ADD" value="Add" 
+                     i18n:attributes="value add-button" />
+               </div>
+             </div>
+
+      </form>
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step12/browser/subthread.pt
===================================================================
--- messageboard/trunk/step12/browser/subthread.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/subthread.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,8 @@
+<ul>
+  <li tal:repeat="item view/listContentInfo">
+    <a href="" 
+        tal:attributes="href item/url"
+        tal:content="item/title">Message 1</a>
+    <div tal:replace="structure item/thread"/>
+  </li>
+</ul>
\ No newline at end of file

Added: messageboard/trunk/step12/browser/tests/__init__.py
===================================================================
--- messageboard/trunk/step12/browser/tests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/tests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step12/browser/tests/test_widgets.py
===================================================================
--- messageboard/trunk/step12/browser/tests/test_widgets.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/tests/test_widgets.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,76 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""HTMLSourceWidget Tests
+
+$Id: test_widgets.py,v 1.1 2003/06/10 14:40:44 srichter Exp $
+"""
+import unittest
+from zope.app.form.browser.tests.test_textareawidget import TextAreaWidgetTest
+from book.messageboard.browser.widgets import HTMLSourceWidget
+from book.messageboard.fields import HTML
+
+class HTMLSourceWidgetTest(TextAreaWidgetTest):
+
+    _FieldFactory = HTML
+    _WidgetFactory = HTMLSourceWidget
+
+
+    def test_AllowedTagsConvert(self):
+        widget = self._widget
+        widget.context.allowed_tags=('h1','pre')
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1>Blah</h1>')) 
+        self.assertEqual(u'<pre>Blah</pre>',
+                         widget._toFieldValue(u'<pre>Blah</pre>') )
+        self.assertEqual(u'<h1><pre>Blah</pre></h1>',
+                         widget._toFieldValue(u'<h1><pre>Blah</pre></h1>')) 
+        self.assertEqual(u'<h1 attr=".">Blah</h1>',
+                         widget._toFieldValue(u'<h1 attr=".">Blah</h1>')) 
+
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2>Blah</h2>')) 
+        self.assertEqual(u'<pre>Blah</pre>',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2 a="b">Blah</h2>')) 
+
+
+    def test_ForbiddenTagsConvert(self):
+        widget = self._widget
+        widget.context.forbidden_tags=('h2','pre')
+
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1>Blah</h1>')) 
+        self.assertEqual(u'<h1 a="b">Blah</h1>',
+                         widget._toFieldValue(u'<h1 a="b">Blah</h1>')) 
+
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2>Blah</h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<pre>Blah</pre>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'Blah',
+                         widget._toFieldValue(u'<h2><pre>Blah</pre></h2>')) 
+        self.assertEqual(u'<h1>Blah</h1>',
+                         widget._toFieldValue(u'<h1><pre>Blah</pre></h1>')) 
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(HTMLSourceWidgetTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step12/browser/thread.pt
===================================================================
--- messageboard/trunk/step12/browser/thread.pt	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/thread.pt	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,11 @@
+<html metal:use-macro="views/standard_macros/view">
+  <body>
+    <div metal:fill-slot="body" i18n:domain="messageboard">
+
+      <h1 i18n:translate="">Discussion Thread</h1>
+
+      <div tal:replace="structure view/subthread" />
+
+    </div>
+  </body>
+</html>

Added: messageboard/trunk/step12/browser/thread.py
===================================================================
--- messageboard/trunk/step12/browser/thread.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/thread.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,45 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Browser View for the sub-thread of a Message or MessageBoard
+
+$Id$
+"""
+from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
+
+from book.messageboard.interfaces import IMessage
+from messageboard import hasMessageStatus
+
+class Thread:
+
+    def __init__(self, context, request, base_url=''):
+        self.context = context
+        self.request = request
+        self.base_url = base_url
+
+    def listContentInfo(self):
+        children = []
+        for name, child in self.context.items():
+            if IMessage.providedBy(child) and \
+                   hasMessageStatus(child, 'published'):
+                info = {}
+                info['title'] = child.title
+                url = self.base_url + name + '/'
+                info['url'] = url + '@@thread.html'
+                thread = Thread(child, self.request, url)
+                info['thread'] = thread.subthread()
+                children.append(info)
+        return children
+
+    subthread = ViewPageTemplateFile('subthread.pt')
+

Added: messageboard/trunk/step12/browser/widgets.py
===================================================================
--- messageboard/trunk/step12/browser/widgets.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/browser/widgets.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Module containing custom widget definitions.
+
+$Id: widgets.py,v 1.1 2003/06/10 14:40:44 srichter Exp $
+"""
+import re
+from zope.app.form.browser import TextAreaWidget
+from book.messageboard.fields import forbidden_regex, allowed_regex
+
+class HTMLSourceWidget(TextAreaWidget):
+
+    def _toFieldValue(self, input):
+        input = super(HTMLSourceWidget, self)._toFieldValue(input)
+
+        if self.context.forbidden_tags:
+            regex = forbidden_regex %'|'.join(
+                self.context.forbidden_tags)
+            input = re.sub(regex, '', input)
+
+        if self.context.allowed_tags:
+            regex = allowed_regex %'[ />]|'.join(
+                self.context.allowed_tags)
+            input = re.sub(regex, '', input)
+
+        return input
+

Added: messageboard/trunk/step12/configure.zcml
===================================================================
--- messageboard/trunk/step12/configure.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/configure.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,205 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    xmlns:mail="http://namespaces.zope.org/mail"
+    i18n_domain="messageboard">
+
+  <permission
+      id="book.messageboard.View"
+      title="View Message Board and Messages"
+      description="View the Message Board and all its content."
+      />
+  <permission
+      id="book.messageboard.Add"
+      title="Add Message"
+      description="Add Message."
+      />
+  <permission
+      id="book.messageboard.Edit"
+      title="Edit Messages"
+      description="Edit Messages."
+      />
+  <permission
+      id="book.messageboard.Delete"
+      title="Delete Message"
+      description="Delete Message."
+      />
+
+  <permission
+      id="book.messageboard.PublishContent"
+      title="Publish Message"
+      description="Publish Message."/>
+
+  <interface 
+      interface=".interfaces.IMessageBoard" 
+      type="zope.app.content.interfaces.IContentType"
+      /> 
+
+  <content class=".messageboard.MessageBoard">
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContentContainer" 
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContainerNamesContainer"
+        />
+    <factory
+        id="book.messageboard.MessageBoard"
+        description="Message Board" 
+        />
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessageBoard"
+        />
+    <require
+        permission="book.messageboard.Edit"
+        set_schema=".interfaces.IMessageBoard"
+        />
+  </content>
+
+  <adapter
+      factory=".messageboard.PlainText"
+      provides=".interfaces.IPlainText"
+      for=".interfaces.IMessageBoard" />
+
+  <adapter
+     for=".interfaces.IMessageBoard"
+     provides="zope.app.filerepresentation.interfaces.IReadDirectory"
+     factory=".filerepresentation.ReadDirectory"
+     permission="zope.View" />
+
+  <adapter
+     for=".interfaces.IMessageBoard"
+     provides="zope.app.filerepresentation.interfaces.IDirectoryFactory"
+     factory=".filerepresentation.MessageFactory"
+     permission="zope.View" />
+
+  <interface 
+      interface=".interfaces.IMessage" 
+      type="zope.app.content.interfaces.IContentType"
+      /> 
+
+  <content class=".message.Message">
+    <implements
+        interface="zope.app.annotation.interfaces.IAttributeAnnotatable"
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContentContainer" 
+        />
+    <implements
+        interface="zope.app.container.interfaces.IContainerNamesContainer"
+        />
+    <implements interface=
+        "zope.app.workflow.interfaces.IProcessInstanceContainerAdaptable"/>
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessage"
+        />
+    <require
+        permission="book.messageboard.View"
+        interface=".interfaces.IMessageContainer"
+        />
+    <require
+        permission="book.messageboard.Add"
+        set_schema=".interfaces.IMessage"
+        />
+  </content>
+
+  <adapter
+      factory=".message.MessageSized"
+      provides="zope.app.size.interfaces.ISized"
+      for=".interfaces.IMessage"
+      />
+
+  <adapter
+      factory=".message.PlainText"
+      provides=".interfaces.IPlainText"
+      for=".interfaces.IMessage" />
+
+  <adapter
+     for=".interfaces.IMessage"
+     provides="zope.app.filerepresentation.interfaces.IReadDirectory"
+     factory=".filerepresentation.ReadDirectory"
+     permission="zope.View" />
+
+  <adapter
+     for=".interfaces.IMessage"
+     provides="zope.app.filerepresentation.interfaces.IDirectoryFactory"
+     factory=".filerepresentation.MessageFactory"
+     permission="zope.View" />
+
+  <adapter
+      factory=".message.MailSubscriptions"
+      provides=".interfaces.IMailSubscriptions"
+      for=".interfaces.IMessage"
+      permission="book.messageboard.Add"      
+      trusted="true" />
+
+  <content class=".filerepresentation.VirtualContentsFile">
+  
+    <implements interface="
+        zope.app.annotation.interfaces.IAttributeAnnotatable" />
+  
+    <require
+        permission="book.messageboard.View"
+        interface="zope.app.filerepresentation.interfaces.IReadFile" />
+  
+    <require
+        permission="book.messageboard.Edit"
+        interface="zope.app.filerepresentation.interfaces.IWriteFile"
+        set_schema="zope.app.filerepresentation.interfaces.IReadFile" />
+  
+  </content>
+
+  <xmlrpc:view
+      name="methods"
+      for=".interfaces.IMessageBoard"
+      permission="book.messageboard.Edit" 
+      allowed_attributes="getMessageNames addMessage deleteMessage 
+                          getDescription setDescription"
+      class=".xmlrpc.MessageBoardMethods" />
+
+  <xmlrpc:defaultView
+      for=".interfaces.IMessageBoard"
+      name="methods" />
+      
+  <xmlrpc:view
+      name="methods"
+      for=".interfaces.IMessage"
+      permission="book.messageboard.Edit" 
+      allowed_attributes="getMessageNames addMessage deleteMessage 
+                          getTitle setTitle getBody setBody"
+      class=".xmlrpc.MessageMethods" />
+
+  <xmlrpc:defaultView
+      for=".interfaces.IMessage"
+      name="methods" />
+
+  <mail:smtpMailer name="msgboard-smtp" hostname="localhost" port="25" />
+  
+  <mail:queuedDelivery 
+      name="msgboard-delivery"
+      permission="zope.SendMail"
+      queuePath="./mail-queue"
+      mailer="msgboard-smtp" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.event.interfaces.IObjectModifiedEvent" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.container.interfaces.IObjectAddedEvent" />
+
+  <subscriber
+      factory=".message.mailer"
+      for="zope.app.container.interfaces.IObjectRemovedEvent" />
+
+  <i18n:registerTranslations directory="locales" />
+
+  <include package=".browser" />
+
+</configure>

Added: messageboard/trunk/step12/fields.py
===================================================================
--- messageboard/trunk/step12/fields.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/fields.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Module containing custom field definitions.
+
+$Id$
+"""
+import re
+
+from zope.i18n import MessageIDFactory
+from zope.schema import Text
+from zope.schema.interfaces import ValidationError
+
+_ = MessageIDFactory('messageboard')
+
+forbidden_regex = r'</?(?:%s).*?/?>'
+allowed_regex = r'</??(?!%s[ />])[a-zA-Z0-9]*? ?(?:[a-z0-9]*?=?".*?")*/??>'
+
+class ForbiddenTags(ValidationError):
+    __doc__ = _("Forbidden HTML Tags used.")
+
+
+class HTML(Text):
+  
+    allowed_tags = ()
+    forbidden_tags = ()
+
+    def __init__(self, allowed_tags=(), forbidden_tags=(), **kw):
+        self.allowed_tags = allowed_tags
+        self.forbidden_tags = forbidden_tags
+        super(HTML, self).__init__(**kw)
+
+    def _validate(self, value):
+        super(HTML, self)._validate(value)
+
+        if self.forbidden_tags:
+            regex = forbidden_regex %'|'.join(self.forbidden_tags)
+            if re.findall(regex, value):
+                raise ForbiddenTags(value, self.forbidden_tags)
+
+        if self.allowed_tags:
+            regex = allowed_regex %'[ />]|'.join(self.allowed_tags)
+            if re.findall(regex, value):
+                raise ForbiddenTags(value, self.allowed_tags)
+
+

Added: messageboard/trunk/step12/filerepresentation.py
===================================================================
--- messageboard/trunk/step12/filerepresentation.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/filerepresentation.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,98 @@
+##############################################################################
+#
+# Copyright (c) 2003, 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""FTP Views for the MessageBoard and Message component
+
+$Id$
+"""
+from zope.interface import implements
+from interfaces import IVirtualContentsFile, IPlainText
+
+from zope.app.filerepresentation.interfaces import IReadDirectory
+from zope.app.folder.filerepresentation import \
+     ReadDirectory as ReadDirectoryBase
+from zope.app.filerepresentation.interfaces import IDirectoryFactory
+
+from message import Message
+
+
+class VirtualContentsFile(object):
+
+    implements(IVirtualContentsFile)
+
+    def __init__(self, context):
+        self.context = context
+
+    def setContentType(self, contentType):
+        '''See interface IFile'''
+        pass
+
+    def getContentType(self):
+        '''See interface IFile'''
+        return u'text/plain'
+
+    contentType = property(getContentType, setContentType)
+
+    def edit(self, data, contentType=None):
+        '''See interface IFile'''
+        self.setData(data)
+
+    def getData(self):
+        '''See interface IFile'''
+        adapter = IPlainText(self.context)
+        return adapter.getText() or u''
+
+    def setData(self, data):
+        '''See interface IFile'''
+        adapter = IPlainText(self.context)
+        return adapter.setText(data)
+
+    data = property(getData, setData)
+
+    def getSize(self):
+        '''See interface IFile'''
+        return len(self.getData())
+
+    size = property(getSize)
+
+
+class ReadDirectory(ReadDirectoryBase):
+    """An special implementation of the directory."""
+    
+    implements(IReadDirectory)
+
+    def keys(self):
+        keys = self.context.keys()
+        return list(keys) + ['contents']
+
+    def get(self, key, default=None):
+        if key == 'contents':
+            return VirtualContentsFile(self.context)
+        return self.context.get(key, default)
+
+    def __len__(self):
+        l = len(self.context)
+        return l+1
+
+
+class MessageFactory(object):
+    """A simple message factory for file system representations."""
+
+    implements(IDirectoryFactory)
+
+    def __init__(self, context):
+        self.context = context
+
+    def __call__(self, name):
+        """See IDirectoryFactory interface."""
+        return Message()

Added: messageboard/trunk/step12/interfaces.py
===================================================================
--- messageboard/trunk/step12/interfaces.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/interfaces.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,136 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Interfaces
+
+Interfaces for the Zope 3 based Message Board Package
+
+$Id$
+"""
+from zope.i18n import MessageIDFactory
+from zope.interface import classImplements, Interface
+from zope.schema import Text, TextLine, Field, Tuple
+from zope.schema.interfaces import IText
+
+from zope.app.container.constraints import ContainerTypesConstraint
+from zope.app.container.constraints import ItemTypePrecondition
+from zope.app.container.interfaces import IContained, IContainer
+from zope.app.file.interfaces import IFile, IFileContent
+
+from fields import HTML
+
+_ = MessageIDFactory('messageboard')
+
+
+class IMessage(Interface):
+    """A message object."""
+
+    title = TextLine(
+        title=_("Title/Subject"),
+        description=_("Title and/or subject of the message."),
+        default=u"",
+        required=True)
+
+    body = HTML(
+        title=_("Message Body"),
+        description=_("This is the actual message. Type whatever!"),
+        default=u"",
+        allowed_tags=('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'a',
+                      'br', 'b', 'i', 'u', 'em', 'sub', 'sup',
+                      'table', 'tr', 'td', 'th', 'code', 'pre',
+                      'center', 'div', 'span', 'p', 'font', 'ol',
+                      'ul', 'li', 'q', 's', 'strong'),
+        required=False)
+
+
+class IMessageBoard(IContainer):
+    """The message board is the base object for our package. It can only
+    contain IMessage objects."""
+
+    def __setitem__(name, object):
+        """Add a IMessage object."""
+
+    __setitem__.precondition = ItemTypePrecondition(IMessage)
+
+    description = Text(
+        title=_("Description"),
+        description=_("A detailed description of the content of the board."),
+        default=u"",
+        required=False)
+
+
+class IMessageContained(IContained):
+    """Interface that specifies the type of objects that can contain
+    messages."""
+    __parent__ = Field(
+        constraint = ContainerTypesConstraint(IMessageBoard, IMessage))
+
+
+class IMessageContainer(IContainer):
+    """We also want to make the message object a container that can contain
+    responses (other messages) and attachments (files and images)."""
+
+    def __setitem__(name, object):
+        """Add a IMessage object."""
+
+    __setitem__.precondition = ItemTypePrecondition(IMessage, IFile)
+
+
+class IHTML(IText):
+    """A text field that handles HTML input."""
+
+    allowed_tags = Tuple(
+        title=_("Allowed HTML Tags"),
+        description=_("""\
+        Only listed tags can be used in the value of the field.
+        """),
+        required=False)
+
+    forbidden_tags = Tuple(
+        title=_("Forbidden HTML Tags"),
+        description=_("""\
+        Listed tags cannot be used in the value of the field.
+        """),
+        required=False)
+
+classImplements(HTML, IHTML)
+
+
+class IMailSubscriptions(Interface):
+    """This interface allows you to retrieve a list of E-mails for
+    mailings. In our context these are messages."""
+
+    def getSubscriptions():
+        """Return a list of E-mails."""
+
+    def addSubscriptions(emails):
+        """Add a bunch of subscriptions; one would be okay too."""
+
+    def removeSubscriptions(emails):
+        """Remove a set of subscriptions."""
+
+
+class IPlainText(Interface):
+    """This interface allows you to represent an object's content in plain
+    text."""
+
+    def getText():
+        """Get a pure text representation of the object's content."""
+
+    def setText(text):
+        """Write the text to the object."""
+
+
+class IVirtualContentsFile(IFile, IFileContent):
+    """Marker Interface to mark special Message and Message Board 
+    Contents files in FS representations."""

Added: messageboard/trunk/step12/locales/de/LC_MESSAGES/messageboard.mo
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step12/locales/de/LC_MESSAGES/messageboard.mo
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step12/locales/de/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step12/locales/de/LC_MESSAGES/messageboard.po	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/locales/de/LC_MESSAGES/messageboard.po	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,323 @@
+# translation of messageboard.po to German
+# translation of messageboard.po to English US
+# Copyright (C) YEAR ORGANIZATION.
+# Stephan Richter <stephan.richter at tufts.edu>, 2003.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"POT-Creation-Date: Mon Dec 15 11:10:14 2003\n"
+"PO-Revision-Date: 2003-12-15 11:13-0500\n"
+"Last-Translator: Stephan Richter <stephan.richter at tufts.edu>\n"
+"Language-Team: German <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.4\n"
+"X-Generator: KBabel 1.3\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+msgid "add-button"
+msgstr "  Add  "
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr "Add Content"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:10
+msgid "Message Board Help"
+msgstr "Message Board Help"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:111
+msgid "Menu of objects to be added to Messages."
+msgstr "Menu of objects to be added to Messages."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:134
+#: src/zopeproducts/messageboard/browser/configure.zcml:142
+msgid "Change Message"
+msgstr "Change Message"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:150
+msgid "Preview"
+msgstr "Preview"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:158
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr "Message Details"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:18
+msgid "Menu of objects to be added to Message Boards."
+msgstr "Menu of objects to be added to Message Boards."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:186
+msgid "Subscriptions"
+msgstr "Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:191
+msgid "Message Mail Subscriptions"
+msgstr "Message Mail Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:205
+msgid "Image"
+msgstr "Image"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:211
+msgid "File"
+msgstr "File"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:22
+#: src/zopeproducts/messageboard/browser/configure.zcml:115
+msgid "Add"
+msgstr "Add"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:34
+msgid "Add Message Board"
+msgstr "Add Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:44
+#: src/zopeproducts/messageboard/browser/configure.zcml:134
+msgid "Edit"
+msgstr "Edit"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:44
+#: src/zopeproducts/messageboard/browser/configure.zcml:52
+msgid "Change Message Board"
+msgstr "Change Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:60
+#: src/zopeproducts/messageboard/browser/configure.zcml:166
+msgid "Thread"
+msgstr "Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:68
+#: src/zopeproducts/messageboard/browser/configure.zcml:174
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+#: src/zopeproducts/messageboard/browser/skin/board_overview.pt:5
+msgid "Discussion Thread"
+msgstr "Discussion Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:76
+#: src/zopeproducts/messageboard/browser/configure.zcml:217
+msgid "Contents"
+msgstr "Contents"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:84
+msgid "Review Messages"
+msgstr "Review Messages"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:92
+msgid "Publication Review"
+msgstr "Publication Review"
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr "Author"
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr "Date/Time"
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr "Parent"
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr "Body"
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr "Title"
+
+# 8/browser/messageboard_add.pt:19
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr "Workflow erstellen"
+
+# 8/browser/messageboard_add.pt:21
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+msgstr ""
+"Ohne dem Workflow wirst Du nicht in der Lage sein Anzeigen vor der "
+"Veröffentlichung zu prüfen. Es ist zu erwähnen das Du jederzeit den Workflow "
+"später ändern kannst, so dass all Übergänge automatisch ausgeführt werden."
+
+# 7/browser/subscriptions.pt:35
+# Default: "Refresh"
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+msgid "refresh-button"
+msgstr "refresh-button"
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+msgid "submit-button"
+msgstr "Abschicken"
+
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr "Schwebende Anzeigen"
+
+#: src/zopeproducts/messageboard/browser/skin/board_intro.pt:5
+msgid "Welcome to the Message Board"
+msgstr "Willkommen zum Anzeigenbrett"
+
+#: src/zopeproducts/messageboard/browser/skin/board_posts.pt:5
+msgid "Posts"
+msgstr "Anzeigen"
+
+#: src/zopeproducts/messageboard/browser/skin/board_review.pt:20
+msgid "Publish"
+msgstr "Veröffentlichen"
+
+#: src/zopeproducts/messageboard/browser/skin/board_review.pt:5
+msgid "Review Pending Posts"
+msgstr "Schwebende Anzeigen prüfen"
+
+#: src/zopeproducts/messageboard/browser/skin/configure.zcml:62
+msgid "Reply to Message"
+msgstr "Auf Anzeige antworten"
+
+#: src/zopeproducts/messageboard/browser/skin/dialog_macros.pt:29
+#: src/zopeproducts/messageboard/browser/skin/template.pt:28
+msgid "Zope 3 Message Board"
+msgstr "Zope 3 Anzeigenbrett"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr "Current Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+msgid "remove-button"
+msgstr "   Add  "
+
+# 7/browser/subscriptions.pt:25
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr "Enter new Users (separate by 'Return')"
+
+#: src/zopeproducts/messageboard/configure.zcml:109
+#: src/zopeproducts/messageboard/browser/configure.zcml:126
+#: src/zopeproducts/messageboard/browser/configure.zcml:199
+msgid "Message"
+msgstr "Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:11
+msgid "Users that actually use the Message Board."
+msgstr "Users that actually use the Message Board."
+
+#: src/zopeproducts/messageboard/configure.zcml:11
+msgid "Message Board User"
+msgstr "Message Board User"
+
+#: src/zopeproducts/messageboard/configure.zcml:16
+msgid "Message Board Editor"
+msgstr "Message Board Editor"
+
+#: src/zopeproducts/messageboard/configure.zcml:16
+msgid "The Editor can edit and delete Messages."
+msgstr "The Editor can edit and delete Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:21
+msgid "View the Message Board and all its content."
+msgstr "View the Message Board and all its content."
+
+#: src/zopeproducts/messageboard/configure.zcml:21
+msgid "View Message Board and Messages"
+msgstr "View Message Board and Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:30
+msgid "Add Message."
+msgstr "Add Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:30
+#: src/zopeproducts/messageboard/browser/skin/configure.zcml:53
+#: src/zopeproducts/messageboard/browser/configure.zcml:126
+msgid "Add Message"
+msgstr "Add Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:39
+msgid "Edit Messages."
+msgstr "Edit Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:39
+msgid "Edit Messages"
+msgstr "Edit Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:48
+msgid "Delete Message."
+msgstr "Delete Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:48
+msgid "Delete Message"
+msgstr "Delete Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:58
+msgid "Publish Message"
+msgstr "Publish Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:58
+msgid "Publish Message."
+msgstr "Publish Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:78
+#: src/zopeproducts/messageboard/browser/configure.zcml:34
+msgid "Message Board"
+msgstr "Message Board"
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr "Forbidden HTML Tags used."
+
+#: src/zopeproducts/messageboard/interfaces.py:112
+msgid "Allowed HTML Tags"
+msgstr "Allowed HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:113
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:119
+msgid "Forbidden HTML Tags"
+msgstr "Forbidden HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:120
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "Description"
+msgstr "Description"
+
+#: src/zopeproducts/messageboard/interfaces.py:40
+msgid "A detailed description of the content of the board."
+msgstr "A detailed description of the content of the board."
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title/Subject"
+msgstr "Title/Subject"
+
+#: src/zopeproducts/messageboard/interfaces.py:50
+msgid "Title and/or subject of the message."
+msgstr "Title and/or subject of the message."
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "Message Body"
+msgstr "Message Body"
+
+#: src/zopeproducts/messageboard/interfaces.py:56
+msgid "This is the actual message. Type whatever!"
+msgstr "This is the actual message. Type whatever!"
+

Added: messageboard/trunk/step12/locales/en/LC_MESSAGES/messageboard.mo
===================================================================
(Binary files differ)


Property changes on: messageboard/trunk/step12/locales/en/LC_MESSAGES/messageboard.mo
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: messageboard/trunk/step12/locales/en/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step12/locales/en/LC_MESSAGES/messageboard.po	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/locales/en/LC_MESSAGES/messageboard.po	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,323 @@
+# translation of messageboard.po to German
+# translation of messageboard.po to English US
+# Copyright (C) YEAR ORGANIZATION.
+# Stephan Richter <stephan.richter at tufts.edu>, 2003.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"POT-Creation-Date: Mon Dec 15 11:10:14 2003\n"
+"PO-Revision-Date: 2003-12-15 19:58-0500\n"
+"Last-Translator: Stephan Richter <stephan.richter at tufts.edu>\n"
+"Language-Team: German <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.4\n"
+"X-Generator: KBabel 1.3\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+msgid "add-button"
+msgstr "  Add  "
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr "Add Content"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:10
+msgid "Message Board Help"
+msgstr "Message Board Help"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:111
+msgid "Menu of objects to be added to Messages."
+msgstr "Menu of objects to be added to Messages."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:134
+#: src/zopeproducts/messageboard/browser/configure.zcml:142
+msgid "Change Message"
+msgstr "Change Message"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:150
+msgid "Preview"
+msgstr "Preview"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:158
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr "Message Details"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:18
+msgid "Menu of objects to be added to Message Boards."
+msgstr "Menu of objects to be added to Message Boards."
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:186
+msgid "Subscriptions"
+msgstr "Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:191
+msgid "Message Mail Subscriptions"
+msgstr "Message Mail Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:205
+msgid "Image"
+msgstr "Image"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:211
+msgid "File"
+msgstr "File"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:22
+#: src/zopeproducts/messageboard/browser/configure.zcml:115
+msgid "Add"
+msgstr "Add"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:34
+msgid "Add Message Board"
+msgstr "Add Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:44
+#: src/zopeproducts/messageboard/browser/configure.zcml:134
+msgid "Edit"
+msgstr "Edit"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:44
+#: src/zopeproducts/messageboard/browser/configure.zcml:52
+msgid "Change Message Board"
+msgstr "Change Message Board"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:60
+#: src/zopeproducts/messageboard/browser/configure.zcml:166
+msgid "Thread"
+msgstr "Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:68
+#: src/zopeproducts/messageboard/browser/configure.zcml:174
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+#: src/zopeproducts/messageboard/browser/skin/board_overview.pt:5
+msgid "Discussion Thread"
+msgstr "Discussion Thread"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:76
+#: src/zopeproducts/messageboard/browser/configure.zcml:217
+msgid "Contents"
+msgstr "Contents"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:84
+msgid "Review Messages"
+msgstr "Review Messages"
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:92
+msgid "Publication Review"
+msgstr "Publication Review"
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr "Author"
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr "Date/Time"
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr "Parent"
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr "Body"
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr "Title"
+
+# 8/browser/messageboard_add.pt:19
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr "Workflow erstellen"
+
+# 8/browser/messageboard_add.pt:21
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+msgstr ""
+"Without the workflow you will not be able to review messages before they are "
+"published. Note that you can always modify the messageboard workflow later "
+"to make all transitions automatically."
+
+# 7/browser/subscriptions.pt:35
+# Default: "Refresh"
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+msgid "refresh-button"
+msgstr "refresh-button"
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+msgid "submit-button"
+msgstr "Abschicken"
+
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr "Schwebende Anzeigen"
+
+#: src/zopeproducts/messageboard/browser/skin/board_intro.pt:5
+msgid "Welcome to the Message Board"
+msgstr "Welcome to the Message Board"
+
+#: src/zopeproducts/messageboard/browser/skin/board_posts.pt:5
+msgid "Posts"
+msgstr "Posts"
+
+#: src/zopeproducts/messageboard/browser/skin/board_review.pt:20
+msgid "Publish"
+msgstr "Publish"
+
+#: src/zopeproducts/messageboard/browser/skin/board_review.pt:5
+msgid "Review Pending Posts"
+msgstr "Review Pending Posts"
+
+#: src/zopeproducts/messageboard/browser/skin/configure.zcml:62
+msgid "Reply to Message"
+msgstr "Reply to Message"
+
+#: src/zopeproducts/messageboard/browser/skin/dialog_macros.pt:29
+#: src/zopeproducts/messageboard/browser/skin/template.pt:28
+msgid "Zope 3 Message Board"
+msgstr "Zope 3 Message Board"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr "Current Subscriptions"
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+msgid "remove-button"
+msgstr "   Add  "
+
+# 7/browser/subscriptions.pt:25
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr "Enter new Users (separate by 'Return')"
+
+#: src/zopeproducts/messageboard/configure.zcml:109
+#: src/zopeproducts/messageboard/browser/configure.zcml:126
+#: src/zopeproducts/messageboard/browser/configure.zcml:199
+msgid "Message"
+msgstr "Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:11
+msgid "Users that actually use the Message Board."
+msgstr "Users that actually use the Message Board."
+
+#: src/zopeproducts/messageboard/configure.zcml:11
+msgid "Message Board User"
+msgstr "Message Board User"
+
+#: src/zopeproducts/messageboard/configure.zcml:16
+msgid "Message Board Editor"
+msgstr "Message Board Editor"
+
+#: src/zopeproducts/messageboard/configure.zcml:16
+msgid "The Editor can edit and delete Messages."
+msgstr "The Editor can edit and delete Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:21
+msgid "View the Message Board and all its content."
+msgstr "View the Message Board and all its content."
+
+#: src/zopeproducts/messageboard/configure.zcml:21
+msgid "View Message Board and Messages"
+msgstr "View Message Board and Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:30
+msgid "Add Message."
+msgstr "Add Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:30
+#: src/zopeproducts/messageboard/browser/skin/configure.zcml:53
+#: src/zopeproducts/messageboard/browser/configure.zcml:126
+msgid "Add Message"
+msgstr "Add Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:39
+msgid "Edit Messages."
+msgstr "Edit Messages."
+
+#: src/zopeproducts/messageboard/configure.zcml:39
+msgid "Edit Messages"
+msgstr "Edit Messages"
+
+#: src/zopeproducts/messageboard/configure.zcml:48
+msgid "Delete Message."
+msgstr "Delete Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:48
+msgid "Delete Message"
+msgstr "Delete Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:58
+msgid "Publish Message"
+msgstr "Publish Message"
+
+#: src/zopeproducts/messageboard/configure.zcml:58
+msgid "Publish Message."
+msgstr "Publish Message."
+
+#: src/zopeproducts/messageboard/configure.zcml:78
+#: src/zopeproducts/messageboard/browser/configure.zcml:34
+msgid "Message Board"
+msgstr "Message Board"
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr "Forbidden HTML Tags used."
+
+#: src/zopeproducts/messageboard/interfaces.py:112
+msgid "Allowed HTML Tags"
+msgstr "Allowed HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:113
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:119
+msgid "Forbidden HTML Tags"
+msgstr "Forbidden HTML Tags"
+
+#: src/zopeproducts/messageboard/interfaces.py:120
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "Description"
+msgstr "Description"
+
+#: src/zopeproducts/messageboard/interfaces.py:40
+msgid "A detailed description of the content of the board."
+msgstr "A detailed description of the content of the board."
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title/Subject"
+msgstr "Title/Subject"
+
+#: src/zopeproducts/messageboard/interfaces.py:50
+msgid "Title and/or subject of the message."
+msgstr "Title and/or subject of the message."
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "Message Body"
+msgstr "Message Body"
+
+#: src/zopeproducts/messageboard/interfaces.py:56
+msgid "This is the actual message. Type whatever!"
+msgstr "This is the actual message. Type whatever!"
+

Added: messageboard/trunk/step12/locales/messageboard.pot
===================================================================
--- messageboard/trunk/step12/locales/messageboard.pot	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/locales/messageboard.pot	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,319 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: Zope X3 Pre-M4\n"
+"POT-Creation-Date: Mon Dec 15 11:10:14 2003\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: Zope 3 Developers <zope3-dev at zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: ENCODING\n"
+"Generated-By: zope/app/translation_files/extract.py\n"
+
+#: src/zopeproducts/messageboard/browser/add.pt:65
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:37
+# Default: "Add"
+msgid "add-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/add.pt:8
+msgid "Add Content"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:10
+msgid "Message Board Help"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:111
+msgid "Menu of objects to be added to Messages."
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:134
+#: src/zopeproducts/messageboard/browser/configure.zcml:142
+msgid "Change Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:150
+msgid "Preview"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:158
+#: src/zopeproducts/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:18
+msgid "Menu of objects to be added to Message Boards."
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:186
+msgid "Subscriptions"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:191
+msgid "Message Mail Subscriptions"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:205
+msgid "Image"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:211
+msgid "File"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:22
+#: src/zopeproducts/messageboard/browser/configure.zcml:115
+msgid "Add"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:34
+msgid "Add Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:44
+#: src/zopeproducts/messageboard/browser/configure.zcml:134
+msgid "Edit"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:44
+#: src/zopeproducts/messageboard/browser/configure.zcml:52
+msgid "Change Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:60
+#: src/zopeproducts/messageboard/browser/configure.zcml:166
+msgid "Thread"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:68
+#: src/zopeproducts/messageboard/browser/configure.zcml:174
+#: src/zopeproducts/messageboard/browser/thread.pt:5
+#: src/zopeproducts/messageboard/browser/skin/board_overview.pt:5
+msgid "Discussion Thread"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:76
+#: src/zopeproducts/messageboard/browser/configure.zcml:217
+msgid "Contents"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:84
+msgid "Review Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/configure.zcml:92
+msgid "Publication Review"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:19
+msgid "Create Workflow"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:21
+msgid "Without the workflow you will not be able to review messages before they are published. Note that you can always modify the messageboard workflow later to make all transitions automatically."
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:30
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:35
+# Default: "Refresh"
+msgid "refresh-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/messageboard_add.pt:32
+# Default: "Submit"
+msgid "submit-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/skin/board_intro.pt:5
+msgid "Welcome to the Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/skin/board_posts.pt:5
+msgid "Posts"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/skin/board_review.pt:20
+msgid "Publish"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/skin/board_review.pt:5
+msgid "Review Pending Posts"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/skin/configure.zcml:62
+msgid "Reply to Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/skin/dialog_macros.pt:29
+#: src/zopeproducts/messageboard/browser/skin/template.pt:28
+msgid "Zope 3 Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:12
+msgid "Current Subscriptions"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:19
+# Default: "Remove"
+msgid "remove-button"
+msgstr ""
+
+#: src/zopeproducts/messageboard/browser/subscriptions.pt:25
+msgid "Enter new Users (separate by 'Return')"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:109
+#: src/zopeproducts/messageboard/browser/configure.zcml:126
+#: src/zopeproducts/messageboard/browser/configure.zcml:199
+msgid "Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:11
+msgid "Users that actually use the Message Board."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:11
+msgid "Message Board User"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:16
+msgid "Message Board Editor"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:16
+msgid "The Editor can edit and delete Messages."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:21
+msgid "View the Message Board and all its content."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:21
+msgid "View Message Board and Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:30
+msgid "Add Message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:30
+#: src/zopeproducts/messageboard/browser/skin/configure.zcml:53
+#: src/zopeproducts/messageboard/browser/configure.zcml:126
+msgid "Add Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:39
+msgid "Edit Messages."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:39
+msgid "Edit Messages"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:48
+msgid "Delete Message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:48
+msgid "Delete Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:58
+msgid "Publish Message"
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:58
+msgid "Publish Message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/configure.zcml:78
+#: src/zopeproducts/messageboard/browser/configure.zcml:34
+msgid "Message Board"
+msgstr ""
+
+#: src/zopeproducts/messageboard/fields.py:26
+msgid "Forbidden HTML Tags used."
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:112
+msgid "Allowed HTML Tags"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:113
+msgid ""
+"        Listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:119
+msgid "Forbidden HTML Tags"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:120
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:39
+msgid "Description"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:40
+msgid "A detailed description of the content of the board."
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:49
+msgid "Title/Subject"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:50
+msgid "Title and/or subject of the message."
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:55
+msgid "Message Body"
+msgstr ""
+
+#: src/zopeproducts/messageboard/interfaces.py:56
+msgid "This is the actual message. Type whatever!"
+msgstr ""
+

Added: messageboard/trunk/step12/message.py
===================================================================
--- messageboard/trunk/step12/message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,381 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Implementation
+
+An implementation of the Message using BTreeContainers as base.
+
+$Id: message.py,v 1.1 2003/06/07 11:24:48 srichter Exp $
+"""
+from zope.i18n import MessageIDFactory
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.container.btree import BTreeContainer
+from zope.app.container.interfaces import IObjectAddedEvent
+from zope.app.container.interfaces import IObjectRemovedEvent
+from zope.app.event.interfaces import IObjectModifiedEvent
+from zope.app.mail.interfaces import IMailDelivery
+from zope.app.size.interfaces import ISized
+
+from book.messageboard.interfaces import IMessage
+from book.messageboard.interfaces import IMessageContained, IMessageContainer
+from book.messageboard.interfaces import IMailSubscriptions
+from book.messageboard.interfaces import IPlainText
+
+_ = MessageIDFactory('messageboard')
+
+
+class Message(BTreeContainer):
+    """A simple implementation of a message.
+
+    Make sure that the ``Message`` implements the ``IMessage`` interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMessage, Message)
+    True
+
+    Here is an example of changing the title and description of the message:
+
+    >>> message = Message()
+    >>> message.title
+    u''
+    >>> message.body
+    u''
+    >>> message.title = u'Message Title'
+    >>> message.body = u'Message Body'
+    >>> message.title
+    u'Message Title'
+    >>> message.body
+    u'Message Body'
+    """
+    implements(IMessage, IMessageContained, IMessageContainer)
+
+    # See book.messageboard.interfaces.IMessage
+    title = u''
+
+    # See book.messageboard.interfaces.IMessage
+    body = u''
+
+  
+class MessageSized(object):
+
+    implements(ISized)
+    __used_for__ = IMessage
+
+    def __init__(self, message):
+        self._message = message
+
+    def sizeForSorting(self):
+        """See ISized
+
+        Create the adapter first.
+
+        >>> size = MessageSized(Message())
+
+        Here are some examples of the expected output.
+
+        >>> size.sizeForSorting()
+        ('item', 0)
+        >>> size._message['msg1'] = Message()
+        >>> size.sizeForSorting()
+        ('item', 1)
+        >>> size._message['att1'] = object()
+        >>> size.sizeForSorting()
+        ('item', 2)
+        """
+        return ('item', len(self._message))
+
+    def sizeForDisplay(self):
+        """See ISized
+
+        Creater the adapter first.
+
+        >>> size = MessageSized(Message())
+
+        Here are some examples of the expected output.
+
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 0, atts: 0'
+        >>> size._message['msg1'] = Message()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'1 reply, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 1, atts: 0'
+        >>> size._message['att1'] = object()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'1 reply, 1 attachment'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 1, atts: 1'
+        >>> size._message['msg2'] =  Message()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, 1 attachment'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 2, atts: 1'
+        >>> size._message['att2'] = object()
+        >>> str = size.sizeForDisplay()
+        >>> str
+        u'${messages} replies, ${attachments} attachments'
+        >>> 'msgs: %(messages)s, atts: %(attachments)s' %str.mapping
+        'msgs: 2, atts: 2'
+        """
+        messages = 0
+        for obj in self._message.values():
+            if IMessage.providedBy(obj):
+                messages += 1
+
+        attachments = len(self._message)-messages
+
+        if messages == 1 and attachments == 1: 
+            size = _('1 reply, 1 attachment')
+        elif messages == 1 and attachments != 1:
+            size = _('1 reply, ${attachments} attachments')
+        elif messages != 1 and attachments == 1:
+            size = _('${messages} replies, 1 attachment')
+        else: 
+            size = _('${messages} replies, ${attachments} attachments')
+  
+        size.mapping = {'messages': `messages`, 'attachments': `attachments`}
+
+        return size
+
+
+SubscriberKey='http://www.zope.org/messageboard#1.0/MailSubscriptions/emails'
+
+
+class MailSubscriptions:
+    """Message Mail Subscriptions.
+
+    Verify the interface implementation
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMailSubscriptions, MailSubscriptions)
+    True
+
+    Create asubscription instance of a message
+
+    >>> msg = Message()
+    >>> sub = MailSubscriptions(msg)
+
+    Verify that we have initially no subscriptions and then add some.
+
+    >>> sub.getSubscriptions()
+    ()
+    >>> sub.addSubscriptions(('foo at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com',)
+    >>> sub.addSubscriptions(('blah at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com', 'blah at bar.com')
+    >>> sub.addSubscriptions(('doh at bar.com',))
+    >>> sub.getSubscriptions()
+    ('foo at bar.com', 'blah at bar.com', 'doh at bar.com')
+
+    Now let's also check that we can remove entries.
+
+    >>> sub.removeSubscriptions(('foo at bar.com',))
+    >>> sub.getSubscriptions()
+    ('blah at bar.com', 'doh at bar.com')
+
+    When we construct a new mail subscription adapter instance, the values
+    should still be there.
+
+    >>> sub1 = MailSubscriptions(msg)
+    >>> sub1.getSubscriptions()
+    ('blah at bar.com', 'doh at bar.com')
+    """
+    implements(IMailSubscriptions)
+    __used_for__ = IMessage
+
+    def __init__(self, context):
+        self.context = context
+        self._annotations = IAnnotations(context)
+        if not self._annotations.get(SubscriberKey):
+            self._annotations[SubscriberKey] = ()
+
+    def getSubscriptions(self):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        return self._annotations[SubscriberKey]
+        
+    def addSubscriptions(self, emails):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        subscribers = list(self._annotations[SubscriberKey])
+        for email in emails:
+            if email not in subscribers:
+                subscribers.append(email.strip())
+        self._annotations[SubscriberKey] = tuple(subscribers)
+                
+    def removeSubscriptions(self, emails):
+        "See book.messageboard.interfaces.IMailSubscriptions"
+        subscribers = list(self._annotations[SubscriberKey])
+        for email in emails:
+            if email in subscribers:
+                subscribers.remove(email)
+        self._annotations[SubscriberKey] = tuple(subscribers)
+
+
+class MessageMailer:
+    """Class to handle all outgoing mail."""
+  
+    def __call__(self, event):
+        r"""Called by the event system.
+
+        Here is a demonstration on how the notification process and mail
+        sending works.
+
+        Before we can test this method, we have to create a mail delivery
+        object for testing.
+
+        >>> mail_result = [] 
+
+        >>> from zope.interface import implements
+        >>> from zope.app.mail.interfaces import IMailDelivery
+        
+        >>> class MailDeliveryStub(object):
+        ...     implements(IMailDelivery)
+        ... 
+        ...     def send(self, fromaddr, toaddrs, message):
+        ...         mail_result.append((fromaddr, toaddrs, message))
+
+        >>> from zope.app.tests import ztapi
+        >>> ztapi.provideUtility(IMailDelivery, MailDeliveryStub(),
+        ...                      name='msgboard-delivery')
+
+        Create a message.
+
+        >>> from zope.interface import directlyProvides
+        >>> from zope.app.traversing.interfaces import IContainmentRoot
+
+        >>> msg = Message()
+        >>> directlyProvides(msg, IContainmentRoot)
+        >>> msg.__name__ = 'msg'
+        >>> msg.__parent__ = None
+        >>> msg.title = 'Hello'
+        >>> msg.body = 'Hello World!'
+
+        Add a subscription to message.
+
+        >>> msg_sub = MailSubscriptions(msg)
+        >>> msg_sub.context.__annotations__[SubscriberKey] = ('foo at bar.com',)
+
+        Now, create an event and send it to the message mailer object.
+
+        >>> from zope.app.event.objectevent import ObjectModifiedEvent
+        >>> event = ObjectModifiedEvent(msg)
+        >>> mailer(event)
+
+        >>> from pprint import pprint
+        >>> pprint(mail_result)
+        [('mailer at messageboard.org',
+          ('foo at bar.com',),
+          'Subject: Modified: msg\n\n\nHello World!')]
+        """
+        if IMessage.providedBy(event.object):
+            if IObjectAddedEvent.providedBy(event):
+                self.handleAdded(event.object)
+            elif IObjectModifiedEvent.providedBy(event):
+                self.handleModified(event.object)
+            elif IObjectRemovedEvent.providedBy(event):
+                self.handleRemoved(event.object)
+  
+    def handleAdded(self, object):
+        subject = 'Added: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = object.body
+        self.mail(emails, subject, body)        
+  
+    def handleModified(self, object):
+        subject = 'Modified: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = object.body
+        self.mail(emails, subject, body)
+  
+    def handleRemoved(self, object):
+        subject = 'Removed: '+zapi.getName(object)
+        emails = self.getAllSubscribers(object)
+        body = subject
+        self.mail(emails, subject, body)
+  
+    def getAllSubscribers(self, object):
+        """Retrieves all email subscribers.
+
+        Here a small demonstration of retrieving all subscribers.
+
+        >>> from zope.interface import directlyProvides
+        >>> from zope.app.traversing.interfaces import IContainmentRoot
+
+        Create a parent message as it would be located in the message
+        board. Also add a subscriber to the message.
+
+        >>> msg1 = Message()
+        >>> directlyProvides(msg1, IContainmentRoot)
+        >>> msg1.__name__ = 'msg1'
+        >>> msg1.__parent__ = None
+        >>> msg1_sub = MailSubscriptions(msg1)
+        >>> msg1_sub.context.__annotations__[SubscriberKey] = ('foo at bar.com',)
+
+        Create a reply to the first message and also give it a subscriber.
+       
+        >>> msg2 = Message()
+        >>> msg2_sub = MailSubscriptions(msg2)
+        >>> msg2_sub.context.__annotations__[SubscriberKey] = ('blah at bar.com',)
+        >>> msg1['msg2'] = msg2
+
+        When asking for all subscriptions of message 2, we should get the
+        subscriber from message 1 as well.
+
+        >>> mailer.getAllSubscribers(msg2)
+        ('blah at bar.com', 'foo at bar.com')
+        """
+        emails = ()
+        msg = object
+        while IMessage.providedBy(msg):
+            emails += tuple(IMailSubscriptions(msg).getSubscriptions())
+            msg = zapi.getParent(msg)
+        return emails
+  
+    def mail(self, toaddrs, subject, body):
+        """Mail out the Message Board change message."""
+        if not toaddrs:
+            return
+        msg = 'Subject: %s\n\n\n%s' %(subject, body)
+        mail_utility = zapi.getUtility(IMailDelivery, 'msgboard-delivery')
+        mail_utility.send('mailer at messageboard.org' , toaddrs, msg)
+  
+mailer = MessageMailer()
+
+
+class PlainText:
+
+    implements(IPlainText)
+
+    def __init__(self, context):
+        self.context = context
+
+    def getText(self):
+        return 'Title: %s\n\n%s' %(self.context.title, 
+                                   self.context.body)
+
+    def setText(self, text):
+        if text.startswith('Title: '):
+            title, text = text.split('\n', 1)
+            self.context.title = title[7:]
+
+        self.context.body = text.strip()

Added: messageboard/trunk/step12/messageboard.py
===================================================================
--- messageboard/trunk/step12/messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,62 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Implementation
+
+An implementation of the Message Board using BTreeContainers as base.
+
+$Id$
+"""
+from zope.interface import implements
+from zope.app.container.btree import BTreeContainer
+
+from book.messageboard.interfaces import IMessageBoard
+from book.messageboard.interfaces import IPlainText
+
+class MessageBoard(BTreeContainer):
+    """A very simple implementation of a message board using B-Tree Containers
+
+    Make sure that the ``MessageBoard`` implements the ``IMessageBoard``
+    interface:
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(IMessageBoard, MessageBoard)
+    True
+    
+    Here is an example of changing the description of the board:
+
+    >>> board = MessageBoard()
+    >>> board.description
+    u''
+    >>> board.description = u'Message Board Description'
+    >>> board.description
+    u'Message Board Description'
+    """
+    implements(IMessageBoard)
+
+    # See book.messageboard.interfaces.IMessageBoard
+    description = u''
+
+
+class PlainText:
+
+    implements(IPlainText)
+
+    def __init__(self, context):
+        self.context = context
+
+    def getText(self):
+        return self.context.description
+
+    def setText(self, text):
+        self.context.description = unicode(text)

Added: messageboard/trunk/step12/security.zcml
===================================================================
--- messageboard/trunk/step12/security.zcml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/security.zcml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,54 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="messageboard">
+
+  <role
+      id="book.messageboard.User"
+      title="Message Board User"
+      description="Users that actually use the Message Board."
+      />
+  <grant
+      permission="book.messageboard.View"
+      role="book.messageboard.User"
+      />
+  <grant
+      permission="book.messageboard.Add"
+      role="book.messageboard.User"
+      />
+
+  <role
+      id="book.messageboard.Editor"
+      title="Message Board Editor"
+      description="The Editor can edit and delete Messages."
+      />
+  <grant
+      permission="book.messageboard.Edit"
+      role="book.messageboard.Editor"
+      />
+  <grant
+      permission="book.messageboard.Delete"
+      role="book.messageboard.Editor"
+      />
+  <grant
+      permission="book.messageboard.PublishContent"
+      role="book.messageboard.Editor"/>
+
+
+  <grant
+      permission="book.messageboard.View"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Add"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Edit"
+      role="zope.Manager"
+      />
+  <grant
+      permission="book.messageboard.Delete"
+      role="zope.Manager"
+      /> 
+
+</configure>

Added: messageboard/trunk/step12/tests/__init__.py
===================================================================
--- messageboard/trunk/step12/tests/__init__.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/tests/__init__.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step12/tests/test_fields.py
===================================================================
--- messageboard/trunk/step12/tests/test_fields.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/tests/test_fields.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_fields.py,v 1.1 2003/06/10 14:40:45 srichter Exp $
+"""
+import unittest
+from zope.schema.tests.test_strfield import TextTest
+
+from book.messageboard.fields import HTML, ForbiddenTags
+
+class HTMLTest(TextTest):
+
+    _Field_Factory = HTML
+
+    def test_AllowedTagsHTMLValidate(self):
+        html = self._Field_Factory(allowed_tags=('h1','pre'))
+        html.validate(u'<h1>Blah</h1>') 
+        html.validate(u'<pre>Blah</pre>') 
+        html.validate(u'<h1><pre>Blah</pre></h1>') 
+        html.validate(u'<h1 style="..."><pre>Blah</pre></h1>') 
+        html.validate(u'<h1 style="..."><pre f="">Blah</pre></h1>') 
+
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2>Foo</h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2><pre>Foo</pre></h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2 attr="blah">Foo</h2>')
+
+
+    def test_ForbiddenTagsHTMLValidate(self):
+        html = self._Field_Factory(forbidden_tags=('h2','pre'))
+        html.validate(u'<h1>Blah</h1>') 
+        html.validate(u'<h1 style="...">Blah</h1>') 
+        html.validate(u'<h1 style="..."><div>Blah</div></h1>') 
+        html.validate(u'<h1 style="..."><div f="">Blah</div></h1>') 
+
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2>Foo</h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2><div>Foo</div></h2>')
+        self.assertRaises(ForbiddenTags, html.validate,
+                          u'<h2 attr="blah">Foo</h2>')
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(HTMLTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step12/tests/test_filerepresentation.py
===================================================================
--- messageboard/trunk/step12/tests/test_filerepresentation.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/tests/test_filerepresentation.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,178 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""FTP Views for the MessageBoard and Message component
+
+$Id$
+"""
+import unittest
+from zope.interface.verify import verifyObject
+from zope.app import zapi
+from zope.app.tests import ztapi
+from zope.app.tests.placelesssetup import PlacelessSetup
+
+from book.messageboard.interfaces import \
+     IVirtualContentsFile, IPlainText, IMessage, IMessageBoard
+from book.messageboard.message import \
+     Message, PlainText as MessagePlainText
+from book.messageboard.messageboard import \
+     MessageBoard, PlainText as MessageBoardPlainText
+from book.messageboard.filerepresentation import VirtualContentsFile
+from book.messageboard.filerepresentation import ReadDirectory
+
+class VirtualContentsFileTestBase(PlacelessSetup):
+
+    def _makeFile(self):
+        raise NotImplemented
+
+    def _registerPlainTextAdapter(self):
+        raise NotImplemented
+
+    def setUp(self):
+        PlacelessSetup.setUp(self)
+        self._registerPlainTextAdapter()
+
+    def testContentType(self):
+        file = self._makeFile()
+        self.assertEqual(file.getContentType(), 'text/plain')
+        file.setContentType('text/html')
+        self.assertEqual(file.getContentType(), 'text/plain')
+        self.assertEqual(file.contentType, 'text/plain')
+
+    def testData(self):
+        file = self._makeFile()
+
+        file.setData('Foobar')
+        self.assert_(file.getData().find('Foobar') >= 0)
+        self.assert_(file.data.find('Foobar') >= 0)
+
+        file.edit('Blah', 'text/html')
+        self.assertEqual(file.contentType, 'text/plain')
+        self.assert_(file.data.find('Blah') >= 0)
+
+    def testInterface(self):
+        file = self._makeFile()
+        self.failUnless(IVirtualContentsFile.providedBy(file))
+        self.failUnless(verifyObject(IVirtualContentsFile, file))
+
+
+class MessageVirtualContentsFileTest(VirtualContentsFileTestBase,
+                                     unittest.TestCase):
+    
+    def _makeFile(self):
+        return VirtualContentsFile(Message())
+
+    def _registerPlainTextAdapter(self):
+        ztapi.provideAdapter(IMessage, IPlainText, MessagePlainText)
+
+    def testMessageSpecifics(self):
+        file = self._makeFile()
+        self.assertEqual(file.context.title, '')
+        self.assertEqual(file.context.body, '')
+        file.data = 'Title: Hello\n\nWorld'
+        self.assertEqual(file.context.title, 'Hello')
+        self.assertEqual(file.context.body, 'World')
+        file.data = 'World 2'
+        self.assertEqual(file.context.body, 'World 2')
+
+
+class MessageBoardVirtualContentsFileTest(
+      VirtualContentsFileTestBase, unittest.TestCase):
+    
+    def _makeFile(self):
+        return VirtualContentsFile(MessageBoard())
+
+    def _registerPlainTextAdapter(self):
+        ztapi.provideAdapter(IMessageBoard, IPlainText, 
+                             MessageBoardPlainText)
+
+    def testMessageBoardSpecifics(self):
+        file = self._makeFile()
+        self.assertEqual(file.context.description, '')
+        file.data = 'Title: Hello\n\nWorld'
+        self.assertEqual(file.context.description, 
+                         'Title: Hello\n\nWorld')
+        file.data = 'World 2'
+        self.assertEqual(file.context.description, 'World 2')
+
+
+class ReadDirectoryTestBase(PlacelessSetup):
+
+    def _makeDirectoryObject(self):
+        raise NotImplemented
+
+    def _makeTree(self):
+        base = self._makeDirectoryObject()
+        msg1 = Message()
+        msg1.title = 'Message 1'
+        msg1.description = 'This is Message 1.'
+        msg11 = Message()
+        msg11.title = 'Message 1-1'
+        msg11.description = 'This is Message 1-1.'
+        msg2 = Message()
+        msg2.title = 'Message 1'
+        msg2.description = 'This is Message 1.'
+        msg1['msg11'] = msg11
+        base['msg1'] = msg1
+        base['msg2'] = msg2
+        return ReadDirectory(base)
+
+    def testKeys(self):
+        tree = self._makeTree()
+        keys = list(tree.keys())
+        keys.sort()
+        self.assertEqual(keys, ['contents', 'msg1', 'msg2'])
+        keys = list(ReadDirectory(tree['msg1']).keys())
+        keys.sort()
+        self.assertEqual(keys, ['contents', 'msg11'])
+
+    def testGet(self):
+        tree = self._makeTree()
+        self.assertEqual(tree.get('msg1'), tree.context['msg1'])
+        self.assertEqual(tree.get('msg3'), None)
+        default = object()
+        self.assertEqual(tree.get('msg3', default), default)
+        self.assertEqual(tree.get('contents').__class__, 
+                         VirtualContentsFile)
+
+    def testLen(self):
+        tree = self._makeTree()
+        self.assertEqual(len(tree), 3)
+        self.assertEqual(len(ReadDirectory(tree['msg1'])), 2)
+        self.assertEqual(len(ReadDirectory(tree['msg2'])), 1)
+        
+
+class MessageReadDirectoryTest(ReadDirectoryTestBase, 
+                               unittest.TestCase):
+
+    def _makeDirectoryObject(self):
+        return Message()
+
+
+class MessageBoardReadDirectoryTest(ReadDirectoryTestBase, 
+                                    unittest.TestCase):
+
+    def _makeDirectoryObject(self):
+        return MessageBoard()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MessageVirtualContentsFileTest),
+        unittest.makeSuite(MessageBoardVirtualContentsFileTest),
+        unittest.makeSuite(MessageReadDirectoryTest),
+        unittest.makeSuite(MessageBoardReadDirectoryTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step12/tests/test_message.py
===================================================================
--- messageboard/trunk/step12/tests/test_message.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/tests/test_message.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_message.py,v 1.2 2003/08/20 17:07:46 srichter Exp $
+"""
+import unittest
+from zope.interface import classImplements 
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.annotation.interfaces import IAnnotations
+from zope.app.annotation.interfaces import IAttributeAnnotatable
+from zope.app.annotation.attribute import AttributeAnnotations
+from zope.app.container.tests.test_icontainer import TestSampleContainer
+from zope.app.location.traversing import LocationPhysicallyLocatable
+from zope.app.location.interfaces import ILocation
+from zope.app.tests import placelesssetup
+from zope.app.tests import ztapi
+from zope.app.traversing.interfaces import IPhysicallyLocatable
+
+from book.messageboard.interfaces import IMailSubscriptions
+from book.messageboard.interfaces import IMessage
+from book.messageboard.message import MailSubscriptions
+from book.messageboard.message import Message
+
+
+class Test(TestSampleContainer):
+
+    def makeTestObject(self):
+        return Message()
+
+
+def setUp():
+    placelesssetup.setUp()
+    classImplements(Message, IAttributeAnnotatable)
+    ztapi.provideAdapter(IAttributeAnnotatable, IAnnotations,
+                         AttributeAnnotations)
+    ztapi.provideAdapter(ILocation, IPhysicallyLocatable,
+                         LocationPhysicallyLocatable)
+    ztapi.provideAdapter(IMessage, IMailSubscriptions, MailSubscriptions)
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite('book.messageboard.message',
+                     setUp=setUp, tearDown=placelesssetup.tearDown),
+        unittest.makeSuite(Test),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step12/tests/test_messageboard.py
===================================================================
--- messageboard/trunk/step12/tests/test_messageboard.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/tests/test_messageboard.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""Message Board Tests
+
+$Id: test_messageboard.py,v 1.2 2003/08/20 17:07:46 srichter Exp $
+"""
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.container.tests.test_icontainer import TestSampleContainer
+
+from book.messageboard.messageboard import MessageBoard
+
+
+class Test(TestSampleContainer):
+
+    def makeTestObject(self):
+        return MessageBoard()
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite('book.messageboard.messageboard'),
+        unittest.makeSuite(Test),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step12/tests/test_xmlrpc.py
===================================================================
--- messageboard/trunk/step12/tests/test_xmlrpc.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/tests/test_xmlrpc.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,101 @@
+##############################################################################
+#
+# Copyright (c) 2003, 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""XML-RPC Representation Tests
+
+$Id$
+"""
+import unittest
+
+from zope.publisher.xmlrpc import TestRequest
+
+from zope.app.tests.placelesssetup import PlacelessSetup
+from zope.app import zapi
+
+from book.messageboard.message import Message
+from book.messageboard.messageboard import MessageBoard
+from book.messageboard.xmlrpc import MessageBoardMethods, MessageMethods
+
+class MessageContainerTest(PlacelessSetup):
+
+    def _makeMethodObject(self):
+        return NotImplemented
+
+    def _makeTree(self):
+        methods = self._makeMethodObject()
+        msg1 = Message()
+        msg1.title = 'Message 1'
+        msg1.description = 'This is Message 1.'
+        msg2 = Message()
+        msg2.title = 'Message 1'
+        msg2.description = 'This is Message 1.'
+        methods.context['msg1'] = msg1
+        methods.context['msg2'] = msg2
+        return methods
+
+    def test_getMessageNames(self):
+        methods = self._makeTree()
+        self.assert_(isinstance(methods.getMessageNames(), list))
+        self.assertEqual(list(methods.context.keys()),
+                         methods.getMessageNames())
+
+    def test_addMessage(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.addMessage('msg3', 'M3', 'MB3'), 'msg3')
+        self.assertEqual(methods.context['msg3'].title, 'M3')
+        self.assertEqual(methods.context['msg3'].body, 'MB3')
+
+    def test_deleteMessage(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.deleteMessage('msg2'), True)
+        self.assertEqual(list(methods.context.keys()), ['msg1'])
+
+
+class MessageBoardMethodsTest(MessageContainerTest, unittest.TestCase):
+
+    def _makeMethodObject(self):
+        return MessageBoardMethods(MessageBoard(), TestRequest())
+
+    def test_description(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.getDescription(), '')
+        self.assertEqual(methods.setDescription('Board 1') , True)
+        self.assertEqual(methods.getDescription(), 'Board 1')
+
+
+class MessageMethodsTest(MessageContainerTest, unittest.TestCase):
+
+    def _makeMethodObject(self):
+        return MessageMethods(Message(), TestRequest())
+
+    def test_title(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.getTitle(), '')
+        self.assertEqual(methods.setTitle('Message 1') , True)
+        self.assertEqual(methods.getTitle(), 'Message 1')
+
+    def test_body(self):
+        methods = self._makeTree()
+        self.assertEqual(methods.getBody(), '')
+        self.assertEqual(methods.setBody('Body 1') , True)
+        self.assertEqual(methods.getBody(), 'Body 1')
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(MessageBoardMethodsTest),
+        unittest.makeSuite(MessageMethodsTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: messageboard/trunk/step12/workflow.xml
===================================================================
--- messageboard/trunk/step12/workflow.xml	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/workflow.xml	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<workflow type="StatefulWorkflow" title="Message Publication Review">
+  <schema name=""/>
+  <states>
+    <state name="INITIAL" title="initial" />
+    <state name="private" title="Private" />
+    <state name="pending" title="Pending Publication" />
+    <state name="published" title="Public" />
+  </states>
+  <transitions>
+     
+    <transition 
+        sourceState="published"
+        destinationState="private"
+        name="published_private"
+        title="Unpublish Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="private"
+        destinationState="pending"
+        name="private_pending"
+        title="Submit Message"
+        permission="book.messageboard.Edit"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="INITIAL" 
+        destinationState="private"
+        name="initial_private"
+        title="Make Private"
+        triggerMode="Automatic" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="published"
+        name="pending_published"
+        title="Publish Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="private"
+        name="pending_private"
+        title="Retract Message"
+        permission="book.messageboard.Edit"
+        triggerMode="Manual" />
+
+    <transition 
+        sourceState="pending"
+        destinationState="private"
+        name="pending_private_reject"
+        title="Reject Message"
+        permission="book.messageboard.PublishContent"
+        triggerMode="Manual" />
+    
+  </transitions>
+  
+</workflow>

Added: messageboard/trunk/step12/xmlrpc.py
===================================================================
--- messageboard/trunk/step12/xmlrpc.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/xmlrpc.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,74 @@
+##############################################################################
+#
+# Copyright (c) 2003, 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""XML-RPC Representations
+
+$Id$
+"""
+from zope.event import notify
+from zope.publisher.xmlrpc import MethodPublisher
+
+from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
+
+from book.messageboard.message import Message
+
+
+class MessageContainerMethods(MethodPublisher):
+
+  def getMessageNames(self):
+      """Get a list of all messages."""
+      return list(self.context.keys())
+
+  def addMessage(self, name, title, body):
+      """Add a message."""
+      msg = Message()
+      msg.title = title
+      msg.body = body
+      notify(ObjectCreatedEvent(msg))
+      self.context[name] = msg
+      return name
+
+  def deleteMessage(self, name):
+      """Delete a message. Return True, if successful."""
+      self.context.__delitem__(name)
+      return True 
+
+
+class MessageMethods(MessageContainerMethods):
+
+    def getTitle(self):
+        return self.context.title
+
+    def setTitle(self, title):
+        self.context.title = title
+        notify(ObjectModifiedEvent(self.context))
+        return True
+
+    def getBody(self):
+        return self.context.body
+
+    def setBody(self, body):
+        self.context.body = body
+        notify(ObjectModifiedEvent(self.context))
+        return True
+
+
+class MessageBoardMethods(MessageContainerMethods):
+
+    def getDescription(self):
+        return self.context.description
+
+    def setDescription(self, description):
+        self.context.description = description
+        notify(ObjectModifiedEvent(self.context))
+        return True

Added: messageboard/trunk/step12/xmlrpc_client.py
===================================================================
--- messageboard/trunk/step12/xmlrpc_client.py	2004-08-15 19:20:31 UTC (rev 27139)
+++ messageboard/trunk/step12/xmlrpc_client.py	2004-08-15 19:26:48 UTC (rev 27140)
@@ -0,0 +1,77 @@
+#!/usr/bin/env python2.3
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL 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.
+#
+##############################################################################
+"""XML-RPC Client
+
+$Id: xmlrpc_client.py,v 1.1 2003/07/18 11:55:34 srichter Exp $
+"""
+import httplib
+import xmlrpclib
+from code import InteractiveConsole
+from base64 import encodestring
+
+class BasicAuthTransport(xmlrpclib.Transport):
+    def __init__(self, username=None, password=None, verbose=0):
+        self.username=username
+        self.password=password
+        self.verbose=verbose
+
+    def request(self, host, handler, request_body, verbose=0):
+        # issue XML-RPC request
+
+        self.verbose = verbose
+
+        h = httplib.HTTP(host)
+        h.putrequest("POST", handler)
+
+        # required by HTTP/1.1
+        h.putheader("Host", host)
+
+        # required by XML-RPC
+        h.putheader("User-Agent", self.user_agent)
+        h.putheader("Content-Type", "text/xml")
+        h.putheader("Content-Length", str(len(request_body)))
+
+        # basic auth
+        if self.username is not None and self.password is not None:
+            h.putheader("AUTHORIZATION", "Basic %s" %
+                        encodestring("%s:%s" % (self.username, self.password)
+                                     ).replace("\012", ""))
+        h.endheaders()
+
+        if request_body:
+            h.send(request_body)
+
+        errcode, errmsg, headers = h.getreply()
+
+        if errcode != 200:
+            raise xmlrpclib.ProtocolError(host + handler,
+                                          errcode, errmsg, headers)
+
+        return self.parse_response(h.getfile())
+
+
+if __name__ == '__main__':
+    url = raw_input('Message Board URL [http://localhost:8080/board/]: ')
+    if url == '':
+        url = 'http://localhost:8080/board/'
+    username = raw_input('Username: ')
+    password = raw_input('Password: ')
+    
+    board = xmlrpclib.Server(
+        url, transport = BasicAuthTransport(username, password))
+
+    print "The message board is available as 'board'"
+    console = InteractiveConsole(locals={'board': board})
+    console.interact()


Property changes on: messageboard/trunk/step12/xmlrpc_client.py
___________________________________________________________________
Name: svn:executable
   + *



More information about the Zope3-Checkins mailing list