Added basic infrastructure for discussion controlpanel.

svn path=/plone.app.discussion/trunk/; revision=27269
This commit is contained in:
Timo Stollenwerk 2009-06-02 21:20:53 +00:00
parent 47911fa24a
commit f27156e916
6 changed files with 146 additions and 45 deletions

View File

@ -3,6 +3,8 @@
xmlns:browser="http://namespaces.zope.org/browser" xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="plone.app.discussion"> i18n_domain="plone.app.discussion">
<include package="plone.app.registry" />
<!-- Traversal adapter --> <!-- Traversal adapter -->
<adapter factory=".traversal.ConversationNamespace" name="conversation" /> <adapter factory=".traversal.ConversationNamespace" name="conversation" />
@ -66,4 +68,20 @@
layer="..interfaces.IDiscussionLayer" layer="..interfaces.IDiscussionLayer"
/> />
<browser:page
name="discussion-settings"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".controlpanel.DiscussionSettingsControlPanel"
permission="cmf.ManagePortal"
/>
<!-- Utility view - use in portal_css or similar as portal/@@xdv-check/enabled" -->
<browser:page
name="discussion-check"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".controlpanel.Utility"
permission="zope.Public"
allowed_attributes="globally_enabled"
/>
</configure> </configure>

View File

@ -0,0 +1,57 @@
from Products.Five.browser import BrowserView
from zope.component import queryUtility
from plone.registry.interfaces import IRegistry
from plone.app.registry.browser import controlpanel
from plone.app.discussion.interfaces import IDiscussionSettings, _
try:
# only in z3c.form 2.0
from z3c.form.browser.textlines import TextLinesFieldWidget
from z3c.form.browser.widget import SingleCheckBoxWidget
except ImportError:
from plone.z3cform.textlines import TextLinesFieldWidget
from plone.z3cform.widget import SingleCheckBoxWidget
class DiscussionSettingsEditForm(controlpanel.RegistryEditForm):
schema = IDiscussionSettings
label = _(u"Discussion settings")
description = _(u"Please enter the options specified")
def updateFields(self):
super(DiscussionSettingsEditForm, self).updateFields()
#self.fields['globally_enabled'].widgetFactory = SingleCheckBoxWidget
def updateWidgets(self):
super(DiscussionSettingsEditForm, self).updateWidgets()
class DiscussionSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
form = DiscussionSettingsEditForm
class Utility(BrowserView):
"""Utility view to determine if the site is currently styled with xdv
"""
def globally_enabled(self):
"""Determine if the utility is enabled and we are in an enabled domain
"""
registry = queryUtility(IRegistry)
if registry is None:
return False
settings = None
try:
settings = registry.for_interface(IDiscussionSettings)
except KeyError:
return False
if not settings.globally_enabled:
return False
else:
return True
return False

View File

@ -11,80 +11,80 @@ class IDiscussionSettings(Interface):
configuration registry and obtainable via plone.registry. configuration registry and obtainable via plone.registry.
""" """
globally_enabled = schema.Bool(title=_(u"Globally enabled"), #globally_enabled = schema.Bool(title=_(u"Globally enabled"),
description=_(u"Use this setting to enable or disable comments globally"), # description=_(u"Use this setting to enable or disable comments globally"),
default=True) # default=True)
class IConversation(IIterableMapping): class IConversation(IIterableMapping):
"""A conversation about a content object. """A conversation about a content object.
This is a persistent object in its own right and manages all comments. 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 The dict interface allows access to all comments. They are stored by
long integer key, in the order they were added. long integer key, in the order they were added.
Note that __setitem__() is not supported - use addComment() instead. Note that __setitem__() is not supported - use addComment() instead.
However, comments can be deleted using __delitem__(). However, comments can be deleted using __delitem__().
To get replies at the top level, adapt the conversation to IReplies. To get replies at the top level, adapt the conversation to IReplies.
The conversation can be traversed to via the ++comments++ namespace. The conversation can be traversed to via the ++comments++ namespace.
For example, path/to/object/++comments++/123 retrieves comment 123. For example, path/to/object/++comments++/123 retrieves comment 123.
The __parent__ of the conversation (and the acquisition parent during The __parent__ of the conversation (and the acquisition parent during
traversal) is the content object. The conversation is the __parent__ traversal) is the content object. The conversation is the __parent__
(and acquisition parent) for all comments, regardless of threading. (and acquisition parent) for all comments, regardless of threading.
""" """
enabled = schema.Bool(title=_(u"Is commenting enabled?")) 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) 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) 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) commentators = schema.Set(title=_(u"The set of unique commentators (usernames)"), readonly=True)
def addComment(comment): def addComment(comment):
"""Adds a new comment to the list of comments, and returns the """Adds a new comment to the list of comments, and returns the
comment id that was assigned. The comment_id property on the comment comment id that was assigned. The comment_id property on the comment
will be set accordingly. will be set accordingly.
""" """
def __delitem__(key): def __delitem__(key):
"""Delete the comment with the given key. The key is a long id. """Delete the comment with the given key. The key is a long id.
""" """
def getComments(start=0, size=None): def getComments(start=0, size=None):
"""Return an iterator of comment objects for rendering. """Return an iterator of comment objects for rendering.
The 'start' parameter is the id of the comment from which to start the The 'start' parameter is the id of the comment from which to start the
batch. If no such comment exists, the next higher id will be used. batch. If no such comment exists, the next higher id will be used.
This means that you can use max key from a previous batch + 1 safely. This means that you can use max key from a previous batch + 1 safely.
The 'size' parameter is the number of comments to return in the The 'size' parameter is the number of comments to return in the
batch. batch.
The comments are returned in creation date order, in the exact batch The comments are returned in creation date order, in the exact batch
size specified. size specified.
""" """
def getThreads(start=0, size=None, root=0, depth=None): def getThreads(start=0, size=None, root=0, depth=None):
"""Return a batch of comment objects for rendering. """Return a batch of comment objects for rendering.
The 'start' parameter is the id of the comment from which to start The 'start' parameter is the id of the comment from which to start
the batch. If no such comment exists, the next higher id will be used. the batch. If no such comment exists, the next higher id will be used.
This means that you can use max key from a previous batch + 1 safely. This means that you can use max key from a previous batch + 1 safely.
This should be a root level comment. This should be a root level comment.
The 'size' parameter is the number of threads to return in the The 'size' parameter is the number of threads to return in the
batch. Full threads are always returned (although you can stop batch. Full threads are always returned (although you can stop
consuming the iterator if you want to abort). consuming the iterator if you want to abort).
'root', if given, is the id of the comment to which reply 'root', if given, is the id of the comment to which reply
threads will be found. 'root' itself is not included. If not given, threads will be found. 'root' itself is not included. If not given,
all threads are returned. all threads are returned.
If 'depth' is given, it can be used to limit the depth of threads If 'depth' is given, it can be used to limit the depth of threads
returned. For example, depth=1 will return only direct replies. returned. For example, depth=1 will return only direct replies.
Comments are returned as an iterator of dicts with keys 'comment', Comments are returned as an iterator of dicts with keys 'comment',
the comment, 'id', the comment id, and 'depth', which is 0 for the comment, 'id', the comment id, and 'depth', which is 0 for
top-level comments, 1 for their replies, and so on. The list is top-level comments, 1 for their replies, and so on. The list is
@ -94,78 +94,78 @@ class IConversation(IIterableMapping):
class IReplies(IIterableMapping): class IReplies(IIterableMapping):
"""A set of related comments in reply to a given content object or """A set of related comments in reply to a given content object or
another comment. another comment.
Adapt a conversation or another comment to this interface to obtain the Adapt a conversation or another comment to this interface to obtain the
direct replies. direct replies.
""" """
def addComment(comment): def addComment(comment):
"""Adds a new comment as a child of this comment, and returns the """Adds a new comment as a child of this comment, and returns the
comment id that was assigned. The comment_id property on the comment comment id that was assigned. The comment_id property on the comment
will be set accordingly. will be set accordingly.
""" """
def __delitem__(key): def __delitem__(key):
"""Delete the comment with the given key. The key is a long id. """Delete the comment with the given key. The key is a long id.
""" """
class IComment(Interface): class IComment(Interface):
"""A comment. """A comment.
Comments are indexed in the catalog and subject to workflow and security. Comments are indexed in the catalog and subject to workflow and security.
""" """
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"Conversation"), schema=Interface) __parent__ = schema.Object(title=_(u"Conversation"), schema=Interface)
__name__ = schema.TextLine(title=_(u"Name")) __name__ = schema.TextLine(title=_(u"Name"))
comment_id = schema.Int(title=_(u"A comment id unique to this conversation")) 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) 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"))
mime_type = schema.ASCIILine(title=_(u"MIME type"), default="text/plain") mime_type = schema.ASCIILine(title=_(u"MIME type"), default="text/plain")
text = schema.Text(title=_(u"Comment text")) text = schema.Text(title=_(u"Comment text"))
creator = schema.TextLine(title=_(u"Author name (for display)")) creator = schema.TextLine(title=_(u"Author name (for display)"))
creation_date = schema.Date(title=_(u"Creation date")) creation_date = schema.Date(title=_(u"Creation date"))
modification_date = schema.Date(title=_(u"Modification date")) modification_date = schema.Date(title=_(u"Modification date"))
# for logged in comments - set to None for anonymous # for logged in comments - set to None for anonymous
author_username = schema.TextLine(title=_(u"Author username"), required=False) author_username = schema.TextLine(title=_(u"Author username"), required=False)
# 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 ICommentingTool(Interface): class ICommentingTool(Interface):
"""A tool that indexes all comments for usage by the management interface. """A tool that indexes all comments for usage by the management interface.
This means the management interface can still work even though we don't This means the management interface can still work even though we don't
index the comments in portal_catalog. index the comments in portal_catalog.
The default implementation of this interface simply defers to The default implementation of this interface simply defers to
portal_catalog, but a custom version of the tool can be used to provide portal_catalog, but a custom version of the tool can be used to provide
an alternate indexing mechanism. an alternate indexing mechanism.
""" """
def indexObject(comment): def indexObject(comment):
"""Indexes a comment """Indexes a comment
""" """
def reindexObject(comment): def reindexObject(comment):
"""Reindex a comment """Reindex a comment
""" """
def unindexObject(comment): def unindexObject(comment):
"""Removes a comment from the indexes """Removes a comment from the indexes
""" """
def uniqueValuesFor(name): def uniqueValuesFor(name):
"""Get unique values for FieldIndex name """Get unique values for FieldIndex name
""" """
def searchResults(REQUEST=None, **kw): def searchResults(REQUEST=None, **kw):
"""Perform a search over all indexed comments. """Perform a search over all indexed comments.
""" """

View File

@ -0,0 +1,20 @@
<?xml version="1.0"?>
<object
name="portal_controlpanel"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="plone.app.discussion"
purge="False">
<configlet
title="Discussion"
action_id="discussion"
appId="plone.app.discussion"
category="Products"
condition_expr=""
url_expr="string:${portal_url}/@@discussion-settings"
visible="True"
i18n:attributes="title">
<permission>Manage portal</permission>
</configlet>
</object>

View File

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<registry>
<records interface="plone.app.discussion.interfaces.IDiscussionSettings" />
</registry>

View File

@ -27,14 +27,16 @@ setup(name='plone.app.discussion',
'setuptools', 'setuptools',
'collective.autopermission', 'collective.autopermission',
'collective.testcaselayer', 'collective.testcaselayer',
'plone.app.registry',
'plone.indexer', 'plone.indexer',
'plone.registry',
'plone.z3cform',
'ZODB3', 'ZODB3',
'zope.interface', 'zope.interface',
'zope.component', 'zope.component',
'zope.annotation', 'zope.annotation',
'zope.event', 'zope.event',
'zope.app.container', # XXX: eventually should change to zope.container 'zope.app.container', # XXX: eventually should change to zope.container
'plone.indexer',
], ],
entry_points=""" entry_points="""
[z3c.autoinclude.plugin] [z3c.autoinclude.plugin]