implement comments/discussion facility

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@3053 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2008-12-08 18:13:20 +00:00
parent a7312a0fa0
commit 4729f0ef5c
13 changed files with 369 additions and 60 deletions

View file

@ -561,6 +561,11 @@ class BaseView(GenericView, I18NView):
jsCall = 'dojo.require("dijit.form.ValidationTextBox");'
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
def registerDojoTextarea(self):
self.registerDojo()
jsCall = 'dojo.require("dijit.form.SimpleTextarea");'
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
def registerDojoEditor(self):
self.registerDojo()
jsCall = 'dojo.require("dijit.Editor");'
@ -574,6 +579,7 @@ class BaseView(GenericView, I18NView):
jsCall = ('dojo.require("dijit.form.Form"); '
'dojo.require("dijit.form.DateTextBox"); '
'dojo.require("dijit.form.TimeTextBox"); '
'dojo.require("dijit.form.SimpleTextarea"); '
'dojo.require("dijit.form.FilteringSelect"); '
'dojo.require("dojox.grid.DataGrid"); '
'dojo.require("dojo.data.ItemFileWriteStore"); '

View file

@ -40,6 +40,22 @@ blockquote ul {
margin-bottom: 0.3em;
}
.dialog div.heading {
font-weight: bold;
font-size: 140%;
margin: 0.5em 0 0.3em 0;
}
.dialog label {
display: block;
font-weight: bold;
margin-top: 0.5em;
}
.dialog div.buttons {
margin-top: 0.5em;
}
table.listing td {
white-space: normal;
vertical-align: middle;

View file

@ -60,7 +60,6 @@ from loops import util
from loops.util import _
from loops.browser.common import BaseView
from loops.browser.concept import ConceptView
from loops.organize.comment.browser import comment_macros
from loops.organize.tracking import access
from loops.versioning.util import getVersion
@ -485,19 +484,16 @@ class NodeView(BaseView):
# comments
@Lazy
def comment_macros(self):
from loops.organize.comment.browser import comment_macros
return comment_macros.macros
# better: provide a ``comments`` view on the target object.
@Lazy
def commentsAllowed(self):
return False
return True
def addCommentUrlFor(self, target):
return '#'
def comments(self):
return component.getMultiAdapter((self.context, self.request),
name='comments.html')
# inner HTML views

View file

@ -15,6 +15,7 @@
The body
</div>
</tal:body>
<metal:fields use-macro="view/comment_macros/comments" />
</div>
</metal:block>
@ -33,6 +34,7 @@
tal:define="linkText item/adapted/linkText|nothing"
tal:content="python: linkText or 'more...'">more...</a>
</b>
<metal:fields use-macro="view/comment_macros/comments" />
</div>
</metal:block>
@ -45,6 +47,7 @@
tal:attributes="src
string:${view/url}/.target${view/targetId}/view?version=this" />
<p><i tal:content="item/description">Description</i></p>
<metal:fields use-macro="view/comment_macros/comments" />
</div>
</metal:block>
@ -80,8 +83,9 @@
tal:attributes="href string:$url/external_edit?version=this">
Open for editing
</a>
</span>
</p>
</span>
</p>
<metal:fields use-macro="view/comment_macros/comments" />
</div>
</metal:block>

View file

@ -0,0 +1,80 @@
===============================================================
loops - Linked Objects for Organization and Processing Services
===============================================================
($Id$)
Let's do some basic setup
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True)
>>> from zope import component, interface
and set up a simple loops site with a concept manager and some concepts
(with all the type machinery, what in real life is done via standard
ZCML setup):
>>> from loops.organize.setup import SetupManager
>>> component.provideAdapter(SetupManager, name='organize')
>>> from loops.tests.setup import TestSite
>>> t = TestSite(site)
>>> concepts, resources, views = t.setup()
Comments - Managed by a Tracking Storage
========================================
>>> loopsRoot = concepts.getLoopsRoot()
>>> records = loopsRoot.getRecordManager()
More setup
----------
In order to be able to login and store favorites and other personal data
we have to prepare our environment. We need some basic adapter registrations,
and a pluggable authentication utility with a principal folder.
>>> from loops.organize.tests import setupObjectsForTesting
>>> setupData = setupObjectsForTesting(site, concepts)
>>> johnC = setupData.johnC
We also assign a document as a target to the home node so that we are able
to assign comments to this document.
>>> home = views['home']
>>> home.target = resources['d001.txt']
Creating comments
-----------------
>>> from loops.browser.node import NodeView
>>> from loops.tests.auth import TestRequest
>>> view = NodeView(home, TestRequest())
>>> from loops.organize.comment.browser import CreateComment
>>> input = dict(subject='My comment', text='Comment text')
>>> fc = CreateComment(view, TestRequest(form=input))
>>> fc.update()
False
Viewing comments
----------------
>>> from loops.organize.comment.browser import CommentsView, CommentView
>>> comments = CommentsView(home, TestRequest())
>>> items = list(comments.allItems())
>>> items
[<loops.organize.comment.browser.CommentView ...>]
>>> item = items[0]
>>> item.subject, item.timeStamp, item.userTitle
('My comment', '... ...', u'john')
Fin de partie
=============
>>> placefulTearDown()

41
organize/comment/base.py Normal file
View file

@ -0,0 +1,41 @@
#
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Base classes for comments/discussions.
$Id$
"""
from zope.component import adapts
from zope.interface import implements
from cybertools.tracking.btree import Track
from cybertools.tracking.interfaces import ITrackingStorage
from cybertools.tracking.comment.interfaces import IComment
from loops import util
class Comment(Track):
implements(IComment)
typeName = 'Comment'
contentType = 'text/restructured'

View file

@ -27,24 +27,104 @@ from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from cybertools.browser.action import actions
from cybertools.tracking.btree import TrackingStorage
from loops.browser.action import DialogAction
from loops.browser.common import BaseView
from loops.browser.form import ObjectForm, EditObject
from loops.browser.node import NodeView
from loops.organize.comment.base import Comment
from loops.organize.party import getPersonForUser
from loops.organize.tracking.browser import BaseTrackView
from loops.setup import addObject
from loops import util
from loops.util import _
comment_macros = ViewPageTemplateFile('comment_macros.pt')
actions.register('addComment', 'button', DialogAction,
title=_(u'Add Comment'),
description=_(u'Add a comment to this object.'),
viewName='create_comment.html',
dialogName='createComment',
innerForm='inner_comment_form.html',
#prerequisites=['registerDojoDateWidget'],
)
class CommentsView(NodeView):
def __init__(self, context, request):
self.context = context
self.request = request
@Lazy
def allowed(self):
return False
return True
@Lazy
def addUrl(self):
return '%s/create_comment.html' % self.nodeView.getUrlForTarget(self.context)
@Lazy
def addOnClick(self):
self.registerDojoFormAll()
return "objectDialog('createComment', '%s'); return false;" % self.addUrl
def allItems(self):
result = []
rm = self.loopsRoot.getRecordManager()
ts = rm.get('comments')
target = self.virtualTargetObject
if None in (ts, target):
return result
for tr in reversed(list(ts.query(taskId=util.getUidForObject(target)))):
view = CommentView(tr, self.request)
view.parent = self
result.append(view)
return result
class CommentsView(BaseView):
class CommentView(BaseTrackView):
pass
@Lazy
def subject(self):
return self.context.data['subject']
@Lazy
def text(self):
return self.parent.renderText(self.context.data['text'],
self.context.contentType)
class CreateCommentForm(ObjectForm):
template = comment_macros
@Lazy
def macro(self):
return self.template.macros['create_comment']
class CreateComment(EditObject):
@Lazy
def personId(self):
p = getPersonForUser(self.context, self.request)
if p is not None:
return util.getUidForObject(p)
return self.request.principal.id
@Lazy
def object(self):
return self.view.virtualTargetObject
def update(self):
form = self.request.form
subject = form.get('subject')
text = form.get('text')
if not subject or not text or self.personId is None or self.object is None:
return True
#contentType = form.get('contentType') or 'text/restructured'
rm = self.view.loopsRoot.getRecordManager()
ts = rm.get('comments')
if ts is None:
ts = addObject(rm, TrackingStorage, 'comments', trackFactory=Comment)
uid = util.getUidForObject(self.object)
ts.saveUserTrack(uid, 0, self.personId, dict(
subject=subject, text=text))
url = self.view.virtualTargetUrl + '?version=this'
self.request.response.redirect(url)
return False

View file

@ -3,32 +3,67 @@
<metal:comments define-macro="comments"
tal:condition="view/commentsAllowed">
tal:define="comments nocall:view/comments"
tal:condition="comments/allowed">
<br />
<h2 i18n:translate="">Comments</h2>
<div class="button">
<a i18n:translate=""
tal:attributes="href python: view.addCommentUrlFor(item.context)">Add Comment</a>
</div>
<tal:comments define="comments item/comments"
tal:condition="comments">
<tal:comment tal:repeat="comment comments">
<metal:comments define-macro="comments_listing"
tal:define="comments comments|nocall:view/comments;
items comments/allItems">
<tal:comment tal:repeat="comment items">
<br />
<div class="comment">
<h3>
<span tal:content="comment/subject">Resource Title</span></h3>
<div class="info">
<span tal:replace="comment/creator">John</span>,
<span tal:replace="comment/userTitle">John</span>,
<span tal:replace="comment/timeStamp">2007-03-30</span>
</div>
<p class="content"
tal:define="text comment/text;
contentType comment/contentType"
tal:content="structure python: view.renderText(text, contentType)" />
tal:content="structure comment/text" />
</div>
</tal:comment>
</tal:comments>
</metal:comments>
<div metal:define-macro="comments_button_add"
tal:define="comments comments|nocall:view/comments"
class="button">
<a i18n:translate=""
tal:attributes="href comments/addUrl;
onClick comments/addOnClick">Add Comment</a>
</div>
</metal:comments>
</html>
<metal:block define-macro="create_comment">
<form method="post"
id="addComment_form" class="dialog"
dojoType="dijit.form.Form">
<input type="hidden" name="form.action" value="create_comment" />
<input type="hidden" name="contentType" value="text/restructured" />
<div class="heading" i18n:translate="">Add Comment</div>
<div>
<label i18n:translate=""
for="comment_subject">Subject</label>
<div><input type="text" name="subject" id="comment_subject"
dojoType="dijit.form.ValidationTextBox" required="true"
style="width: 60em" /></div>
<label i18n:translate=""
for="comment_text">Comment</label>
<div>
<textarea name="text" cols="80" rows="6" id="comment_text"
dojoType="dijit.form.SimpleTextarea"
style="width: 60em"></textarea><div>
</div>
<div class="buttons">
<input value="Save" type="submit"
onClick="return closeDialog(true)"
i18n:attributes="value">
<input type="button" value="Cancel"
onClick="return closeDialog(false)"
i18n:attributes="value">
</div>
</form>
</metal:block>
</html>

View file

@ -0,0 +1,42 @@
<!-- $Id$ -->
<configure
xmlns:zope="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="loops">
<zope:class class="loops.organize.comment.base.Comment">
<require permission="zope.View"
interface="cybertools.tracking.comment.interfaces.IComment" />
<require permission="zope.ManageSite"
set_schema="cybertools.tracking.comment.interfaces.IComment" />
</zope:class>
<!-- views -->
<browser:page
name="comments.html"
for="loops.interfaces.INode"
class="loops.organize.comment.browser.CommentsView"
permission="zope.View" />
<browser:page
name="index.html"
for="cybertools.tracking.comment.interfaces.IComment"
class="loops.organize.comment.browser.CommentView"
permission="zope.View" />
<browser:page
name="create_comment.html"
for="loops.interfaces.INode"
class="loops.organize.comment.browser.CreateCommentForm"
permission="zope.View" />
<zope:adapter
name="create_comment"
for="loops.browser.node.NodeView
zope.publisher.interfaces.browser.IBrowserRequest"
factory="loops.organize.comment.browser.CreateComment"
permission="zope.View" />
</configure>

22
organize/comment/tests.py Executable file
View file

@ -0,0 +1,22 @@
# $Id$
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
class Test(unittest.TestCase):
"Basic tests for the loops.organize.comment package."
def testBasics(self):
pass
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
return unittest.TestSuite((
unittest.makeSuite(Test),
DocFileSuite('README.txt', optionflags=flags),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')

View file

@ -68,6 +68,7 @@
<!-- include -->
<include package=".browser" />
<include package=".comment" />
<include package=".job" />
<include package=".personal" />
<include package=".process" />

View file

@ -4,17 +4,6 @@
xmlns="http://namespaces.zope.org/zope"
i18n_domain="loops">
<class class="cybertools.tracking.btree.TrackingStorage">
<require permission="zope.View"
interface="zope.app.container.interfaces.IReadContainer" />
<require permission="zope.View"
interface="cybertools.tracking.interfaces.ITrackingStorage" />
<require permission="zope.ManageContent"
interface="zope.app.container.interfaces.IWriteContainer" />
<require permission="zope.ManageContent"
set_schema="cybertools.tracking.interfaces.ITrackingStorage" />
</class>
<class class="loops.organize.personal.favorite.Favorite">
<require permission="zope.View"
interface="loops.organize.personal.interfaces.IFavorite" />

View file

@ -5,6 +5,17 @@
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="loops">
<zope:class class="cybertools.tracking.btree.TrackingStorage">
<require permission="zope.View"
interface="zope.app.container.interfaces.IReadContainer" />
<require permission="zope.View"
interface="cybertools.tracking.interfaces.ITrackingStorage" />
<require permission="zope.ManageContent"
interface="zope.app.container.interfaces.IWriteContainer" />
<require permission="zope.ManageContent"
set_schema="cybertools.tracking.interfaces.ITrackingStorage" />
</zope:class>
<zope:class class="loops.organize.tracking.change.ChangeRecord">
<require permission="zope.View"
interface="loops.organize.tracking.change.IChangeRecord" />
@ -74,20 +85,6 @@
title="View"
action="index.html" />
<!--<zope:adapter
for="loops.organize.tracking.change.IChangeRecord
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.interface.Interface"
factory="loops.organize.tracking.browser.ChangeView"
permission="zope.View" />
<zope:adapter
for="loops.organize.tracking.access.IAccessRecord
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.interface.Interface"
factory="loops.organize.tracking.browser.AccessView"
permission="zope.View" />-->
<!-- application views -->
<browser:page