Whitespace.

This commit is contained in:
Timo Stollenwerk 2012-01-09 16:31:52 +01:00
parent c5864ca9f7
commit 1f01a71aac
7 changed files with 84 additions and 84 deletions

View File

@ -1,7 +1,7 @@
[buildout]
extends =
http://svn.plone.org/svn/collective/buildout/plonetest/plone-4.1.x.cfg
package-name = plone.app.discussion
package-name = plone.app.discussion
package-directory = plone/app/discussion
parts += instance

View File

@ -2,7 +2,7 @@
extends =
buildout.cfg
extensions = buildout.eggtractor mr.developer
tractor-src-directory =
tractor-src-directory =
.
src
auto-checkout =
@ -15,7 +15,7 @@ auto-checkout =
parts +=
omelette
releaser
pocompile
pocompile
zopepy
sphinxbuilder
sphinxupload

View File

@ -19,19 +19,19 @@ plone.app.discussion.
permissions.
**Discussion items are light weight objects**
Discussion item objects are as light weight as possible. Ideally, a
discussion item should be as lightweight as a catalog brain. This may mean
that we forego convenience base classes and re-implement certain interfaces.
Comments should not provide the full set of dublin core metadata, though
Discussion item objects are as light weight as possible. Ideally, a
discussion item should be as lightweight as a catalog brain. This may mean
that we forego convenience base classes and re-implement certain interfaces.
Comments should not provide the full set of dublin core metadata, though
custom indexers can be used to provide values for standard catalog indexes.
**Optimise for retrival speed**
HTML filtering and other processing should happen on save, not on render,
to make rendering quick.
**Settings are stored using plone.registry**
**Settings are stored using plone.registry**
Any global setting should be stored in plone.registry records.
**Forms are constructed using extensible z3c.form forms**
This allows plugins (such as spam protection algorithms) to provide
additional validation. It also allows integrators to write add-ons that add
@ -45,15 +45,15 @@ plone.app.discussion.
not stored threaded, the dict interface should act as if they are, i.e.
calling items() on a comment should return the replies to that comment
(in order).
**Discussion items are retrieved in reverse creation date order**
Discussion items do not need to support explicit ordering. They should
always be retrieved in reverse creation date order (most recent for).
They can be stored with keys so that this is always true.
**Discussion items do not need readable ids**
Ids can be based on the creation date.
**Discussion items send events**
The usual zope.lifecycleevent and zope.container events are fired when
The usual zope.lifecycleevent and zope.container events are fired when
discussion items are added, removed, or modified.

View File

@ -71,67 +71,67 @@ logger = logging.getLogger("plone.app.discussion")
class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
RoleManager, Owned, Implicit, Persistent):
"""A comment.
This object attempts to be as lightweight as possible. We implement a
number of standard methods instead of subclassing, to have total control
over what goes into the object.
"""
implements(IComment)
meta_type = portal_type = 'Discussion Item'
# This needs to be kept in sync with types/Discussion_Item.xml title
fti_title = 'Comment'
__parent__ = None
comment_id = None # long
in_reply_to = None # long
comment_id = None # long
in_reply_to = None # long
title = u""
mime_type = None
text = u""
creator = None
creation_date = None
modification_date = None
author_username = None
author_name = None
author_email = None
user_notification = None
# Note: we want to use zope.component.createObject() to instantiate
# comments as far as possible. comment_id and __parent__ are set via
# IConversation.addComment().
def __init__(self):
self.creation_date = self.modification_date = datetime.utcnow()
@property
def __name__(self):
return self.comment_id and unicode(self.comment_id) or None
@property
def id(self):
return self.comment_id and str(self.comment_id) or None
def getId(self):
"""The id of the comment, as a string.
"""
return self.id
def getText(self, targetMimetype=None):
"""The body text of a comment.
"""
transforms = getToolByName(self, 'portal_transforms')
if targetMimetype is None:
targetMimetype = 'text/x-html-safe'
sourceMimetype = getattr(self, 'mime_type', None)
if sourceMimetype is None:
registry = queryUtility(IRegistry)
@ -146,21 +146,21 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
text,
context=self,
mimetype=sourceMimetype).getData()
def Title(self):
"""The title of the comment.
"""
if self.title:
return self.title
if not self.creator:
creator = translate(Message(_(u"label_anonymous",
default=u"Anonymous")))
else:
creator = self.creator
creator = creator
# Fetch the content object (the parent of the comment is the
# conversation, the parent of the conversation is the content object).
content = aq_base(self.__parent__.__parent__)
@ -169,26 +169,26 @@ class Comment(CatalogAware, WorkflowAware, DynamicType, Traversable,
mapping={'creator': creator,
'content': safe_unicode(content.Title())}))
return title
def Creator(self):
"""The name of the person who wrote the comment.
"""
return self.creator
def Type(self):
"""The Discussion Item content type.
"""
return self.fti_title
# CMF's event handlers assume any IDynamicType has these :(
def opaqueItems(self): # pragma: no cover
def opaqueItems(self): # pragma: no cover
return []
def opaqueIds(self): # pragma: no cover
def opaqueIds(self): # pragma: no cover
return []
def opaqueValues(self): # pragma: no cover
def opaqueValues(self): # pragma: no cover
return []
CommentFactory = Factory(Comment)
@ -242,24 +242,24 @@ def notify_content_object_moved(obj, event):
for comment in conversation.getComments():
aq_base(comment).__parent__.__parent__.__parent__ = event.newParent
catalog.reindexObject(aq_base(comment))
def notify_user(obj, event):
"""Tell users when a comment has been added.
This method composes and sends emails to all users that have added a
comment to this conversation and enabled user notification.
This requires the user_notification setting to be enabled in the
discussion control panel.
"""
# Check if user notification is enabled
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings, check=False)
if not settings.user_notification_enabled:
return
# Get informations that are necessary to send an email
mail_host = getToolByName(obj, 'MailHost')
portal_url = getToolByName(obj, 'portal_url')
@ -282,16 +282,16 @@ def notify_user(obj, event):
if (obj != comment and
comment.user_notification and comment.author_email):
emails.add(comment.author_email)
if not emails:
return
subject = translate(_(u"A comment has been posted."),
context=obj.REQUEST)
message = translate(Message(
MAIL_NOTIFICATION_MESSAGE,
mapping={'title': safe_unicode(content_object.title),
'link': content_object.absolute_url() +
'link': content_object.absolute_url() +
'/view#' + obj.id,
'text': obj.text}),
context=obj.REQUEST)
@ -312,13 +312,13 @@ def notify_user(obj, event):
def notify_moderator(obj, event):
"""Tell the moderator when a comment needs attention.
This method sends an email to the moderator if comment moderation a new
This method sends an email to the moderator if comment moderation a new
comment has been added that needs to be approved.
The moderator_notification setting has to be enabled in the discussion
control panel.
Configure the moderator e-mail address in the discussion control panel.
If no moderator is configured but moderator notifications are turned on,
the site admin email (from the mail control panel) will be used.
@ -328,25 +328,25 @@ def notify_moderator(obj, event):
settings = registry.forInterface(IDiscussionSettings, check=False)
if not settings.moderator_notification_enabled:
return
# Get informations that are necessary to send an email
mail_host = getToolByName(obj, 'MailHost')
portal_url = getToolByName(obj, 'portal_url')
portal = portal_url.getPortalObject()
sender = portal.getProperty('email_from_address')
if settings.moderator_email:
mto = settings.moderator_email
else:
mto = sender
# Check if a sender address is available
if not sender:
return
conversation = aq_parent(obj)
content_object = aq_parent(conversation)
# Compose email
subject = translate(_(u"A comment has been posted."), context=obj.REQUEST)
message = translate(Message(MAIL_NOTIFICATION_MESSAGE_MODERATOR,
@ -358,7 +358,7 @@ def notify_moderator(obj, event):
'link_delete': obj.absolute_url() + '/@@moderate-delete-comment',
}),
context=obj.REQUEST)
# Send email
try:
mail_host.send(message, mto, sender, subject, charset='utf-8')

View File

@ -17,7 +17,7 @@ id and allow traversal). Hence, traversing to obj/++conversation++/123 retrieves
the comment with id 123.
Comments ids are assigned in order, so a comment with id N was posted before
a comment with id N + 1. However, it is not guaranteed that ids will be
a comment with id N + 1. However, it is not guaranteed that ids will be
incremental. Ids must be positive integers - 0 or negative numbers are not
allowed.
@ -42,13 +42,13 @@ Factories
Comments should always be created via the 'Discussion Item' IFactory utility.
Conversations should always be obtained via the IConversation adapter (even
the ++conversation++ namespace should use this). This makes it possible to
the ++conversation++ namespace should use this). This makes it possible to
replace conversations and comments transparently.
The Comment class
-----------------
The inheritance tree for DiscussionItem is shown below. Classes we want to
The inheritance tree for DiscussionItem is shown below. Classes we want to
mix in and interface we want to implement in the Comment class are marked
with [x].
@ -60,17 +60,17 @@ with [x].
[ ] DynamicType = [ ] IDynamicType
[ ] CMFCatalogAware = [ ] <no interface>
[ ] SimpleItem = [ ] ISimpleItem
[ ] Item [ ]
[ ] Item [ ]
[?] Base = [ ] <no interface>
[ ] Resource = [ ] <no interface>
[ ] CopySource = [ ] ICopySource
[ ] CopySource = [ ] ICopySource
[ ] Tabs = [ ] <no interface>
[x] Traversable = [ ] ITraversable
[ ] Element = [ ] <no interface>
[x] Owned = [ ] IOwned
[ ] UndoSupport = [ ] IUndoSupport
[ ] Persistent [ ]
[ ] Implicit [ ]
[ ] Persistent [ ]
[ ] Implicit [ ]
[x] RoleManager = [ ] IRoleManager
[ ] RoleManager = [ ] IPermissionMappingSupport
[ ] DefaultDublinCoreImpl = [ ] IDublinCore
@ -86,7 +86,7 @@ Thus, we want:
- we do not want implicit acquisition
* Owned, to be able to track ownership
* RoleManager, to support permissions and local roles
We also want to use a number of custom indexers for most of the standard
metadata such as creator, effective date etc.
@ -121,7 +121,7 @@ In addition, we'll need a 'Moderator' role and a moderation permission,
* Moderate comment
* Bypass moderation
To control whether Anonymous can post comments, we manage the 'Reply to Item'
permission. To control whether moderation is required for various roles, we
could manage the 'Bypass moderation' permission.
@ -136,7 +136,7 @@ These could work in a workflow like this:
+----- {auto-publish} -----+
| |
+----- {auto-moderate} ----+
The 'posted' state is the initial state. 'published' is the state where the
comment is visible to non-reviewers.
@ -150,22 +150,22 @@ the 'Bypass moderation' permission.
The 'auto-moderate' transition would be another automatic transition protected
by an expression (e.g. calling a view) that returns True if the user is on
an auto-moderation 'white-list', e.g. by email address or username.
Forms and UI
------------
The basic commenting display/reply form is placed in a viewlet.
The reply form is dynamically created right under the comment when the user hits
the reply button. To do so, we copy the standard comment form with a jQuery
the reply button. To do so, we copy the standard comment form with a jQuery
function. This function sets the form's hidden in_reply_to field to the id of
the comment the user wants to reply to. This also makes it possible to use
z3c.form validation for the reply forms, because we can uniquely identify the
the comment the user wants to reply to. This also makes it possible to use
z3c.form validation for the reply forms, because we can uniquely identify the
reply form request and return the reply form with validation errors.
Since we rely on JavaScript for the reply form creation, the reply button is
removed for non JavaScript enabled browsers.
The comment form uses z3c.form and plone.z3cform's ExtensibleForm support. This
makes it possible to plug in additional fields declaratively, e.g. to include
The comment form uses z3c.form and plone.z3cform's ExtensibleForm support. This
makes it possible to plug in additional fields declaratively, e.g. to include
SPAM protection.

View File

@ -112,7 +112,7 @@ class IDiscussionSettings(Interface):
required=False,
default=False,
)
moderator_email = schema.ASCIILine(
title = _(u'label_moderator_email', default=u'Moderator Email Address'),
description = _(u'help_moderator_email',

View File

@ -1,7 +1,7 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup">
<genericsetup:upgradeStep
source="*"
destination="100"
@ -10,5 +10,5 @@
profile="plone.app.discussion:default"
handler=".upgrades.update_registry"
/>
</configure>