[Zope3-checkins] CVS: Products3/demo/messageboard/step12 - __init__.py:1.1 configure.zcml:1.1 fields.py:1.1 fs.py:1.1 interfaces.py:1.1 message.py:1.1 messageboard.py:1.1 workflow.xml:1.1 xmlrpc.py:1.1 xmlrpc_client.py:1.1

Stephan Richter srichter@cosmos.phy.tufts.edu
Mon, 21 Jul 2003 17:33:54 -0400


Update of /cvs-repository/Products3/demo/messageboard/step12
In directory cvs.zope.org:/tmp/cvs-serv1069

Added Files:
	__init__.py configure.zcml fields.py fs.py interfaces.py 
	message.py messageboard.py workflow.xml xmlrpc.py 
	xmlrpc_client.py 
Log Message:
Final step of the Message Board Demo product. This step corresponds to the
recipe: http://dev.zope.org/Zope3/DevelopingSkins


=== Added File Products3/demo/messageboard/step12/__init__.py ===


=== Added File Products3/demo/messageboard/step12/configure.zcml ===
<zopeConfigure 
   xmlns="http://namespaces.zope.org/zope"
   xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
   xmlns:event="http://namespaces.zope.org/event"
   xmlns:mail="http://namespaces.zope.org/mail"
   xmlns:translate="http://namespaces.zope.org/gts">
  <!-- Security definitions -->

  <role
      id="zopeproducts.messageboard.User"
      title="Message Board User"
      description="Users that actually use the Message Board."/>

  <role
      id="zopeproducts.messageboard.Editor"
      title="Message Board Editor"
      description="The Editor can edit and delete Messages."/>

  <permission
      id="zopeproducts.messageboard.View"
      title="View Message Board and Messages"
      description="View the Message Board and all its content."/>

  <grant
      permission="zopeproducts.messageboard.View"
      role="zopeproducts.messageboard.User"/>

  <permission
      id="zopeproducts.messageboard.Add"
      title="Add Message"
      description="Add Message."/>

  <grant
      permission="zopeproducts.messageboard.Add"
      role="zopeproducts.messageboard.User"/>

  <permission
      id="zopeproducts.messageboard.Edit"
      title="Edit Messages"
      description="Edit Messages."/>

  <grant
      permission="zopeproducts.messageboard.Edit"
      role="zopeproducts.messageboard.Editor"/>

  <permission
      id="zopeproducts.messageboard.Delete"
      title="Delete Message"
      description="Delete Message."/>

  <grant
      permission="zopeproducts.messageboard.Delete"
      role="zopeproducts.messageboard.Editor"/>


  <permission
      id="zopeproducts.messageboard.PublishContent"
      title="Publish Message"
      description="Publish Message."/>

  <grant
      permission="zopeproducts.messageboard.PublishContent"
      role="zopeproducts.messageboard.Editor"/>


  <!-- Content declarations -->

  <content class=".messageboard.MessageBoard">

    <implements
       interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />

    <implements
       interface="zope.app.interfaces.container.IContentContainer" />

    <factory
        id="MessageBoard"
        permission="zope.ManageContent"
        description="Message Board" />

    <require
        permission="zopeproducts.messageboard.View"
        interface=".interfaces.IMessageBoard"/>

    <require
        permission="zopeproducts.messageboard.Edit"
        set_schema=".interfaces.IMessageBoard"/>

  </content>

  <adapter
      factory=".messageboard.PlainText"
      provides=".interfaces.IPlainText"
      for=".interfaces.IMessageBoard" />

  <content class=".message.Message">

    <implements
       interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />

    <implements
       interface="zope.app.interfaces.container.IContentContainer" />

    <implements interface=
        "zope.app.interfaces.workflow.IProcessInstanceContainerAdaptable" />

    <factory
        id="Message"
        permission="zopeproducts.messageboard.Add"
        description="Message" />

    <require
        permission="zopeproducts.messageboard.View"
        interface=".interfaces.IMessage"/>

    <require
        permission="zopeproducts.messageboard.Edit"
        set_schema=".interfaces.IMessage"/>

  </content>

  <adapter
      factory=".message.MessageSized"
      provides="zope.app.interfaces.size.ISized"
      for=".interfaces.IMessage" />

  <adapter
      factory=".message.PlainText"
      provides=".interfaces.IPlainText"
      for=".interfaces.IMessage" />

  <!-- File System Representation -->

    <adapter
       for=".interfaces.IMessageBoard"
       provides="zope.app.interfaces.file.IReadDirectory"
       factory=".fs.ReadDirectory"
       permission="zope.View"/>

    <adapter
       for=".interfaces.IMessageBoard"
       provides="zope.app.interfaces.file.IDirectoryFactory"
       factory=".fs.MessageFactory"
       permission="zope.View"/>

    <adapter
       for=".interfaces.IMessage"
       provides="zope.app.interfaces.file.IReadDirectory"
       factory=".fs.ReadDirectory"
       permission="zope.View"/>

    <adapter
       for=".interfaces.IMessage"
       provides="zope.app.interfaces.file.IDirectoryFactory"
       factory=".fs.MessageFactory"
       permission="zope.View"/>

    <content class=".fs.VirtualContentsFile">

      <implements
         interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />

      <require
          permission="zope.View"
          interface="zope.app.interfaces.content.file.IReadFile" />

      <require
          permission="zope.ManageContent"
          interface="zope.app.interfaces.content.file.IWriteFile"
          set_schema="zope.app.interfaces.content.file.IReadFile" />

    </content>

  <!-- XML-RPC presentation -->
  <xmlrpc:view
      name="methods"
      for=".interfaces.IMessageBoard"
      permission="zopeproducts.messageboard.Edit" 
      allowed_methods="getMessageNames addMessage deleteMessage 
                       getDescription setDescription"
      factory=".xmlrpc.MessageBoardMethods" />

  <xmlrpc:view
      name="methods"
      for=".interfaces.IMessage"
      permission="zopeproducts.messageboard.Edit" 
      allowed_methods="getMessageNames addMessage deleteMessage 
                       getTitle setTitle getBody setBody"
      factory=".xmlrpc.MessageMethods" />

  <!-- Mail Subscriptions support -->
  <adapter
      factory=".message.MailSubscriptions"
      provides=".interfaces.IMailSubscriptions"
      for=".interfaces.IMessage" />

  <!-- Register Mailer and Mail Service -->

  <mail:smtpMailer id="msgboard-smtp" hostname="localhost" port="25" />

  <mail:queuedService permission="zope.SendMail"
                      queuePath="./src/zopeproducts/messageboard/mail-queue"
                      mailer="msgboard-smtp" />

  <!-- Register event listener for change mails -->
  <event:subscribe
      subscriber=".message.mailer"
      event_types="zope.app.interfaces.event.IObjectAddedEvent
                   zope.app.interfaces.event.IObjectModifiedEvent
                   zope.app.interfaces.event.IObjectRemovedEvent
                   zope.app.interfaces.event.IObjectMovedEvent" />

  <include package=".browser" />

  <translate:registerTranslations directory="locales" />

</zopeConfigure>


=== Added File Products3/demo/messageboard/step12/fields.py ===
##############################################################################
#
# 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: fields.py,v 1.1 2003/07/21 21:33:45 srichter Exp $
"""
import re

from zope.schema.interfaces import ValidationError
from zope.schema import Text

from zope.i18n import MessageIDFactory
_ = MessageIDFactory('messageboard')

ForbiddenTags = _('Forbidden HTML Tags used.')
forbidden_regex = r'</?(?:%s).*?/?>'
allowed_regex = r'</??(?!%s)[a-zA-Z0-9]*? ?(?:[a-z0-9]*?=?".*?")*/??>'

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 ValidationError(
                    ForbiddenTags, value, self.forbidden_tags)

        if self.allowed_tags:
            regex = allowed_regex %'|'.join(self.allowed_tags)
            if re.findall(regex, value):
                raise ValidationError(
                    ForbiddenTags, value, self.allowed_tags)



=== Added File Products3/demo/messageboard/step12/fs.py ===
##############################################################################
#
# 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: fs.py,v 1.1 2003/07/21 21:33:45 srichter Exp $
"""
from zope.component import getAdapter
from zope.interface import implements
from zope.app.interfaces.file import IReadDirectory, IDirectoryFactory
from zope.app.interfaces.index.text import ISearchableText
from zope.app.content.folder import ReadDirectory as ReadDirectoryBase
from zope.app.content.file import File
from zope.app.context import ContextWrapper

from interfaces import IVirtualContentsFile, IPlainText
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 '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 = getAdapter(self.context, IPlainText)
        return adapter.getText()

    def setData(self, data):
        '''See interface IFile'''
        adapter = getAdapter(self.context, IPlainText)
        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 File Products3/demo/messageboard/step12/interfaces.py ===
##############################################################################
#
# 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 Product 

$Id: interfaces.py,v 1.1 2003/07/21 21:33:45 srichter Exp $
"""
from zope.app.interfaces.container import IContainer
from zope.app.interfaces.content.file import IFile, IFileContent
from zope.schema.interfaces import IText

from zope.interface import Interface
from zope.interface import classImplements
from zope.schema import Text, TextLine, Container

from fields import HTML

from zope.i18n import MessageIDFactory
_ = MessageIDFactory('messageboard')


class IMessageBoard(IContainer):
    """The message board is the base object for our product. It can only
    contain IMessage objects."""

    description = Text(
        title=_("Description"),
        description=_("A detailed description of the content of the board."),
        default=u"",
        required=False)


class IMessage(IContainer):
    """A message object. It can contain its own responses."""

    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 IVirtualContentsFile(IFile, IFileContent):
    """Marker Interface to mark special Message and Message Board Contents
    files in FS representations.

    This interface is most likely to be implemented as an adapter to the
    'contents' file's container, so that the data can be directly stored where
    it belongs.
    """


class IMailSubscriptions(Interface):
    """This interface allows you to retrieve a list of E-mails for
    mailings. In our context """

    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.

    It is used to create the contents for the IVirtualContentsFile."""

    def getText():
        """Get a pure text representation of the object's content."""

    def setText(text):
        """Write the text to the object.

        This method might have some logic to read various attributes from the
        text. For example, if a text starts with 'Title: ', then the method
        could use the following text to extract the title for the object and
        so on.
        """


class IHTML(IText):
    """A text field that is geared towards handeling HTML input."""

    allowed_tags = Container(
        title=_("Allowed HTML Tags"),
        description=_("""\
        Listed tags can be used in the value of the field.
        """),
        required=False)

    forbidden_tags = Container(
        title=_("Forbidden HTML Tags"),
        description=_("""\
        Listed tags cannot be used in the value of the field.
        """),
        required=False)

# To avoid recursive imports:
classImplements(HTML, IHTML)


=== Added File Products3/demo/messageboard/step12/message.py ===
##############################################################################
#
# 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 Folders as base.

$Id: message.py,v 1.1 2003/07/21 21:33:45 srichter Exp $
"""
from zope.app.interfaces.annotation import IAnnotations
from zope.app.interfaces.event import ISubscriber
from zope.app.interfaces.event import IObjectAddedEvent, IObjectModifiedEvent
from zope.app.interfaces.event import IObjectRemovedEvent, IObjectMovedEvent
from zope.app.interfaces.size import ISized
from zopeproducts.messageboard.interfaces import IMessage, IPlainText
from zopeproducts.messageboard.interfaces import IMailSubscriptions

from zope.interface import implements
from zope.component import getAdapter, getService
from zope.app.container.btree import BTreeContainer
from zope.app.traversing import getParent, getName

from zope.i18n import MessageIDFactory
_ = MessageIDFactory('messageboard')

SubscriberKey = 'http://www.zope.org/messageboard#1.0/MailSubscriptions/emails'


class Message(BTreeContainer):
    __doc__ = IMessage.__doc__

    implements(IMessage)
    _title = u''
    _body = u''

    def getTitle(self):
        """Get the title of the board."""
        return self._title

    def setTitle(self, title):
        """Set the title of the board."""
        self._title = title

    # See zopeproducts.messageboard.interfaces.IMessage
    title = property(getTitle, setTitle)

    def getBody(self):
        """Get the body of the board."""
        return self._body

    def setBody(self, body):
        """Set the body of the board."""
        self._body = body
        
    # See zopeproducts.messageboard.interfaces.IMessage
    body = property(getBody, setBody)


class MessageSized:

    implements(ISized)

    def __init__(self, message):
        self._message = message

    def sizeForSorting(self):
        'See ISized'
        return ('item', len(self._message))

    def sizeForDisplay(self):
        'See ISized'
        messages = 0
        for obj in self._message.values():
            if IMessage.isImplementedBy(obj):
                messages += 1

        attach = len(self._message)-messages

        if messages == 1: size = '1 reply'
        else: size = '${messages} replies'

        if attach == 1: size += ', 1 attachment'
        else: size += ', ${attach} attachments'

        size = _(size)
        size.mapping = {'messages': `messages`, 'attach': `attach`}
        size.default = unicode(size)
        
        return size


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()


class MailSubscriptions:
    """An adapter for IMessage to provide an interface for collecting E-mails
    for sending out change notices."""

    implements(IMailSubscriptions)
    __used_for__ = IMessage

    def __init__(self, context):
        self.context = context
        self._annotations = getAdapter(context, IAnnotations)
        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."""

    implements(ISubscriber)

    def notify(self, event):
        """See zope.app.interfaces.event.ISubscriber"""
        if IMessage.isImplementedBy(event.object):
            if IObjectAddedEvent.isImplementedBy(event):
                self.handleAdded(event.object)
            elif IObjectModifiedEvent.isImplementedBy(event):
                self.handleModified(event.object)
            elif IObjectRemovedEvent.isImplementedBy(event):
                self.handleRemoved(event.object)

    def handleAdded(self, object):
        subject = 'Added: '+getName(object)
        emails = self.getAllSubscribers(object)
        body = object.body
        self.mail(emails, subject, body)        

    def handleModified(self, object):
        subject = 'Modified: '+getName(object)
        emails = self.getAllSubscribers(object)
        body = object.body
        self.mail(emails, subject, body)

    def handleRemoved(self, object):
        subject = 'Removed: '+getName(object)
        emails = self.getAllSubscribers(object)
        body = subject
        self.mail(emails, subject, body)

    def getAllSubscribers(self, object):
        """Retrieves all email subscribers for this message and all above."""
        emails = ()
        msg = object
        while IMessage.isImplementedBy(msg):
            emails += tuple(getAdapter(
                msg, IMailSubscriptions).getSubscriptions())
            msg = getParent(msg)
        return emails

    def mail(self, toaddrs, subject, body):
        """Mail out the Wiki change message."""
        if not toaddrs:
            return
        msg = 'Subject: %s\n\n\n%s' %(subject, body)
        mail_service = getService(None, 'Mail')
        mail_service.send('mailer@messageboard.org' , toaddrs, msg)


# Create a global mailer object.
mailer = MessageMailer()


=== Added File Products3/demo/messageboard/step12/messageboard.py ===
##############################################################################
#
# 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 Folders as base.

$Id: messageboard.py,v 1.1 2003/07/21 21:33:45 srichter Exp $
"""
from zope.interface import implements
from zope.app.container.btree import BTreeContainer
from zopeproducts.messageboard.interfaces import IMessageBoard, IPlainText

class MessageBoard(BTreeContainer):
    __doc__ = IMessageBoard.__doc__

    implements(IMessageBoard)

    _desc = u''

    def getDescription(self):
        """Get the description of the board."""
        return self._desc

    def setDescription(self, desc):
        """Set the description of the board."""
        self._desc = desc

    # See zopeproducts.messageboard.interfaces.IMessageBoard
    description = property(getDescription, setDescription)


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 File Products3/demo/messageboard/step12/workflow.xml ===
<?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="zopeproducts.messageboard.PublishContent"
        triggerMode="Manual" />

    <transition 
        sourceState="private"
        destinationState="pending"
        name="private_pending"
        title="Submit Message"
        permission="zopeproducts.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="zopeproducts.messageboard.PublishContent"
        triggerMode="Manual" />

    <transition 
        sourceState="pending"
        destinationState="private"
        name="pending_private"
        title="Retract Message"
        permission="zopeproducts.messageboard.Edit"
        triggerMode="Manual" />

    <transition 
        sourceState="pending"
        destinationState="private"
        name="pending_private_reject"
        title="Reject Message"
        permission="zopeproducts.messageboard.PublishContent"
        triggerMode="Manual" />
    
  </transitions>
  
</workflow>


=== Added File Products3/demo/messageboard/step12/xmlrpc.py ===
##############################################################################
#
# 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 Representations

$Id: xmlrpc.py,v 1.1 2003/07/21 21:33:45 srichter Exp $
"""
from zope.publisher.xmlrpc import XMLRPCView

from zope.app.event import publish
from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent

from zopeproducts.messageboard.message import Message


class MessageContainerMethods(XMLRPCView):

  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
      publish(self.context, ObjectCreatedEvent(msg))
      return self.context.setObject(name, msg)

  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
        publish(self.context, ObjectModifiedEvent(self.context))
        return True

    def getBody(self):
        return self.context.body

    def setBody(self, body):
        self.context.body = body
        publish(self.context, ObjectModifiedEvent(self.context))
        return True


class MessageBoardMethods(MessageContainerMethods):

    def getDescription(self):
        return self.context.description

    def setDescription(self, description):
        self.context.description = description
        publish(self.context, ObjectModifiedEvent(self.context))
        return True


=== Added File Products3/demo/messageboard/step12/xmlrpc_client.py ===
#!/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/21 21:33:45 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()