[Zope-CVS] SVN: messageboard/trunk/step08/ Updated the workflow chapter.

Stephan Richter srichter at cosmos.phy.tufts.edu
Sun May 23 13:43:11 EDT 2004


Log message for revision 24910:
Updated the workflow chapter.



-=-
Added: messageboard/trunk/step08/__init__.py
===================================================================
--- messageboard/trunk/step08/__init__.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/__init__.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step08/browser/__init__.py
===================================================================
--- messageboard/trunk/step08/browser/__init__.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/__init__.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step08/browser/configure.zcml
===================================================================
--- messageboard/trunk/step08/browser/configure.zcml	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/configure.zcml	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1,149 @@
+<configure
+    xmlns="http://namespaces.zope.org/browser"
+    xmlns:zope="http://namespaces.zope.org/zope">
+
+  <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"/>
+
+  <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" 
+      />
+
+  <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/step08/browser/details.pt
===================================================================
--- messageboard/trunk/step08/browser/details.pt	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/details.pt	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/browser/ftests/__init__.py
===================================================================
--- messageboard/trunk/step08/browser/ftests/__init__.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/ftests/__init__.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step08/browser/ftests/test_message.py
===================================================================
--- messageboard/trunk/step08/browser/ftests/test_message.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/ftests/test_message.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/browser/message.png
===================================================================
(Binary files differ)


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

Added: messageboard/trunk/step08/browser/message.py
===================================================================
--- messageboard/trunk/step08/browser/message.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/message.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/browser/messageboard.png
===================================================================
(Binary files differ)


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

Added: messageboard/trunk/step08/browser/messageboard.py
===================================================================
--- messageboard/trunk/step08/browser/messageboard.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/messageboard.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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.isImplementedBy(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 = zapi.queryAdapter(msg, IProcessInstanceContainer)
+    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/step08/browser/messageboard_add.pt
===================================================================
--- messageboard/trunk/step08/browser/messageboard_add.pt	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/messageboard_add.pt	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/browser/review.pt
===================================================================
--- messageboard/trunk/step08/browser/review.pt	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/review.pt	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/browser/subscriptions.pt
===================================================================
--- messageboard/trunk/step08/browser/subscriptions.pt	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/subscriptions.pt	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/browser/subthread.pt
===================================================================
--- messageboard/trunk/step08/browser/subthread.pt	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/subthread.pt	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/browser/tests/__init__.py
===================================================================
--- messageboard/trunk/step08/browser/tests/__init__.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/tests/__init__.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step08/browser/tests/test_widgets.py
===================================================================
--- messageboard/trunk/step08/browser/tests/test_widgets.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/tests/test_widgets.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/browser/thread.pt
===================================================================
--- messageboard/trunk/step08/browser/thread.pt	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/thread.pt	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/browser/thread.py
===================================================================
--- messageboard/trunk/step08/browser/thread.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/thread.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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.isImplementedBy(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/step08/browser/widgets.py
===================================================================
--- messageboard/trunk/step08/browser/widgets.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/browser/widgets.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/configure.zcml
===================================================================
--- messageboard/trunk/step08/configure.zcml	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/configure.zcml	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1,114 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    xmlns:event="http://namespaces.zope.org/event"
+    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.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" />
+
+  <mail:smtpMailer name="msgboard-smtp" hostname="localhost" port="25" />
+  
+  <mail:queuedDelivery 
+      name="msgboard-delivery"
+      permission="zope.SendMail"
+      queuePath="./mail-queue"
+      mailer="msgboard-smtp" />
+
+  <event:subscribe
+      subscriber=".message.mailer"
+      event_types="zope.app.event.interfaces.IObjectModifiedEvent
+                   zope.app.container.interfaces.IObjectAddedEvent
+                   zope.app.container.interfaces.IObjectRemovedEvent" />
+
+
+  <i18n:registerTranslations directory="locales" />
+
+  <include package=".browser" />
+
+</configure>

Added: messageboard/trunk/step08/fields.py
===================================================================
--- messageboard/trunk/step08/fields.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/fields.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/interfaces.py
===================================================================
--- messageboard/trunk/step08/interfaces.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/interfaces.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1,112 @@
+##############################################################################
+#
+# 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 IContainer
+from zope.app.file.interfaces import IFile
+
+from fields import HTML
+
+_ = MessageIDFactory('messageboard')
+
+
+class IMessage(IContainer):
+    """A message object. It can contain its own responses."""
+
+    def __setitem__(name, object):
+        """Add a IMessage 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)
+
+
+IMessage['__setitem__'].setTaggedValue('precondition',
+                                       ItemTypePrecondition(IMessage, IFile))
+IMessage.setTaggedValue('__parent__', Field(
+    constraint=ContainerTypesConstraint(IMessageBoard, IMessage)))
+
+
+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/step08/locales/de/LC_MESSAGES/messageboard.mo
===================================================================
(Binary files differ)


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

Added: messageboard/trunk/step08/locales/de/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step08/locales/de/LC_MESSAGES/messageboard.po	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/locales/de/LC_MESSAGES/messageboard.po	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1,269 @@
+# 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, 2004.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"POT-Creation-Date: Sun May 23 13:34:25 2004\n"
+"PO-Revision-Date: 2004-05-23 13:40-0400\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.9\n"
+
+#: src/book/messageboard/browser/configure.zcml:138
+msgid "Subscriptions"
+msgstr "Abonnements"
+
+#: src/book/messageboard/browser/configure.zcml:16
+msgid "A Message Board"
+msgstr "Ein Anzeigebrett"
+
+#: src/book/messageboard/browser/configure.zcml:24
+msgid "Change Message Board"
+msgstr "Anzeigebrett ändern"
+
+#: src/book/messageboard/browser/configure.zcml:24
+#: src/book/messageboard/browser/configure.zcml:82
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: src/book/messageboard/browser/configure.zcml:40
+#: src/book/messageboard/browser/configure.zcml:111
+msgid "Thread"
+msgstr "Unterhaltungsfaden"
+
+#: src/book/messageboard/browser/configure.zcml:5
+msgid "Add Message Board"
+msgstr "Anzeigebrett hinzufügen"
+
+#: src/book/messageboard/browser/configure.zcml:57
+msgid "Review Messages"
+msgstr "Anzeigen prüfen"
+
+#: src/book/messageboard/browser/configure.zcml:74
+msgid "A Message"
+msgstr "Eine Anzeige"
+
+#: src/book/messageboard/browser/configure.zcml:74
+msgid "Message"
+msgstr "Anzeige"
+
+#: src/book/messageboard/browser/configure.zcml:82
+msgid "Change Message"
+msgstr "Anzeige ändern"
+
+#: src/book/messageboard/browser/configure.zcml:99
+msgid "Preview"
+msgstr "Vorschau"
+
+#: src/book/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr "Autor"
+
+#: src/book/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr "Datum/Zeit"
+
+#: src/book/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr "Vorfahre"
+
+#: src/book/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr "Inhalt"
+
+#: src/book/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr "Anzeigedetails"
+
+#: src/book/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr "Titel"
+
+#: src/book/messageboard/browser/message.py:34
+msgid "unknown"
+msgstr "unbekannt"
+
+#: src/book/messageboard/browser/messageboard_add.pt:11
+msgid "Create Workflow"
+msgstr "Workflow erstellen"
+
+#: src/book/messageboard/browser/messageboard_add.pt:13
+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 alle Übergänge automatisch "
+"ausgeführt werden."
+
+#: src/book/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr "Schwebende Anzeigen"
+
+#: src/book/messageboard/browser/subscriptions.pt:16
+msgid "remove-button"
+msgstr "Entfernen"
+
+# 7/browser/subscriptions.pt:25
+#: src/book/messageboard/browser/subscriptions.pt:22
+msgid "Enter new Users (separate by 'Return')"
+msgstr "Neue Nutzer eingeben (durch 'Return' getrennt)"
+
+# 7/browser/subscriptions.pt:35
+# Default: "Refresh"
+#: src/book/messageboard/browser/subscriptions.pt:32
+msgid "refresh-button"
+msgstr "Zurücksetzen"
+
+#: src/book/messageboard/browser/subscriptions.pt:34
+msgid "add-button"
+msgstr "Hinzufügen"
+
+#: src/book/messageboard/browser/subscriptions.pt:8
+msgid "Current Subscriptions"
+msgstr "Derzeitige Abonnements"
+
+#: src/book/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr "Diskussions-Faden"
+
+#: src/book/messageboard/configure.zcml:13
+msgid "Add Message."
+msgstr "Nachricht hinzufügen."
+
+#: src/book/messageboard/configure.zcml:13
+#: src/book/messageboard/browser/configure.zcml:65
+msgid "Add Message"
+msgstr "Anzeige hinzufügen"
+
+#: src/book/messageboard/configure.zcml:18
+msgid "Edit Messages."
+msgstr "Anzeigen bearbeiten."
+
+#: src/book/messageboard/configure.zcml:18
+msgid "Edit Messages"
+msgstr "Anzeigen bearbeiten"
+
+#: src/book/messageboard/configure.zcml:23
+msgid "Delete Message."
+msgstr "Anzeigen löschen."
+
+#: src/book/messageboard/configure.zcml:23
+msgid "Delete Message"
+msgstr "Anzeigen löschen"
+
+#: src/book/messageboard/configure.zcml:29
+msgid "Publish Message"
+msgstr "Anzeige veröffentlichen"
+
+#: src/book/messageboard/configure.zcml:29
+msgid "Publish Message."
+msgstr "Anzeigen veröoffentlichen."
+
+#: src/book/messageboard/configure.zcml:46
+#: src/book/messageboard/browser/configure.zcml:16
+msgid "Message Board"
+msgstr "Anzeigenbrett"
+
+#: src/book/messageboard/configure.zcml:8
+msgid "View the Message Board and all its content."
+msgstr "Das Anzeigebrett und seine Inhalte anschauen."
+
+#: src/book/messageboard/configure.zcml:8
+msgid "View Message Board and Messages"
+msgstr "Anzeigebrett und Anzeigen ansehen"
+
+#: src/book/messageboard/fields.py:30
+msgid "Forbidden HTML Tags used."
+msgstr "Verbotene HTML Tags wurden verwendet."
+
+#: src/book/messageboard/interfaces.py:42
+msgid "Title/Subject"
+msgstr "Titel/Thema"
+
+#: src/book/messageboard/interfaces.py:43
+msgid "Title and/or subject of the message."
+msgstr "Titel und/oder Thema der Anzeige."
+
+#: src/book/messageboard/interfaces.py:48
+msgid "Message Body"
+msgstr "Text der Anzeige"
+
+#: src/book/messageboard/interfaces.py:49
+msgid "This is the actual message. Type whatever!"
+msgstr "Dies ist die Anzeige selbst. Schreib was immer Du willst!"
+
+#: src/book/messageboard/interfaces.py:69
+msgid "Description"
+msgstr "Beschreibung"
+
+#: src/book/messageboard/interfaces.py:70
+msgid "A detailed description of the content of the board."
+msgstr "Eine detailierte Beschreibung des Brett-Inhaltes."
+
+#: src/book/messageboard/interfaces.py:85
+msgid "Allowed HTML Tags"
+msgstr "Erlaubte HTML Tags"
+
+#: src/book/messageboard/interfaces.py:86
+msgid ""
+"        Only listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Aufgelistete Tags können im Inhalt dieses Feldes benutzt werden.\n"
+"        "
+
+#: src/book/messageboard/interfaces.py:92
+msgid "Forbidden HTML Tags"
+msgstr "Verbotene HTML Tags"
+
+#: src/book/messageboard/interfaces.py:93
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+"        Aufgelistete Tags können nicht im Inhalt dieses Feldes benutzt "
+"werden.\n"
+"        "
+
+#: src/book/messageboard/message.py:146
+msgid "1 reply, 1 attachment"
+msgstr "1 Antwort, 1 Anhang"
+
+#: src/book/messageboard/message.py:148
+msgid "1 reply, ${attachments} attachments"
+msgstr "1 Antwort; ${attachments} Anhänge"
+
+#: src/book/messageboard/message.py:150
+msgid "${messages} replies, 1 attachment"
+msgstr "${messages} Antworten; 1 Anhang"
+
+#: src/book/messageboard/message.py:152
+msgid "${messages} replies, ${attachments} attachments"
+msgstr "${messages} Antworten, ${attachments} Anhänge"
+
+#: src/book/messageboard/security.zcml:19
+msgid "Message Board Editor"
+msgstr "Anzeigebrett Editor"
+
+#: src/book/messageboard/security.zcml:19
+msgid "The Editor can edit and delete Messages."
+msgstr "Der Editor kann Anzeigen bearbeiten und löschen."
+
+#: src/book/messageboard/security.zcml:5
+msgid "Message Board User"
+msgstr "Anzeigenbrett Nutzer"
+
+#: src/book/messageboard/security.zcml:5
+msgid "Users that actually use the Message Board."
+msgstr "Nutzer die das Anzeigenbrett nutzen."
+

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


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

Added: messageboard/trunk/step08/locales/en/LC_MESSAGES/messageboard.po
===================================================================
--- messageboard/trunk/step08/locales/en/LC_MESSAGES/messageboard.po	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/locales/en/LC_MESSAGES/messageboard.po	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1,8 @@
+# This file contains no message ids because the messageboard's default
+# language is English
+msgid ""
+msgstr ""
+"Project-Id-Version: messageboard\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"

Added: messageboard/trunk/step08/locales/messageboard.pot
===================================================================
--- messageboard/trunk/step08/locales/messageboard.pot	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/locales/messageboard.pot	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1,264 @@
+##############################################################################
+#
+# 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: Development/Unknown\n"
+"POT-Creation-Date: Sun May 23 13:34:25 2004\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: zope/app/translation_files/extract.py\n"
+
+#: src/book/messageboard/browser/configure.zcml:138
+msgid "Subscriptions"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:16
+msgid "A Message Board"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:24
+msgid "Change Message Board"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:24
+#: src/book/messageboard/browser/configure.zcml:82
+msgid "Edit"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:40
+#: src/book/messageboard/browser/configure.zcml:111
+msgid "Thread"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:5
+msgid "Add Message Board"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:57
+msgid "Review Messages"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:74
+msgid "A Message"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:74
+msgid "Message"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:82
+msgid "Change Message"
+msgstr ""
+
+#: src/book/messageboard/browser/configure.zcml:99
+msgid "Preview"
+msgstr ""
+
+#: src/book/messageboard/browser/details.pt:13
+msgid "Author"
+msgstr ""
+
+#: src/book/messageboard/browser/details.pt:18
+msgid "Date/Time"
+msgstr ""
+
+#: src/book/messageboard/browser/details.pt:23
+msgid "Parent"
+msgstr ""
+
+#: src/book/messageboard/browser/details.pt:32
+msgid "Body"
+msgstr ""
+
+#: src/book/messageboard/browser/details.pt:5
+msgid "Message Details"
+msgstr ""
+
+#: src/book/messageboard/browser/details.pt:8
+msgid "Title"
+msgstr ""
+
+#: src/book/messageboard/browser/message.py:34
+msgid "unknown"
+msgstr ""
+
+#: src/book/messageboard/browser/messageboard_add.pt:11
+msgid "Create Workflow"
+msgstr ""
+
+#: src/book/messageboard/browser/messageboard_add.pt:13
+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/book/messageboard/browser/review.pt:5
+msgid "Pending Messages"
+msgstr ""
+
+#: src/book/messageboard/browser/subscriptions.pt:16
+# Default: "Remove"
+msgid "remove-button"
+msgstr ""
+
+#: src/book/messageboard/browser/subscriptions.pt:22
+msgid "Enter new Users (separate by 'Return')"
+msgstr ""
+
+#: src/book/messageboard/browser/subscriptions.pt:32
+# Default: "Refresh"
+msgid "refresh-button"
+msgstr ""
+
+#: src/book/messageboard/browser/subscriptions.pt:34
+# Default: "Add"
+msgid "add-button"
+msgstr ""
+
+#: src/book/messageboard/browser/subscriptions.pt:8
+msgid "Current Subscriptions"
+msgstr ""
+
+#: src/book/messageboard/browser/thread.pt:5
+msgid "Discussion Thread"
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:13
+msgid "Add Message."
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:13
+#: src/book/messageboard/browser/configure.zcml:65
+msgid "Add Message"
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:18
+msgid "Edit Messages."
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:18
+msgid "Edit Messages"
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:23
+msgid "Delete Message."
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:23
+msgid "Delete Message"
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:29
+msgid "Publish Message"
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:29
+msgid "Publish Message."
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:46
+#: src/book/messageboard/browser/configure.zcml:16
+msgid "Message Board"
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:8
+msgid "View the Message Board and all its content."
+msgstr ""
+
+#: src/book/messageboard/configure.zcml:8
+msgid "View Message Board and Messages"
+msgstr ""
+
+#: src/book/messageboard/fields.py:30
+msgid "Forbidden HTML Tags used."
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:42
+msgid "Title/Subject"
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:43
+msgid "Title and/or subject of the message."
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:48
+msgid "Message Body"
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:49
+msgid "This is the actual message. Type whatever!"
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:69
+msgid "Description"
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:70
+msgid "A detailed description of the content of the board."
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:85
+msgid "Allowed HTML Tags"
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:86
+msgid ""
+"        Only listed tags can be used in the value of the field.\n"
+"        "
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:92
+msgid "Forbidden HTML Tags"
+msgstr ""
+
+#: src/book/messageboard/interfaces.py:93
+msgid ""
+"        Listed tags cannot be used in the value of the field.\n"
+"        "
+msgstr ""
+
+#: src/book/messageboard/message.py:146
+msgid "1 reply, 1 attachment"
+msgstr ""
+
+#: src/book/messageboard/message.py:148
+msgid "1 reply, ${attachments} attachments"
+msgstr ""
+
+#: src/book/messageboard/message.py:150
+msgid "${messages} replies, 1 attachment"
+msgstr ""
+
+#: src/book/messageboard/message.py:152
+msgid "${messages} replies, ${attachments} attachments"
+msgstr ""
+
+#: src/book/messageboard/security.zcml:19
+msgid "Message Board Editor"
+msgstr ""
+
+#: src/book/messageboard/security.zcml:19
+msgid "The Editor can edit and delete Messages."
+msgstr ""
+
+#: src/book/messageboard/security.zcml:5
+msgid "Message Board User"
+msgstr ""
+
+#: src/book/messageboard/security.zcml:5
+msgid "Users that actually use the Message Board."
+msgstr ""
+

Added: messageboard/trunk/step08/message.py
===================================================================
--- messageboard/trunk/step08/message.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/message.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1,371 @@
+##############################################################################
+#
+# 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.event.interfaces import ISubscriber
+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 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)
+
+    # 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 zopeproducts.messageboard.interfaces.IMailSubscriptions"
+        return self._annotations[SubscriberKey]
+        
+    def addSubscriptions(self, emails):
+        "See zopeproducts.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 zopeproducts.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.
+
+    Verify the interface implementation
+
+    >>> from zope.interface.verify import verifyClass
+    >>> verifyClass(ISubscriber, MessageMailer)
+    True
+    """
+  
+    implements(ISubscriber)
+  
+    def notify(self, event):
+        r"""See zope.app.event.interfaces.ISubscriber
+
+        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.notify(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(None, IMailDelivery,
+                                       'msgboard-delivery')
+        mail_utility.send('mailer at messageboard.org' , toaddrs, msg)
+  
+mailer = MessageMailer()

Added: messageboard/trunk/step08/messageboard.py
===================================================================
--- messageboard/trunk/step08/messageboard.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/messageboard.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/security.zcml
===================================================================
--- messageboard/trunk/step08/security.zcml	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/security.zcml	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/tests/__init__.py
===================================================================
--- messageboard/trunk/step08/tests/__init__.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/tests/__init__.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -0,0 +1 @@
+# Make it a Python package

Added: messageboard/trunk/step08/tests/test_fields.py
===================================================================
--- messageboard/trunk/step08/tests/test_fields.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/tests/test_fields.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/tests/test_message.py
===================================================================
--- messageboard/trunk/step08/tests/test_message.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/tests/test_message.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/tests/test_messageboard.py
===================================================================
--- messageboard/trunk/step08/tests/test_messageboard.py	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/tests/test_messageboard.py	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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/step08/workflow.xml
===================================================================
--- messageboard/trunk/step08/workflow.xml	2004-05-23 17:24:36 UTC (rev 24909)
+++ messageboard/trunk/step08/workflow.xml	2004-05-23 17:43:10 UTC (rev 24910)
@@ -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>




More information about the Zope-CVS mailing list