Some more design notes/sketchings. Nothing that works yet.
svn path=/plone.app.discussion/trunk/; revision=26918
This commit is contained in:
parent
32f79c7b3c
commit
3b2db86d54
@ -1,5 +1,5 @@
|
|||||||
"""Implement an IPublishTraverse adapter for discussion items that allows
|
"""Implement the ++comments++ traversal namespace. This should return the
|
||||||
traversal into the 'replies' dictionary, as well as the ++comments++ traversal
|
IDiscussion container for the context, from which traversal will continue
|
||||||
namespace.
|
into an actual comment object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -1,26 +1,15 @@
|
|||||||
"""Discussion items and replies
|
"""The default comment class and factory.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.interface import implements, alsoProvides
|
from zope.interface import implements
|
||||||
|
from zope.component.factory import Factory
|
||||||
from BTrees.OOBTree import OOBTree
|
|
||||||
|
|
||||||
from Acquisition import Explicit
|
from Acquisition import Explicit
|
||||||
from OFS.Traversable import Traversable
|
from OFS.Traversable import Traversable
|
||||||
from AccessControl.Role import RoleManager
|
from AccessControl.Role import RoleManager
|
||||||
from AccessControl.Owned import Owned
|
from AccessControl.Owned import Owned
|
||||||
|
|
||||||
from plone.app.discussion.interfaces import IReplies, IComment
|
from plone.app.discussion.interfaces import IComment
|
||||||
|
|
||||||
def Replies():
|
|
||||||
"""Create a new replies object. Acts like a constructor, but actually
|
|
||||||
returns a BTree marked with an interface. We do this because subclassing
|
|
||||||
an OOBTree does not work properly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
replies = OOBTree()
|
|
||||||
alsoProvides(replies, IReplies)
|
|
||||||
return replies
|
|
||||||
|
|
||||||
class Comment(Explicit, Traversable, RoleManager, Owned):
|
class Comment(Explicit, Traversable, RoleManager, Owned):
|
||||||
"""A comment.
|
"""A comment.
|
||||||
@ -36,11 +25,10 @@ class Comment(Explicit, Traversable, RoleManager, Owned):
|
|||||||
|
|
||||||
__parent__ = None
|
__parent__ = None
|
||||||
__name__ = None
|
__name__ = None
|
||||||
ancestor = None
|
|
||||||
|
|
||||||
title = u""
|
title = u""
|
||||||
mime_type = "text/plain"
|
|
||||||
|
|
||||||
|
mime_type = "text/plain"
|
||||||
text = u""
|
text = u""
|
||||||
|
|
||||||
creator = None
|
creator = None
|
||||||
@ -52,23 +40,32 @@ class Comment(Explicit, Traversable, RoleManager, Owned):
|
|||||||
author_name = None
|
author_name = None
|
||||||
author_email = None
|
author_email = None
|
||||||
|
|
||||||
replies = None
|
def __init__(self, id=None, conversation=None, **kw):
|
||||||
|
self.__name__ = unicode(id)
|
||||||
def __init__(self, id, ancestor, parent, **kw):
|
self.__parent__ = conversation
|
||||||
self.__name__ = id
|
|
||||||
self.__parent__ = parent
|
|
||||||
self.ancestor = ancestor
|
|
||||||
|
|
||||||
for k, v in kw:
|
for k, v in kw:
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
replies = Replies()
|
|
||||||
|
|
||||||
# convenience functions
|
# convenience functions
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
return self.__name__
|
return str(self.__name__)
|
||||||
|
|
||||||
def getId(self):
|
def getId(self):
|
||||||
return self.__name__
|
"""The id of the comment, as a string
|
||||||
|
"""
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
def Title(self):
|
||||||
|
"""The title of the comment
|
||||||
|
"""
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def Creator(self):
|
||||||
|
"""The name of the person who wrote the comment
|
||||||
|
"""
|
||||||
|
return self.creator
|
||||||
|
|
||||||
|
CommentFactory = Factory(Comment)
|
@ -1,6 +1,12 @@
|
|||||||
<configure
|
<configure xmlns="http://namespaces.zope.org/zope" i18n_domain="plone.app.discussion">
|
||||||
xmlns="http://namespaces.zope.org/zope"
|
|
||||||
i18n_domain="plone.app.discussion">
|
|
||||||
|
|
||||||
|
<include file="permissions.zcml" />
|
||||||
|
|
||||||
|
<utility object=".comment.CommentFactory" name="Discussion Item" />
|
||||||
|
|
||||||
|
<class class=".comment.Comment">
|
||||||
|
<require interface=".comment.Comment" permission="zope2.View" />
|
||||||
|
<require attributes="Title Creator getId" permission="zope2.View" />
|
||||||
|
</class>
|
||||||
|
|
||||||
</configure>
|
</configure>
|
||||||
|
95
plone/app/discussion/conversation.py
Normal file
95
plone/app/discussion/conversation.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
"""The conversation and replies adapters
|
||||||
|
|
||||||
|
The conversation is responsible for storing all comments. It provides a
|
||||||
|
dict-like API for accessing comments, where keys are integers and values
|
||||||
|
are IComment objects. It also provides features for finding comments quickly.
|
||||||
|
|
||||||
|
The two IReplies adapters - one for the IConversation and one for IComment -
|
||||||
|
manipulate the same data structures, but provide an API for finding and
|
||||||
|
manipulating the comments directly in reply to a particular comment or at the
|
||||||
|
top level of the conversation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.interface import implements
|
||||||
|
from zope.component import adapts
|
||||||
|
|
||||||
|
from BTrees.OIBTree import OIBTree
|
||||||
|
from BTrees.IOBTree import IOBTree
|
||||||
|
from BTrees.IIBTree import IIBTree, IISet
|
||||||
|
|
||||||
|
from Acquisition import Explicit
|
||||||
|
|
||||||
|
from plone.app.discussion.interfaces import IConversation, IComment, IReplies
|
||||||
|
|
||||||
|
class Conversation(Explicit):
|
||||||
|
"""A conversation is a container for all comments on a content object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
implements(IConversation)
|
||||||
|
|
||||||
|
def __init__(self, id="++comments++"):
|
||||||
|
self.id = id
|
||||||
|
|
||||||
|
# username -> count of comments; key is removed when count reaches 0
|
||||||
|
self._commentators = OIBTree()
|
||||||
|
self._last_comment_date = None
|
||||||
|
|
||||||
|
# id -> comment - find comment by id
|
||||||
|
self._comments = IOBTree()
|
||||||
|
|
||||||
|
# # id -> IISet (children) - find all children for a given comment. 0 signifies root.
|
||||||
|
self._children = IOBTree()
|
||||||
|
|
||||||
|
# id -> id (parent) - find the parent for a given comment. 0 signifies root
|
||||||
|
self._parents = IIBTree()
|
||||||
|
|
||||||
|
def getId(self):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
return self.id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enabled(self):
|
||||||
|
# TODO
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_comments(self):
|
||||||
|
# TODO
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def last_comment_date(self):
|
||||||
|
# TODO
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def commentators(self):
|
||||||
|
# TODO:
|
||||||
|
return set()
|
||||||
|
|
||||||
|
def getComments(start=0, size=None):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getThreads(start=0, size=None, root=None, depth=None):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Dict API
|
||||||
|
|
||||||
|
# TODO: Update internal data structures when items added or removed
|
||||||
|
|
||||||
|
class ConversationReplies(object):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
|
||||||
|
implements(IReplies)
|
||||||
|
adapts(Conversation)
|
||||||
|
|
||||||
|
class CommentReplies(object):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
|
||||||
|
implements(IReplies)
|
||||||
|
adapts(IComment)
|
@ -1,3 +0,0 @@
|
|||||||
"""Default implementation of the IDiscussable adapter.
|
|
||||||
"""
|
|
||||||
|
|
@ -6,29 +6,95 @@ from zope.i18nmessageid import MessageFactory
|
|||||||
|
|
||||||
_ = MessageFactory('plone.app.discussion')
|
_ = MessageFactory('plone.app.discussion')
|
||||||
|
|
||||||
|
class IDiscussionSettings(Interface):
|
||||||
|
"""Global discussion settings. This describes records stored in the
|
||||||
|
configuration registry and obtainable via plone.registry.
|
||||||
|
"""
|
||||||
|
|
||||||
|
globally_enabled = schema.Bool(title=_(u"Globally enabled"),
|
||||||
|
description=_(u"Use this setting to enable or disable comments globally"),
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
index_comments = schema.Bool(title=_(u"Index comments"),
|
||||||
|
description=_(u"Enable this option to ensure that comments are searchable. "
|
||||||
|
"Turning this off may improve performance for sites with large "
|
||||||
|
"volumes of comments that do not wish to make them searcahble using "
|
||||||
|
"the standard search tools."),
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
class IConversation(IIterableMapping, IWriteMapping):
|
||||||
|
"""A conversation about a content object.
|
||||||
|
|
||||||
|
This is a persistent object in its own right and manages all comments.
|
||||||
|
|
||||||
|
The dict interface allows access to all comments. They are stored by
|
||||||
|
integer key, in the order they were added.
|
||||||
|
|
||||||
|
To get replies at the top level, adapt the conversation to IReplies.
|
||||||
|
|
||||||
|
The conversation can be traversed to via the ++comments++ namespace.
|
||||||
|
For example, path/to/object/++comments++/123 retrieves comment 123.
|
||||||
|
|
||||||
|
The __parent__ of the conversation (and the acquisition parent during
|
||||||
|
traversal) is the content object. The conversation is the __parent__
|
||||||
|
(and acquisition parent) for all comments, regardless of threading.
|
||||||
|
"""
|
||||||
|
|
||||||
|
enabled = schema.Bool(title=_(u"Is commenting enabled?"))
|
||||||
|
|
||||||
|
total_comments = schema.Int(title=_(u"Total number of comments on this item"), min=0, readonly=True)
|
||||||
|
last_comment_date = schema.Date(title=_(u"Date of the most recent comment"), readonly=True)
|
||||||
|
commentators = schema.Set(title=_(u"The set of unique commentators (usernames)"), readonly=True)
|
||||||
|
|
||||||
|
def getComments(start=0, size=None):
|
||||||
|
"""Return a batch of comment objects for rendering. The 'start'
|
||||||
|
parameter is the id of the comment from which to start the batch.
|
||||||
|
The 'size' parameter is the number of comments to return in the
|
||||||
|
batch.
|
||||||
|
|
||||||
|
The comments are returned in creation date order, in the exact batche
|
||||||
|
size specified.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def getThreads(start=0, size=None, root=None, depth=None):
|
||||||
|
"""Return a batch of comment objects for rendering. The 'start'
|
||||||
|
parameter is the id of the comment from which to start the batch.
|
||||||
|
The 'size' parameter is the number of comments to return in the
|
||||||
|
batch. 'root', if given, is the id of the comment to which reply
|
||||||
|
threads will be found. If not given, all threads are returned.
|
||||||
|
If 'depth' is given, it can be used to limit the depth of threads
|
||||||
|
returned. For example, depth=1 will return only direct replies.
|
||||||
|
|
||||||
|
Comments are returned as a recursive list of '(comment, children)',
|
||||||
|
where 'children' is a similar list of (comment, children), or an empty
|
||||||
|
list of a comment has no direct replies.
|
||||||
|
|
||||||
|
The returned number of comments may be bigger than the batch size,
|
||||||
|
in order to give enough context to show the full lineage of the
|
||||||
|
starting comment.
|
||||||
|
"""
|
||||||
|
|
||||||
class IReplies(IIterableMapping, IWriteMapping):
|
class IReplies(IIterableMapping, IWriteMapping):
|
||||||
"""A set of related comments
|
"""A set of related comments in reply to a given content object or
|
||||||
|
another comment.
|
||||||
|
|
||||||
This acts as a mapping (dict) with string keys and values being other
|
Adapt a conversation or another comment to this interface to obtain the
|
||||||
discussion items in reply to this discussion item.
|
direct replies.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class IHasReplies(Interface):
|
class IComment(Interface):
|
||||||
"""Common interface for objects that have replies.
|
"""A comment.
|
||||||
"""
|
|
||||||
|
|
||||||
replies = schema.Object(title=_(u"Replies"), schema=IReplies)
|
Comments are indexed in the catalog and subject to workflow and security.
|
||||||
|
|
||||||
class IComment(IHasReplies):
|
|
||||||
"""A comment
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
portal_type = schema.ASCIILine(title=_(u"Portal type"), default="Discussion Item")
|
portal_type = schema.ASCIILine(title=_(u"Portal type"), default="Discussion Item")
|
||||||
|
|
||||||
__parent__ = schema.Object(title=_(u"In reply to"), description=_(u"Another comment or a content item"), schema=Interface)
|
__parent__ = schema.Object(title=_(u"Conversation"), schema=Interface)
|
||||||
__name__ = schema.TextLine(title=_(u"Name"))
|
__name__ = schema.TextLine(title=_(u"Name"))
|
||||||
|
|
||||||
ancestor = schema.Object(title=_(u"The original content object the comment is for"), schema=Interface)
|
comment_id = schema.Int(title=_(u"A comment id unique to this conversation"))
|
||||||
|
in_reply_to = schema.Int(title=_(u"Id of comment this comment is in reply to"), required=False)
|
||||||
|
|
||||||
title = schema.TextLine(title=_(u"Subject"))
|
title = schema.TextLine(title=_(u"Subject"))
|
||||||
|
|
||||||
@ -45,24 +111,3 @@ class IComment(IHasReplies):
|
|||||||
# for anonymous comments only, set to None for logged in comments
|
# for anonymous comments only, set to None for logged in comments
|
||||||
author_name = schema.TextLine(title=_(u"Author name"), required=False)
|
author_name = schema.TextLine(title=_(u"Author name"), required=False)
|
||||||
author_email = schema.TextLine(title=_(u"Author email address"), required=False)
|
author_email = schema.TextLine(title=_(u"Author email address"), required=False)
|
||||||
|
|
||||||
class IDiscussable(IHasReplies):
|
|
||||||
"""Adapt a content item to this interface to determine whether discussions
|
|
||||||
are currently enabled, and obtain a list of comments.
|
|
||||||
"""
|
|
||||||
|
|
||||||
enabled = schema.Bool(title=_(u"Is commenting enabled?"))
|
|
||||||
|
|
||||||
total_comments = schema.Int(title=_(u"Total number of comments on this item"), min=0, readonly=True)
|
|
||||||
last_comment_date = schema.Date(title=_(u"Date of the most recent comment"), readonly=True)
|
|
||||||
commentators = schema.Set(title=_(u"The set of unique commentators (usernames)"), readonly=True)
|
|
||||||
|
|
||||||
class IDiscussionSettings(Interface):
|
|
||||||
"""Global discussion settings. This describes records stored in the
|
|
||||||
configuration registry and obtainable via plone.registry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
globally_enabled = schema.Bool(title=_(u"Globally enabled"),
|
|
||||||
description=_(u"Use this setting to enable or disable comments globally"),
|
|
||||||
default=True)
|
|
||||||
|
|
||||||
|
9
plone/app/discussion/permissions.zcml
Normal file
9
plone/app/discussion/permissions.zcml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<configure
|
||||||
|
xmlns="http://namespaces.zope.org/zope"
|
||||||
|
i18n_domain="plone.app.discussion">
|
||||||
|
|
||||||
|
<include package="collective.autopermission" />
|
||||||
|
|
||||||
|
<!-- custom permissions are defined here -->
|
||||||
|
|
||||||
|
</configure>
|
Loading…
Reference in New Issue
Block a user