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