2010-06-04 13:22:16 +02:00
|
|
|
============
|
|
|
|
Design Notes
|
|
|
|
============
|
2009-05-11 18:52:16 +02:00
|
|
|
|
|
|
|
This document contains design notes for plone.app.discussion.
|
|
|
|
|
2009-05-13 17:20:02 +02:00
|
|
|
Storage and traversal
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
For each content item, there is a Conversation object stored in annotations.
|
2010-06-04 13:22:16 +02:00
|
|
|
This can be traversed to via the ++conversation++ namespace, but also fetched
|
2009-05-13 17:20:02 +02:00
|
|
|
via an adapter lookup to IConversation.
|
|
|
|
|
|
|
|
The conversation stores all comments related to a content object. Each
|
|
|
|
comment has an integer id (also representable as a string, to act as an OFS
|
2010-06-04 13:22:16 +02:00
|
|
|
id and allow traversal). Hence, traversing to obj/++conversation++/123 retrieves
|
2009-05-13 17:20:02 +02:00
|
|
|
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
|
|
|
|
incremental. Ids must be positive integers - 0 or negative numbers are not
|
|
|
|
allowed.
|
|
|
|
|
|
|
|
Threading information is stored in the conversation: we keep track of the
|
|
|
|
set of children and the parent if any comment. Top-level comments have a
|
|
|
|
parent id of 0. This information is managed by the conversation class when
|
|
|
|
comments are manipulated using a dict-like API.
|
|
|
|
|
|
|
|
Note that the __parent__/acquisition parent of an IComment is the
|
|
|
|
IConversation, and the __parent__/acquisition parent of an IConversation is
|
|
|
|
the content object.
|
|
|
|
|
|
|
|
Events
|
|
|
|
------
|
|
|
|
|
|
|
|
Manipulating the IConversation object should fire the usual IObjectAddedEvent
|
|
|
|
and IObjectRemovedEvent events. The UI may further fire IObjectCreatedEvent
|
|
|
|
and IObjectModifiedEvent for comments.
|
|
|
|
|
|
|
|
Factories
|
|
|
|
---------
|
|
|
|
|
|
|
|
Comments should always be created via the 'Discussion Item' IFactory utility.
|
|
|
|
Conversations should always be obtained via the IConversation adapter (even
|
2010-06-04 13:22:16 +02:00
|
|
|
the ++conversation++ namespace should use this). This makes it possible to
|
|
|
|
replace conversations and comments transparently.
|
2009-05-13 17:20:02 +02:00
|
|
|
|
2009-05-11 18:52:16 +02:00
|
|
|
The Comment class
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
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].
|
|
|
|
|
2010-06-17 11:01:59 +02:00
|
|
|
::
|
|
|
|
|
2009-05-11 18:52:16 +02:00
|
|
|
[ ] DiscussionItem
|
|
|
|
[ ] Document
|
|
|
|
[ ] PortalContent = [ ] IContentish
|
|
|
|
[ ] DynamicType = [ ] IDynamicType
|
|
|
|
[ ] CMFCatalogAware = [ ] <no interface>
|
|
|
|
[ ] SimpleItem = [ ] ISimpleItem
|
|
|
|
[ ] Item [ ]
|
|
|
|
[?] Base = [ ] <no interface>
|
|
|
|
[ ] Resource = [ ] <no interface>
|
|
|
|
[ ] CopySource = [ ] ICopySource
|
|
|
|
[ ] Tabs = [ ] <no interface>
|
|
|
|
[x] Traversable = [ ] ITraversable
|
|
|
|
[ ] Element = [ ] <no interface>
|
|
|
|
[x] Owned = [ ] IOwned
|
|
|
|
[ ] UndoSupport = [ ] IUndoSupport
|
|
|
|
[ ] Persistent [ ]
|
|
|
|
[ ] Implicit [ ]
|
|
|
|
[x] RoleManager = [ ] IRoleManager
|
|
|
|
[ ] RoleManager = [ ] IPermissionMappingSupport
|
|
|
|
[ ] DefaultDublinCoreImpl = [ ] IDublinCore
|
|
|
|
[ ] ICatalogableDublinCore
|
|
|
|
[ ] IMutableDublinCore
|
|
|
|
[ ] PropertyManager = [ ] IPropertyManager
|
|
|
|
|
|
|
|
Thus, we want:
|
|
|
|
|
|
|
|
* Traversable, to get absolute_url() and friends
|
2010-06-17 11:01:59 +02:00
|
|
|
- this requires a good acquisition chain at all times
|
2009-05-11 18:52:16 +02:00
|
|
|
* Acquisition.Explicit, to support acquisition
|
2010-06-17 11:01:59 +02:00
|
|
|
- we do not want implicit acquisition
|
2009-05-11 18:52:16 +02:00
|
|
|
* 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.
|
|
|
|
|
|
|
|
Finally, we'll need event handlers to perform the actual indexing.
|
|
|
|
|
|
|
|
Discussion settings
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
Discussion can be enabled per-type and per-instance, via values in the FTI
|
|
|
|
(allow_discussion) and on the object. These will remain unchanged. The
|
2009-05-13 17:20:02 +02:00
|
|
|
IConversation object's 'enabled' property should consult these.
|
2009-05-11 18:52:16 +02:00
|
|
|
|
|
|
|
Global settings should be managed using plone.registry. A control panel
|
|
|
|
can be generated from this as well, using the helper class in
|
|
|
|
plone.app.registry.
|
|
|
|
|
|
|
|
Note that some settings, notably those to do with permissions and workflow,
|
|
|
|
will need to be wired up as custom form fields with custom data mangers
|
|
|
|
or similar.
|
|
|
|
|
|
|
|
Workflow and permissions
|
|
|
|
------------------------
|
|
|
|
|
|
|
|
Where possible, we should use existing permissions:
|
|
|
|
|
|
|
|
* View
|
|
|
|
* Reply to Item
|
|
|
|
* Modify Portal Content
|
|
|
|
* Request Review
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
These could work in a workflow like this:
|
|
|
|
|
2010-06-17 11:01:59 +02:00
|
|
|
::
|
|
|
|
|
2009-05-11 18:52:16 +02:00
|
|
|
* --> [posted] -- {publish} --> [published]--> *
|
|
|
|
| ^
|
|
|
|
| |
|
|
|
|
+----- {auto-publish} -----+
|
|
|
|
| |
|
|
|
|
+----- {auto-moderate} ----+
|
|
|
|
|
|
|
|
The 'posted' state is the initial state. 'published' is the state where the
|
|
|
|
comment is visible to non-reviewers.
|
|
|
|
|
|
|
|
The 'publish' transition would be protected by the 'Moderate comment'
|
|
|
|
permission. We could have states and transition for 'rejected', etc, but it
|
|
|
|
is probably just as good to delete comments that are rejected.
|
|
|
|
|
|
|
|
The 'auto-publish' transition would be an automatic transition protected by
|
|
|
|
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
|
|
|
|
------------
|
|
|
|
|
2010-06-04 13:22:16 +02:00
|
|
|
The basic commenting display/reply form is placed in a viewlet.
|
2009-05-11 18:52:16 +02:00
|
|
|
|
2010-06-04 13:22:16 +02:00
|
|
|
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
|
|
|
|
function. This function sets the form's hidden in_reply_to field to the id of
|
2010-08-06 12:43:44 +02:00
|
|
|
the comment the user wants to reply to. This also makes it possible to use
|
2010-06-04 13:22:16 +02:00
|
|
|
z3c.form validation for the reply forms, because we can uniquely identify the
|
2010-08-06 12:43:44 +02:00
|
|
|
reply form request and return the reply form with validation errors.
|
2009-05-11 18:52:16 +02:00
|
|
|
|
2010-06-04 13:22:16 +02:00
|
|
|
Since we rely on JavaScript for the reply form creation, the reply button is
|
2010-08-06 12:43:44 +02:00
|
|
|
removed for non JavaScript enabled browsers.
|
2009-05-11 18:52:16 +02:00
|
|
|
|
2010-06-04 13:22:16 +02:00
|
|
|
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.
|