Whitespace.
This commit is contained in:
parent
c5864ca9f7
commit
1f01a71aac
@ -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
|
||||
|
||||
|
4
dev.cfg
4
dev.cfg
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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')
|
||||
|
@ -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.
|
||||
|
@ -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',
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user