z3c.form form added to commentsviewlet (not fully functional yet).

svn path=/plone.app.discussion/trunk/; revision=28300
This commit is contained in:
Timo Stollenwerk 2009-08-01 21:47:50 +00:00
parent e9e2ab5331
commit 424d1c79ed
6 changed files with 748 additions and 10 deletions

View File

@ -0,0 +1,338 @@
<tal:block define="userHasReplyPermission view/can_reply;
isDiscussionAllowed view/is_discussion_allowed;
isAnonymousDiscussionAllowed view/anonymous_discussion_allowed;
isAnon view/is_anonymous;
canManage view/can_manage;
replies python:view.get_replies(canManage);
showCommenterImage view/show_commenter_image;
errors options/state/getErrors|nothing;
wtool context/@@plone_tools/workflow"
tal:condition="isDiscussionAllowed"
i18n:domain="plone">
<div class="reply"
tal:condition="python:isAnon and not isAnonymousDiscussionAllowed">
<form tal:attributes="action view/login_action">
<input class="standalone"
style="margin-bottom: 1.25em;"
type="submit"
value="Log in to add comments"
i18n:attributes="value label_login_to_add_comments;"
/>
</form>
</div>
<div class="discussion"
tal:condition="python:replies or (userHasReplyPermission and isDiscussionAllowed) or (isAnon and not userHasReplyPermission and isDiscussionAllowed)">
<tal:getreplies repeat="reply_dict replies">
<div class="comment"
tal:define="reply reply_dict/comment;
depth reply_dict/depth|python:0;
creator reply/Creator;
author_home_url python:view.get_commenter_home_url(username=reply.author_username);
portrait_url python:view.get_commenter_portrait(reply.author_username);
anonymous_creator python:creator in ('Anonymous User', '');
published python:wtool.getInfoFor(reply, 'review_state') == 'published';"
tal:attributes="class python:'comment replyTreeLevel'+str(depth);
style string:margin-left: ${depth}em;
id string:${reply/id}"
tal:condition="python:canManage or published">
<div class="commentImage" tal:condition="showCommenterImage">
<a href="" tal:condition="not:isAnon"
tal:attributes="href author_home_url">
<img src="defaultUser.gif"
alt=""
border="0"
width="75"
tal:attributes="src portrait_url;
alt reply/creator" />
</a>
<img src="defaultUser.gif"
alt=""
border="0"
width="75"
tal:condition="isAnon"
tal:attributes="src portrait_url;
alt reply/creator" />
</div>
<h3>
<a name="comments" tal:attributes="name reply/title">
<span tal:replace="reply/title">Comment title</span>
</a>
</h3>
<div class="documentByLine">
<tal:posted i18n:translate="label_comment_by">Posted by</tal:posted>
<tal:name>
<a href=""
tal:condition="not:isAnon"
tal:content="creator"
tal:attributes="href author_home_url">
Poster Name
</a>
<span tal:condition="isAnon"
tal:replace="creator" />
</tal:name>
<tal:name i18n:translate="label_anonymous_user"
condition="anonymous_creator">Anonymous User</tal:name>
<tal:at i18n:translate="label_commented_at">at</tal:at>
<span tal:replace="python:view.format_time(reply.modification_date)">8/23/2001 12:40:44 PM</span>
</div>
<div class="commentBody"
tal:content="structure reply/text">
This is the body text of the comment.
</div>
<button style="display: inline;"
class="context reply-to-comment-button allowMultiSubmit"
tal:condition="python:userHasReplyPermission and isDiscussionAllowed or isAnonymousDiscussionAllowed"
i18n:translate="label_reply;">
Reply
</button>
<form name="delete"
action=""
method="post"
style="display: inline;"
tal:condition="view/can_manage"
tal:attributes="action string:${reply/absolute_url}/@@moderate-delete-comment">
<input name="form.button.DeleteComment"
class="destructive"
type="submit"
value="Remove"
i18n:attributes="value label_remove;"
/>
</form>
<!-- Workflow actions (e.g. 'publish') -->
<form name=""
action=""
method="get"
style="display: inline;"
tal:repeat="action reply_dict/actions|nothing"
tal:attributes="action string:${reply/absolute_url}/@@moderate-publish-comment;
name action/id">
<input type="hidden" name="workflow_action" tal:attributes="value action/id" />
<input name="form.button.PublishComment"
class="context"
type="submit"
tal:attributes="value action/title"
/>
</form>
</div>
</tal:getreplies>
</div>
<div class="reply"
tal:condition="python: isAnon and not isAnonymousDiscussionAllowed and replies">
<form tal:attributes="action view/login_action">
<input class="standalone"
style="margin-bottom: 1.25em;"
type="submit"
value="Log in to add comments"
i18n:attributes="value label_login_to_add_comments;"
/>
</form>
</div>
<div id="commenting" class="reply"
tal:condition="not:isAnon">
<fieldset>
<legend i18n:translate="legend_add_comment">Add comment</legend>
<p i18n:translate="description_add_comment">
You can add a comment by filling out the form below. Plain text
formatting.
</p>
<form name="reply"
action=""
method="get"
tal:attributes="action string:${context/absolute_url}/++conversation++default/@@add-comment">
<div class="field"
tal:define="error errors/subject|nothing;"
tal:attributes="class python: error and 'field error' or 'field'">
<label for="subject" i18n:translate="label_subject">Subject</label>
<span class="fieldRequired" title="Required"
i18n:attributes="title title_required;"
i18n:translate="label_required">(Required)</span>
<div tal:content="error">Validation error output</div>
<input name="subject"
id="subject"
value=""
size="40"
tal:attributes="value request/subject|request/title_override|nothing;" />
</div>
<div class="field"
tal:define="error errors/body_text|nothing;"
tal:attributes="class python: error and 'field error' or 'field'">
<label for="body_text" i18n:translate="label_comment">Comment</label>
<span class="fieldRequired" title="Required"
i18n:attributes="title title_required;"
i18n:translate="label_required">(Required)</span>
<div tal:content="error">Validation error output</div>
<textarea name="body_text"
id="body_text"
cols="40"
rows="8"
tal:content="request/body_text|request/text_override | nothing"></textarea>
</div>
<div class="formControls">
<input class="context"
type="submit"
value="Add Comment"
name="form.button.AddComment"
i18n:attributes="value label_add_comment;" />
<input class="standalone cancelreplytocomment"
type="submit"
name="form.button.Cancel"
value="Cancel"
i18n:attributes="value label_cancel;" />
</div>
<input type="hidden" name="form.submitted" value="1" />
</form>
</fieldset>
</div>
<div id="commenting" class="reply"
tal:condition="python:isAnon and isAnonymousDiscussionAllowed">
<fieldset>
<legend i18n:translate="legend_add_comment">Add comment</legend>
<p i18n:translate="description_add_comment">
You can add a comment by filling out the form below. Plain text
formatting.
</p>
<form name="reply"
action=""
method="get"
tal:attributes="action string:${context/absolute_url}/++conversation++default/@@add-comment">
<div class="field"
tal:define="error errors/subject|nothing;"
tal:attributes="class python: error and 'field error' or 'field'">
<label for="author_username" i18n:translate="label_author_username">Author username</label>
<span class="fieldRequired" title="Required"
i18n:attributes="title title_required;"
i18n:translate="label_required">(Required)</span>
<div tal:content="error">Validation error output</div>
<input name="author_username"
id="author_username"
value=""
size="40"
tal:attributes="value request/subject|request/title_override|nothing;" />
</div>
<div class="field"
tal:define="error errors/subject|nothing;"
tal:attributes="class python: error and 'field error' or 'field'">
<label for="author_email" i18n:translate="label_author_email">Author email</label>
<span class="fieldRequired" title="Required"
i18n:attributes="title title_required;"
i18n:translate="label_required">(Required)</span>
<div tal:content="error">Validation error output</div>
<input name="author_email"
id="author_email"
value=""
size="40"
tal:attributes="value request/subject|request/title_override|nothing;" />
</div>
<div class="field"
tal:define="error errors/subject|nothing;"
tal:attributes="class python: error and 'field error' or 'field'">
<label for="subject" i18n:translate="label_subject">Subject</label>
<span class="fieldRequired" title="Required"
i18n:attributes="title title_required;"
i18n:translate="label_required">(Required)</span>
<div tal:content="error">Validation error output</div>
<input name="subject"
id="subject"
value=""
size="40"
tal:attributes="value request/subject|request/title_override|nothing;" />
</div>
<div class="field"
tal:define="error errors/body_text|nothing;"
tal:attributes="class python: error and 'field error' or 'field'">
<label for="body_text" i18n:translate="label_comment">Comment</label>
<span class="fieldRequired" title="Required"
i18n:attributes="title title_required;"
i18n:translate="label_required">(Required)</span>
<div tal:content="error">Validation error output</div>
<textarea name="body_text"
id="body_text"
cols="40"
rows="8"
tal:content="request/body_text|request/text_override | nothing"></textarea>
</div>
<div class="formControls">
<input class="context"
type="submit"
value="Add Comment"
name="form.button.AddComment"
i18n:attributes="value label_add_comment;" />
<input class="standalone cancelreplytocomment"
type="submit"
name="form.button.Cancel"
value="Cancel"
i18n:attributes="value label_cancel;" />
</div>
<input type="hidden" name="form.submitted" value="1" />
</form>
</fieldset>
</div>
</tal:block>

View File

@ -0,0 +1,285 @@
from datetime import datetime
from DateTime import DateTime
from urllib import quote as url_quote
from zope.interface import implements
from zope.component import createObject, getMultiAdapter, queryUtility
from zope.viewlet.interfaces import IViewlet
from Acquisition import aq_inner, aq_parent, aq_base
from AccessControl import getSecurityManager
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone import PloneMessageFactory as _
from Products.statusmessages.interfaces import IStatusMessage
from plone.registry.interfaces import IRegistry
from plone.app.layout.viewlets.common import ViewletBase
from plone.app.discussion.comment import CommentFactory
from plone.app.discussion.interfaces import IConversation, IComment, IReplies, IDiscussionSettings
class View(BrowserView):
"""Comment View
"""
def __call__(self):
# Redirect from /path/to/object/++conversation++default/123456789
# to /path/to/object#comment-123456789
comment_id = aq_parent(self).id
#self.request.response.redirect(aq_parent(aq_parent(aq_parent(self))).absolute_url() + '#comment-' + comment_id)
self.request.response.redirect(aq_parent(aq_parent(aq_parent(self))).absolute_url() + '#' + comment_id)
class CommentsViewlet(ViewletBase):
"""Discussion Viewlet
"""
implements(IViewlet)
template = ViewPageTemplateFile('comments.pt')
def update(self):
super(CommentsViewlet, self).update()
self.portal_discussion = getToolByName(self.context, 'portal_discussion', None)
self.portal_membership = getToolByName(self.context, 'portal_membership', None)
def can_reply(self):
return getSecurityManager().checkPermission('Reply to item', aq_inner(self.context))
def can_manage(self):
return getSecurityManager().checkPermission('Manage portal', aq_inner(self.context))
def is_discussion_allowed(self):
context = aq_inner(self.context)
conversation = IConversation(context)
return conversation.enabled
def get_replies(self, workflow_actions=False):
context = aq_inner(self.context)
conversation = IConversation(context)
def replies_with_workflow_actions():
# Return dict with workflow actions
#context = aq_inner(self.context)
wf = getToolByName(context, 'portal_workflow')
for r in conversation.getThreads():
comment_obj = r['comment']
# list all possible workflow actions
actions = [a for a in wf.listActionInfos(object=comment_obj)
if a['category'] == 'workflow' and a['allowed']]
r = r.copy()
r['actions'] = actions
yield r
# Return all direct replies
if conversation.total_comments > 0:
if workflow_actions:
return replies_with_workflow_actions()
else:
return conversation.getThreads()
else:
return None
def get_commenter_home_url(self, username):
if username is None:
return None
else:
return "%s/author/%s" % (self.context.portal_url(), username)
def get_commenter_portrait(self, username):
if username is None:
# return the default user image if no username is given
return 'defaultUser.gif'
else:
portal_membership = getToolByName(self.context, 'portal_membership', None)
return portal_membership.getPersonalPortrait(username).absolute_url();
def anonymous_discussion_allowed(self):
# Check if anonymous comments are allowed in the registry
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings)
return settings.anonymous_comments
def show_commenter_image(self):
# Check if showing commenter image is enabled in the registry
registry = queryUtility(IRegistry)
settings = registry.forInterface(IDiscussionSettings)
return settings.show_commenter_image
def is_anonymous(self):
return self.portal_state.anonymous()
def login_action(self):
return '%s/login_form?came_from=%s' % (self.navigation_root_url, url_quote(self.request.get('URL', '')),)
def format_time(self, time):
# We have to transform Python datetime into Zope DateTime
# before we can call toLocalizedTime.
util = getToolByName(self.context, 'translation_service')
zope_time = DateTime(time.year, time.month, time.day, time.hour, time.minute, time.second)
return util.toLocalizedTime(zope_time, long_format=True)
class AddComment(BrowserView):
"""Add a comment to a conversation
"""
def __call__(self):
if self.request.has_key('form.button.AddComment'):
context = aq_inner(self.context)
subject = self.request.get('subject')
text = self.request.get('body_text')
author_username = self.request.get('author_username')
author_email = self.request.get('author_email')
# Check the form input
if author_username == '':
IStatusMessage(self.request).addStatusMessage(\
_("Username field is empty."),
type="info")
return self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url())
if author_email == '':
IStatusMessage(self.request).addStatusMessage(\
_("Email field is empty."),
type="info")
return self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url())
if subject == '':
IStatusMessage(self.request).addStatusMessage(\
_("Subject field is empty."),
type="info")
return self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url())
if text == '':
IStatusMessage(self.request).addStatusMessage(\
_("Comment field is empty."),
type="info")
return self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url())
# The add-comment view is called on the conversation object
conversation = context
# Create the comment
comment = CommentFactory()
comment.title = subject
comment.text = text
portal_membership = getToolByName(context, 'portal_membership')
if portal_membership.isAnonymousUser():
comment.creator = author_username
comment.author_name = author_username
comment.author_email = author_email
comment.creation_date = comment.modification_date = datetime.now()
else:
member = portal_membership.getAuthenticatedMember()
fullname = member.getProperty('fullname')
if fullname == '' or None:
comment.creator = member.id
else:
comment.creator = fullname
comment.author_username = member.getUserName()
comment.author_name = member.getProperty('fullname')
comment.author_email = member.getProperty('email')
comment.creation_date = comment.modification_date = datetime.now()
# Add comment to the conversation
comment_id = conversation.addComment(comment)
# Redirect to comment (inside a content object page)
#self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url() + '#comment-' + str(comment_id))
self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url() + '#' + str(comment_id))
class ReplyToComment(BrowserView):
"""Reply to a comment
"""
def __call__(self):
context = aq_inner(self.context)
if self.request.has_key('form.button.AddComment'):
reply_to_comment_id = self.request.get('form.reply_to_comment_id')
subject = self.request.get('subject')
text = self.request.get('body_text')
author_username = self.request.get('author_username')
author_email = self.request.get('author_email')
# Check the form input
if author_username == '':
IStatusMessage(self.request).addStatusMessage(\
_("Username field is empty."),
type="info")
return self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url())
if author_email == '':
IStatusMessage(self.request).addStatusMessage(\
_("Email field is empty."),
type="info")
return self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url())
if subject == '':
IStatusMessage(self.request).addStatusMessage(\
_("Subject field is empty."),
type="info")
return self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url())
if text == '':
IStatusMessage(self.request).addStatusMessage(\
_("Comment field is empty."),
type="info")
return self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url())
# The add-comment view is called on the conversation object
conversation = context
# Fetch the comment we want to reply to
comment_to_reply_to = conversation.get(reply_to_comment_id)
replies = IReplies(comment_to_reply_to)
# Create the comment
comment = CommentFactory()
comment.title = subject
comment.text = text
portal_membership = getToolByName(context, 'portal_membership')
if portal_membership.isAnonymousUser():
comment.creator = author_username
comment.author_name = author_username
comment.author_email = author_email
comment.creation_date = comment.modification_date = datetime.now()
else:
member = portal_membership.getAuthenticatedMember()
fullname = member.getProperty('fullname')
if fullname == '' or None:
comment.creator = member.id
else:
comment.creator = fullname
comment.author_username = member.getUserName()
comment.author_name = member.getProperty('fullname')
comment.author_email = member.getProperty('email')
comment.creation_date = comment.modification_date = datetime.now()
# Add the reply to the comment
new_re_id = replies.addComment(comment)
# Redirect to comment (inside a content object page)
#self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url() + '#comment-' + str(reply_to_comment_id))
# Todo: Temporarily remove the "#comment-" to fix a bug
# in CMFPlone/skins/plone_ecmascript/form_tabbing.js
self.request.response.redirect(aq_parent(aq_inner(context)).absolute_url() + '#' + str(reply_to_comment_id))

View File

@ -153,6 +153,8 @@
formatting. formatting.
</p> </p>
<div tal:replace="structure view/contents" />
<form name="reply" <form name="reply"
action="" action=""
method="get" method="get"

View File

@ -1,3 +1,5 @@
### OLD ###
from datetime import datetime from datetime import datetime
from DateTime import DateTime from DateTime import DateTime
@ -28,6 +30,31 @@ from plone.app.layout.viewlets.common import ViewletBase
from plone.app.discussion.comment import CommentFactory from plone.app.discussion.comment import CommentFactory
from plone.app.discussion.interfaces import IConversation, IComment, IReplies, IDiscussionSettings from plone.app.discussion.interfaces import IConversation, IComment, IReplies, IDiscussionSettings
### NEW ###
from plone.app.layout.viewlets.common import ViewletBase
from zope.interface import Interface, implements
from zope.viewlet.interfaces import IViewlet
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from plone.z3cform import layout
from zope import interface, schema
from z3c.form import form, field, button, interfaces
from plone.z3cform.layout import wrap_form
from plone.z3cform import z2
from plone.app.discussion.interfaces import IComment
from zope.annotation import IAttributeAnnotatable
from plone.z3cform.fieldsets import extensible
from Products.Five.browser import BrowserView
class View(BrowserView): class View(BrowserView):
@ -41,19 +68,103 @@ class View(BrowserView):
#self.request.response.redirect(aq_parent(aq_parent(aq_parent(self))).absolute_url() + '#comment-' + comment_id) #self.request.response.redirect(aq_parent(aq_parent(aq_parent(self))).absolute_url() + '#comment-' + comment_id)
self.request.response.redirect(aq_parent(aq_parent(aq_parent(self))).absolute_url() + '#' + comment_id) self.request.response.redirect(aq_parent(aq_parent(aq_parent(self))).absolute_url() + '#' + comment_id)
class CommentsViewlet(ViewletBase):
"""Discussion Viewlet class Comment(object):
""" implements(IComment, IAttributeAnnotatable)
portal_type = u""
#__parent__ = u""
#__name__ = u""
comment_id = u""
in_reply_to = u""
title = u""
mime_type = u""
text = u""
creator = u""
creation_date = u""
modification_date = u""
author_username = u""
author_name = u""
author_email = u""
class CommentForm(extensible.ExtensibleForm, form.Form):
fields = field.Fields(IComment)
ignoreContext = True # don't use context to get widget data
label = u"Add a comment"
# NOT WORKING !!!
name = "foooooo"
method = "get"
action = "foo"
# hide certain fields
#fields['__parent__'].mode = interfaces.HIDDEN_MODE
#fields['__name__'].mode = interfaces.HIDDEN_MODE
fields['portal_type'].mode = interfaces.HIDDEN_MODE
fields['comment_id'].mode = interfaces.HIDDEN_MODE
fields['in_reply_to'].mode = interfaces.HIDDEN_MODE
fields['mime_type'].mode = interfaces.HIDDEN_MODE
fields['creator'].mode = interfaces.HIDDEN_MODE
fields['creation_date'].mode = interfaces.HIDDEN_MODE
fields['modification_date'].mode = interfaces.HIDDEN_MODE
fields['author_username'].mode = interfaces.HIDDEN_MODE
fields['author_name'].mode = interfaces.HIDDEN_MODE
fields['author_email'].mode = interfaces.HIDDEN_MODE
@button.buttonAndHandler(u'Post comment')
def handleApply(self, action):
data, errors = self.extractData()
print data['title'] # ... or do stuff
#@property
#def fields(self):
# TODO !!!
# return fields
class ViewletFormWrapper(ViewletBase, layout.FormWrapper):
implements(IViewlet) implements(IViewlet)
template = ViewPageTemplateFile('comments.pt') form = CommentForm
label = 'Add Comment'
name = 'foo'
formname = 'bar'
index = ViewPageTemplateFile('comments.pt')
#def index(self):
# return ViewPageTemplateFile('comments.pt').__of__(self)(self)
def __init__(self, context, request, view, manager):
super(ViewletFormWrapper, self).__init__(context, request, view, manager)
if self.form is not None:
self.form_instance = self.form(self.context.aq_inner, self.request)
self.form_instance.__name__ = self.__name__
def update(self):
super(CommentsViewlet, self).update()
self.portal_discussion = getToolByName(self.context, 'portal_discussion', None) self.portal_discussion = getToolByName(self.context, 'portal_discussion', None)
self.portal_membership = getToolByName(self.context, 'portal_membership', None) self.portal_membership = getToolByName(self.context, 'portal_membership', None)
#def contents(self):
# """This is the method that'll call your form. You don't
# usually override this.
# """
# # A call to 'switch_on' is required before we can render
# # z3c.forms within Zope 2.
# z2.switch_on(self, request_layer=self.request_layer)
# return self.render_form()
def render_form(self):
#z2.switch_on(self, request_layer=self.request_layer)
# XXX: NOT WORKING !!!
self.form_instance.formname = "foo"
self.form_instance.name = "foooooo"
self.form_instance.method = "get"
self.form_instance.action = "foo"
self.form.update(self.form_instance)
return self.form.render(self.form_instance)
#return self.form_instance()
# view methods
def can_reply(self): def can_reply(self):
return getSecurityManager().checkPermission('Reply to item', aq_inner(self.context)) return getSecurityManager().checkPermission('Reply to item', aq_inner(self.context))
@ -133,6 +244,9 @@ class CommentsViewlet(ViewletBase):
zope_time = DateTime(time.year, time.month, time.day, time.hour, time.minute, time.second) zope_time = DateTime(time.year, time.month, time.day, time.hour, time.minute, time.second)
return util.toLocalizedTime(zope_time, long_format=True) return util.toLocalizedTime(zope_time, long_format=True)
CommentsViewlet = wrap_form(CommentForm, __wrapper_class=ViewletFormWrapper)
class AddComment(BrowserView): class AddComment(BrowserView):
"""Add a comment to a conversation """Add a comment to a conversation
""" """

View File

@ -58,9 +58,8 @@
name="plone.comments" name="plone.comments"
for="Products.CMFCore.interfaces.IContentish" for="Products.CMFCore.interfaces.IContentish"
layer="..interfaces.IDiscussionLayer" layer="..interfaces.IDiscussionLayer"
view="plone.app.layout.globals.interfaces.IViewView" view="plone.app.layout.globals.interfaces.IViewView"
manager="plone.app.layout.viewlets.interfaces.IBelowContent" manager="plone.app.layout.viewlets.interfaces.IBelowContent"
template="comments.pt"
class=".comments.CommentsViewlet" class=".comments.CommentsViewlet"
permission="zope2.View" permission="zope2.View"
/> />

View File

@ -133,8 +133,8 @@ class IComment(Interface):
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)