root/trunk/Discussable.py

Revision 3193, 12.0 kB (checked in by jukka, 2 weeks ago)

Fixed #2046, comments and commenting is included into main view.

  • Property svn:executable set to *
Line 
1 # Copyright 2006 by the LeMill Team (see AUTHORS)
2 #
3 # This file is part of LeMill.
4 #
5 # LeMill is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # LeMill is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with LeMill; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19 from Products.Archetypes.public import registerType
20 from AccessControl import ClassSecurityInfo
21 from Globals import InitializeClass, Persistent, PersistentMapping
22 from permissions import ADD_CONTENT_PERMISSION, ModerateContent
23 from Products.CMFCore.utils import getToolByName
24 from OFS.SimpleItem import SimpleItem
25 from DateTime.DateTime import DateTime
26 from Acquisition import Implicit, aq_base, aq_inner, aq_parent
27 from OFS.Traversable import Traversable
28 from config import to_unicode
29
30
31
32 class Discussable:
33     """Mix-in class for enabling comments"""
34
35     security = ClassSecurityInfo()
36
37     def isDiscussable(self):
38         """ yes we are """
39         return True
40
41     def getDiscussion(self, do_create=True):
42         """ Returns dictionary of discussion items """
43         talkback=getattr(self.aq_base, 'talkback', None)
44         if not talkback:
45             if do_create:
46                 self.talkback=DiscussionContainer()
47             else:
48                 return {}
49         elif not isinstance(self.talkback, DiscussionContainer):
50             self_destruct=self.convertPloneCommentsToLeMill()
51             # This blogpost has had its comments moved to a resource and it has been deleted
52             if self_destruct:
53                 return {}
54         return self.talkback.get()
55
56     def getSortedDiscussion(self):
57         """ Returns a list of discussion items sorted by their timecode """
58         talkback=self.getDiscussion()
59         return [value for key,value in sorted(talkback.items())]
60
61     def postCount(self):
62         """ Returns n of comments (CATALOG METADATA) """
63         return len(self.getDiscussion())
64
65     def getDiscussionURL(self):
66         """ Returns url for page that displays comments """
67         return self.absolute_url()+'/view'
68
69     def getCommentURL(self, comment_id):
70         """ Returns url for a certain comment id, validates if it looks proper """
71         if comment_id.isdigit():
72             return '#'.join((self.getDiscussionURL(), comment_id))
73         else:
74             return self.getDiscussionURL()
75
76     def getLastComment(self):
77         """Returns the latest comment"""
78         comments=self.getSortedDiscussion()
79         if comments:               
80             return comments[-1]
81
82     def getLastCommentDate(self):
83         """ Returns either date of last comment or modification time (CATALOG METADATA)"""
84         last=self.getLastComment()
85         edit_date = self.getLatestEditDate()
86         if last:
87             if last.CreationDate()>edit_date:
88                 return last.CreationDate()
89         return edit_date
90
91     def getTimeToLastComment(self, seconds=False):
92         """ Return time to latest comment in dictionary of days, hours, minutes (or seconds for sorting) """       
93         letool=getToolByName(self,'lemill_tool')
94         return letool.getTimeDifference(self.getLastCommentDate(), seconds)
95
96     # form: discussion.pt
97     security.declareProtected(ADD_CONTENT_PERMISSION, 'createReplyFromForm')
98     def createReplyFromForm(self, REQUEST):
99         """ Get message from a reply form and create it """
100         message=REQUEST.get('body_text', '')
101         if not message:
102             return
103         author=getToolByName(self, 'lemill_usertool').getAuthenticatedId()
104         date = DateTime().timeTime()
105         self.addReply(message, author, date)
106         if author!=self.Creator():
107             self.notifyResourceDiscussed()
108         self.reindexObject()
109         return REQUEST.RESPONSE.redirect(self.getCommentURL(str(int(date))))
110
111        
112     security.declareProtected(ADD_CONTENT_PERMISSION, 'addReply')
113     def addReply(self, message, author, date):
114         """ Create a new discussion item and add it to list """
115         reply=DiscussionObject(message, author, date, parent=self)
116         key=reply.getId()
117         self.talkback.set(key, reply)
118    
119     security.declarePrivate('_deleteReply')
120     def _deleteReply(self, key):
121         self.talkback.delete(key)
122
123     # form: discussion.pt
124     security.declareProtected(ModerateContent, 'deleteReplyFromForm')
125     def deleteReplyFromForm(self, REQUEST):
126         """ handles deletion requests """
127         id=REQUEST.get('delete_id', '')
128         if id:
129             self._deleteReply(id)
130         return REQUEST.RESPONSE.redirect(self.getDiscussionURL())
131
132     ################# RSS ######################################
133     # currently not used
134     def getRSSResults(self,max=30):
135         """ Return discussion comments, metadata returned:
136         'getURL', 'getLatestEdit', 'Title', 'listCreators','Rights' """
137         results=[]
138         url_base=self.getDiscussionURL()
139         title='re:%s' % self.Title()               
140         for reply in self.getSortedDiscussion()[:max]:
141             r_dict={'getURL':'#'.join((url_base, reply.id)), 'getLatestEdit':reply.created, 'Title':title, 'listCreators':[reply.Creator()],'Rights':'CC-SA 2.5'}
142             results.append(r_dict)
143         return results
144
145
146     ################ Notifications ##############################
147
148     # remember to suppress this for blogposts
149     security.declarePrivate('notifyResourceDiscussed')
150     def notifyResourceDiscussed(self):
151         lutool = getToolByName(self, 'lemill_usertool')
152         auth_mf = lutool.getMemberFolder()
153         mf = lutool.getMemberFolder(self.Creator())
154         if mf and mf.canNotify('resource_discussion_note'):
155             language=lutool.getCommunicationLanguage(mf)
156             dict={'name':to_unicode(auth_mf.getNicename()),
157                 'title':to_unicode(self.Title()),
158                 'url':to_unicode('%s/discussion' % self.absolute_url())}               
159             msg = self.translate("%(name)s has wrote a discussion note about your resource '%(title)s': %(url)s", domain="lemill", target_language=language) % dict
160             ltool = getToolByName(self, 'lemill_tool')
161             ltool.mailNotification(msg, recipient=mf.getEmail(), language=language)   
162
163
164     security.declarePrivate('notifyNewReply')
165     def notifyNewReply(self):
166         lutool = getToolByName(self, 'lemill_usertool')
167         auth_mf = lutool.getMemberFolder()
168         # This announcement can have multiple recipients
169         replies = context.getComments().values()
170         # Get all individuals to post in the thread
171         posters=set([r.Creator() for r in replies])
172         posters.remove(lutool.getAuthenticatedId())
173         if self.Creator() != lutool.getAuthenticatedId():
174             posters.add(self.Creator())
175         posters = [lutool.getMemberFolder(p) for p in posters]
176         # Check if the email sending is enabled
177         for recip in [p for p in posters if p and p.canNotify('group_message_posted')]:
178             language=lutool.getCommunicationLanguage(mf)
179             ltool = getToolByName(self, 'lemill_tool')
180             dict={'name':to_unicode(auth_mf.getNicename()),
181                 'title':to_unicode(self.Title()),
182                 'url':to_unicode(self.absolute_url())}               
183             msg = self.translate("%(name)s has posted a message in your group forum '%(title)s': %(url)s",domain="lemill",target_language=language) % dict
184             ltool.mailNotification(msg=msg, recipient=recip.getEmail(), language=language)
185
186
187     ######### Migration ###############################
188
189     security.declarePrivate('convertPloneCommentsToLeMill')
190     def convertPloneCommentsToLeMill(self):
191         print 'converting DiscussionTool comments to LeMill at %s' % self.getId()
192         dtool=getToolByName(self, 'portal_discussion')
193         rc=getToolByName(self, 'reference_catalog')
194         results=rc({ 'sourceUID': self.UID(), 'relationship': 'is_discussion_about' })
195         resource=None
196         self_destruct=False
197         if results:
198             try:
199                 resource=results[0].getObject().getTargetObject()
200             except:
201                 pass
202         if resource:
203             new_talkback=getattr(resource.aq_base, 'talkback', DiscussionContainer())
204             print "Moving comments to resource '%s' (%s)" % (resource.getId(), resource.portal_type)
205             self.talkback=DiscussionContainer()
206             self_destruct=True 
207         else:
208             new_talkback=DiscussionContainer()
209             resource=self
210         discussion=dtool.getDiscussionFor(self)
211         keys=[]
212         print discussion.objectIds()
213         for key, disc_item in discussion.objectItems():
214             new_id=str(key)
215             message=disc_item.text
216             date=disc_item.creation_date
217             timetime=date.timeTime()
218             author=disc_item.listCreators()[0]
219             new_object=DiscussionObject(message, author, timetime, parent=resource)
220             new_object.id=new_id
221             new_talkback.set(new_id, new_object)
222             print 'adding %s to %s' % (new_object, new_id)
223             keys.append(key)
224            
225         if len(new_talkback._container)<len(discussion._container):
226             raise 'New talkback is missing comments!'
227         for key in keys:
228             discussion.deleteReply(key)
229         resource.talkback=new_talkback
230              
231         if self_destruct:
232             blog=self.aq_parent
233             if hasattr(blog, 'recent_posts'):
234                 blog.removeRecent_post(self.getId())
235             blog.manage_delObjects([self.getId()])
236         return self_destruct
237
238
239 ############ DiscussionContainer, gives a persistent folder-like storage for discussion objects ##############
240
241 class DiscussionContainer( Persistent, Implicit, Traversable ):
242     """ Holds discussionobjects """
243
244     security = ClassSecurityInfo()
245
246     def __init__(self):
247         self.id = 'talkback'
248         self._container = PersistentMapping()
249
250     #security.declareProtected(View, 'getId')
251     def getId( self ):
252         return self.id
253
254     def get(self,key=None, else_value=None):
255         if not key:
256             return self._container
257         else:
258             return getattr(self._container, key, else_value)
259
260     def set(self, key, value):
261         self._container[key]=value
262        
263     def delete(self, key):
264         del self._container[key]
265
266 ############### DiscussionObjects are single comments ################
267         
268 class DiscussionObject(SimpleItem):
269     """ Comment object """
270     security = ClassSecurityInfo()
271     portal_type="Discussion Object"
272
273     def __init__(self, message='', author='', date='', parent=None):
274         self.id=str(int(date))
275         self.setBody(message, parent)
276         lutool=getToolByName(parent, 'lemill_usertool')
277         self.creators = [author or lutool.getAuthenticatedId()]
278         now = DateTime()
279         if date and not isinstance( date, DateTime ):
280             date=DateTime(date)
281         self.creation_date = date or now
282         self.modification_date = date or now
283         self.created=date or now       
284        
285     def Creator(self):
286         """ Public access to creator """
287         return self.creators and self.creators[0] or ''
288        
289     def getBodyText(self):
290         """ Comment body, parsed """
291         return self.bodyText
292
293     def getRawBodyText(self):
294         """ Comment body, raw """
295         return self.rawBodyText
296
297     def setBody(self, message, parent=None):       
298         lt=getToolByName(parent or self, 'lemill_tool')
299         self.rawBodyText=message
300         self.bodyText=lt.parse_text(message)
301
302     def CreationDate(self):
303         """ Public access to date """
304         return self.creation_date
305
306     def RSSDate(self):
307         """ In HTML4-format for RSS-feed """
308         return self.creation_date.HTML4()
309        
310 InitializeClass(Discussable)
Note: See TracBrowser for help on using the browser.