Some more design notes/sketchings. Nothing that works yet.

svn path=/plone.app.discussion/trunk/; revision=26918
This commit is contained in:
Martin Aspeli 2009-05-13 14:54:06 +00:00
parent 32f79c7b3c
commit 3b2db86d54
7 changed files with 236 additions and 87 deletions

View File

@ -1,5 +1,5 @@
"""Implement an IPublishTraverse adapter for discussion items that allows
traversal into the 'replies' dictionary, as well as the ++comments++ traversal
namespace.
"""Implement the ++comments++ traversal namespace. This should return the
IDiscussion container for the context, from which traversal will continue
into an actual comment object.
"""

View File

@ -1,26 +1,15 @@
"""Discussion items and replies
"""The default comment class and factory.
"""
from zope.interface import implements, alsoProvides
from BTrees.OOBTree import OOBTree
from zope.interface import implements
from zope.component.factory import Factory
from Acquisition import Explicit
from OFS.Traversable import Traversable
from AccessControl.Role import RoleManager
from AccessControl.Owned import Owned
from plone.app.discussion.interfaces import IReplies, 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
from plone.app.discussion.interfaces import IComment
class Comment(Explicit, Traversable, RoleManager, Owned):
"""A comment.
@ -36,11 +25,10 @@ class Comment(Explicit, Traversable, RoleManager, Owned):
__parent__ = None
__name__ = None
ancestor = None
title = u""
mime_type = "text/plain"
mime_type = "text/plain"
text = u""
creator = None
@ -52,23 +40,32 @@ class Comment(Explicit, Traversable, RoleManager, Owned):
author_name = None
author_email = None
replies = None
def __init__(self, id, ancestor, parent, **kw):
self.__name__ = id
self.__parent__ = parent
self.ancestor = ancestor
def __init__(self, id=None, conversation=None, **kw):
self.__name__ = unicode(id)
self.__parent__ = conversation
for k, v in kw:
setattr(self, k, v)
replies = Replies()
# convenience functions
@property
def id(self):
return self.__name__
return str(self.__name__)
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)

View File

@ -1,6 +1,12 @@
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="plone.app.discussion">
<configure 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>

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

View File

@ -1,3 +0,0 @@
"""Default implementation of the IDiscussable adapter.
"""

View File

@ -6,57 +6,6 @@ from zope.i18nmessageid import MessageFactory
_ = MessageFactory('plone.app.discussion')
class IReplies(IIterableMapping, IWriteMapping):
"""A set of related comments
This acts as a mapping (dict) with string keys and values being other
discussion items in reply to this discussion item.
"""
class IHasReplies(Interface):
"""Common interface for objects that have replies.
"""
replies = schema.Object(title=_(u"Replies"), schema=IReplies)
class IComment(IHasReplies):
"""A comment
"""
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)
__name__ = schema.TextLine(title=_(u"Name"))
ancestor = schema.Object(title=_(u"The original content object the comment is for"), schema=Interface)
title = schema.TextLine(title=_(u"Subject"))
mime_type = schema.ASCIILine(title=_(u"MIME type"), default="text/plain")
text = schema.Text(title=_(u"Comment text"))
creator = schema.TextLine(title=_(u"Author name (for display)"))
creation_date = schema.Date(title=_(u"Creation date"))
modification_date = schema.Date(title=_(u"Modification date"))
# for logged in comments - set to None for anonymous
author_username = schema.TextLine(title=_(u"Author username"), required=False)
# for anonymous comments only, set to None for logged in comments
author_name = schema.TextLine(title=_(u"Author name"), 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.
@ -65,4 +14,100 @@ class IDiscussionSettings(Interface):
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):
"""A set of related comments in reply to a given content object or
another comment.
Adapt a conversation or another comment to this interface to obtain the
direct replies.
"""
class IComment(Interface):
"""A comment.
Comments are indexed in the catalog and subject to workflow and security.
"""
portal_type = schema.ASCIILine(title=_(u"Portal type"), default="Discussion Item")
__parent__ = schema.Object(title=_(u"Conversation"), schema=Interface)
__name__ = schema.TextLine(title=_(u"Name"))
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"))
mime_type = schema.ASCIILine(title=_(u"MIME type"), default="text/plain")
text = schema.Text(title=_(u"Comment text"))
creator = schema.TextLine(title=_(u"Author name (for display)"))
creation_date = schema.Date(title=_(u"Creation date"))
modification_date = schema.Date(title=_(u"Modification date"))
# for logged in comments - set to None for anonymous
author_username = schema.TextLine(title=_(u"Author username"), required=False)
# for anonymous comments only, set to None for logged in comments
author_name = schema.TextLine(title=_(u"Author name"), required=False)
author_email = schema.TextLine(title=_(u"Author email address"), required=False)

View 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>