From 4729f0ef5c2609f9aefed1100c034f7cdfde09f0 Mon Sep 17 00:00:00 2001 From: helmutm Date: Mon, 8 Dec 2008 18:13:20 +0000 Subject: [PATCH] implement comments/discussion facility git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@3053 fd906abe-77d9-0310-91a1-e0d9ade77398 --- browser/common.py | 6 ++ browser/loops.css | 16 +++++ browser/node.py | 14 ++-- browser/resource_macros.pt | 8 ++- organize/comment/README.txt | 80 +++++++++++++++++++++++ organize/comment/base.py | 41 ++++++++++++ organize/comment/browser.py | 100 ++++++++++++++++++++++++++--- organize/comment/comment_macros.pt | 63 ++++++++++++++---- organize/comment/configure.zcml | 42 ++++++++++++ organize/comment/tests.py | 22 +++++++ organize/configure.zcml | 1 + organize/personal/configure.zcml | 11 ---- organize/tracking/configure.zcml | 25 ++++---- 13 files changed, 369 insertions(+), 60 deletions(-) create mode 100644 organize/comment/README.txt create mode 100644 organize/comment/base.py create mode 100644 organize/comment/configure.zcml create mode 100755 organize/comment/tests.py diff --git a/browser/common.py b/browser/common.py index 5b94498..46dc3c5 100644 --- a/browser/common.py +++ b/browser/common.py @@ -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"); ' diff --git a/browser/loops.css b/browser/loops.css index 12aa8a9..593549d 100644 --- a/browser/loops.css +++ b/browser/loops.css @@ -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; diff --git a/browser/node.py b/browser/node.py index 6246b24..139d942 100644 --- a/browser/node.py +++ b/browser/node.py @@ -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 diff --git a/browser/resource_macros.pt b/browser/resource_macros.pt index 40b69cb..66eac2e 100644 --- a/browser/resource_macros.pt +++ b/browser/resource_macros.pt @@ -15,6 +15,7 @@ The body + @@ -33,6 +34,7 @@ tal:define="linkText item/adapted/linkText|nothing" tal:content="python: linkText or 'more...'">more... + @@ -45,6 +47,7 @@ tal:attributes="src string:${view/url}/.target${view/targetId}/view?version=this" />

Description

+ @@ -80,8 +83,9 @@ tal:attributes="href string:$url/external_edit?version=this"> Open for editing - -

+ +

+ diff --git a/organize/comment/README.txt b/organize/comment/README.txt new file mode 100644 index 0000000..4ba1f39 --- /dev/null +++ b/organize/comment/README.txt @@ -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 + [] + >>> item = items[0] + >>> item.subject, item.timeStamp, item.userTitle + ('My comment', '... ...', u'john') + + +Fin de partie +============= + + >>> placefulTearDown() diff --git a/organize/comment/base.py b/organize/comment/base.py new file mode 100644 index 0000000..3ad956e --- /dev/null +++ b/organize/comment/base.py @@ -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' + diff --git a/organize/comment/browser.py b/organize/comment/browser.py index eb23523..8d8daa5 100644 --- a/organize/comment/browser.py +++ b/organize/comment/browser.py @@ -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 diff --git a/organize/comment/comment_macros.pt b/organize/comment/comment_macros.pt index 240e870..ceceae4 100644 --- a/organize/comment/comment_macros.pt +++ b/organize/comment/comment_macros.pt @@ -3,32 +3,67 @@ + tal:define="comments nocall:view/comments" + tal:condition="comments/allowed">

Comments

- - - + +

Resource Title

- John, + John, 2007-03-30

+ tal:content="structure comment/text" />

-
+
+ - \ No newline at end of file + +
+ + +
Add Comment
+
+ +
+ +
+
+
+
+ + +
+ + + + + diff --git a/organize/comment/configure.zcml b/organize/comment/configure.zcml new file mode 100644 index 0000000..757bdf3 --- /dev/null +++ b/organize/comment/configure.zcml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/organize/comment/tests.py b/organize/comment/tests.py new file mode 100755 index 0000000..c43ba30 --- /dev/null +++ b/organize/comment/tests.py @@ -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') diff --git a/organize/configure.zcml b/organize/configure.zcml index 499b27c..973f7e6 100644 --- a/organize/configure.zcml +++ b/organize/configure.zcml @@ -68,6 +68,7 @@ + diff --git a/organize/personal/configure.zcml b/organize/personal/configure.zcml index c946b74..935d454 100644 --- a/organize/personal/configure.zcml +++ b/organize/personal/configure.zcml @@ -4,17 +4,6 @@ xmlns="http://namespaces.zope.org/zope" i18n_domain="loops"> - - - - - - - diff --git a/organize/tracking/configure.zcml b/organize/tracking/configure.zcml index 6f7330a..edd6b5a 100644 --- a/organize/tracking/configure.zcml +++ b/organize/tracking/configure.zcml @@ -5,6 +5,17 @@ xmlns:browser="http://namespaces.zope.org/browser" i18n_domain="loops"> + + + + + + + @@ -74,20 +85,6 @@ title="View" action="index.html" /> - -