[Zope3-checkins] CVS: Products3/demo/messageboard/step7 - __init__.py:1.1 configure.zcml:1.1 fields.py:1.1 interfaces.py:1.1 message.py:1.1 messageboard.py:1.1

Stephan Richter srichter@cosmos.phy.tufts.edu
Sat, 12 Jul 2003 12:43:20 -0400


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

Added Files:
	__init__.py configure.zcml fields.py interfaces.py message.py 
	messageboard.py 
Log Message:
Step 7 of the Content Components recipes. This recipe explains Events, 
Channels and Subscriptions.


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


=== Added File Products3/demo/messageboard/step7/configure.zcml ===
<zopeConfigure 
   xmlns="http://namespaces.zope.org/zope"
   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"/>


  <!-- 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>

  <content class=".message.Message">

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

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

    <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"
      />

  <!-- 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/step7/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/12 16:43:11 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/step7/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/12 16:43:11 srichter Exp $
"""
from zope.app.interfaces.container import IContainer
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 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 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/step7/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/12 16:43:11 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
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`}

        return size


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/step7/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/12 16:43:11 srichter Exp $
"""
from zope.interface import implements
from zope.app.container.btree import BTreeContainer
from zopeproducts.messageboard.interfaces import IMessageBoard

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)