source: trunk/GroupBlog.py @ 1919

Revision 1919, 15.6 KB checked in by jukka, 12 years ago (diff)

Refactored groups to not use portal_groups. Things should be faster and users from weird sources shouldn't cause so much problems. Not much tested yet, but archetype update and quickinstaller reinstall works fine.

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
19from SharedMetadata import description, tags
20from Products.PythonScripts.standard import url_quote
21from Products.Archetypes.public import *
22from Products.ATContentTypes.content.folder import ATFolder
23from Products.ATReferenceBrowserWidget.ATReferenceBrowserWidget import ReferenceBrowserWidget
24from Products.CMFCore.permissions import ModifyPortalContent
25from Products.Archetypes.public import BaseFolder, BaseFolderSchema, registerType
26from Products.Archetypes.atapi import DisplayList
27from Globals import InitializeClass
28from Products.CMFCore.utils import getToolByName
29from AccessControl import ClassSecurityInfo, Unauthorized
30from Products.LeMill import LeMillMessageFactory as _
31
32from config import PROJECTNAME, MODIFY_CONTENT, VIEW, DEFAULT_ICONS
33from permissions import MODIFY_CONTENT
34from SharedMetadata import *
35from Resources import CommonMixIn
36
37from DateTime import DateTime
38import datetime
39import operator
40
41import time
42
43# Same thing as with MemberFolder but easier: MemberBlogs contain the values of groups itself in indexable storage.
44
45schema = BaseFolderSchema + description + tags + coverImage + deletionReason + score + subject_area_schema + latest_edit_schema + Schema((
46    StringField('title',
47        default_method='prefill_title',
48        searchable = True,
49    ),
50    StringField('description',
51        default_method='prefill_description',
52        schemata='default',
53        widget=TextAreaWidget(
54            rows=5,
55            cols=40,
56            label='Description',
57            label_msgid='label_description',
58            )
59    ),
60
61    LinesField('language_skills',
62        default = [],
63        index = 'KeywordIndex:schema',
64        vocabulary = 'getLanguagelist',
65        widget = PicklistWidget(           
66             label = 'Languages',
67             label_msgid = 'label_group_language_skills',
68             description = "Choose languages that the group is willing to use. This affects only when people try to find groups based on a language.",
69             description_msgid = 'help_group_language_skills',
70             i18n_domain = "lemill"
71             )
72        ),       
73    LinesField('recent_posts',
74        default= [],
75        widget = LinesWidget(
76            visible = {'view':'invisible', 'edit':'invisible'},
77            )
78        ),
79    LinesField('groupMembers',
80        default=[],
81        index = 'KeywordIndex:schema',
82        widget = LinesWidget(
83            visible = {'view':'invisible', 'edit':'invisible'},
84            ),
85        ),
86
87 ))
88
89schema = schema.copy()
90schema['allowDiscussion'].widget.visible = {'edit':'invisible', 'view':'invisible'}
91schema['subject'].widget.visible = {'edit':'invisible', 'view':'invisible'}
92schema['contributors'].widget.visible = {'edit':'invisible', 'view':'invisible'}
93schema['creators'].widget.visible = {'edit':'invisible', 'view':'invisible'}
94schema['language'].widget.visible = {'edit':'invisible', 'view':'invisible'}
95schema['rights'].widget.visible = {'edit':'invisible', 'view':'invisible'}
96schema['tags'].schemata='default'
97schema['subject_area'].schemata='default'
98schema.moveField('subject_area', after='language_skills')
99schema['subject_area'].searchable = True
100
101
102class GroupBlog(BaseFolder,CommonMixIn):
103    """Group blog"""
104
105    meta_type = "GroupBlog"
106    archetype_name = "GroupBlog" 
107    typeDescription="Shared blog for a group"
108    typeDescMsgId='description_groupblog'
109    global_allow = 1
110
111    allowed_content_types = ('BlogPost')
112    default_view = ('groupblog_view')
113    filter_content_types = True
114    security = ClassSecurityInfo()
115    schema = schema
116
117    actions = ({ 'id':'view',
118                 'name': 'View',
119                 'action': 'string:${object_url}/groupblog_view',
120                 'permissions': (VIEW,)
121               },
122               { 'id':'edit',
123                 'name': 'Edit',
124                 'action': 'string:${object_url}/base_edit',
125                 'permissions': (MODIFY_CONTENT,)
126               },
127               { 'id':'edit_links',
128                 'name': 'Edit links',
129                 'action': 'string:${object_url}/base_metadata',
130                 'permissions': (MODIFY_CONTENT,)
131               },
132               { 'id':'edit_categories',
133                 'name': 'Edit categories',
134                 'action': 'string:${object_url}/base_metadata',
135                 'permissions': (MODIFY_CONTENT,)
136               },
137               )
138    aliases = {
139        '(Default)' : '',
140        'view'      : 'groupblog_view',
141        'edit'      : 'base_edit',
142        'edit_categories' : 'base_metadata',
143        'edit_links' : 'base_metadata',
144    }
145
146    def manage_afterAdd(self, item, container):
147        # Replaces the left side portlets with the content type's own action portlet.
148        BaseFolder.manage_afterAdd(self, item, container)
149        mtool = getToolByName(self, 'portal_membership')
150        memberfolder=mtool.getHomeFolder()
151        if memberfolder!=None and type(memberfolder)=='MemberFolder':
152            memberfolder.recalculateScore()
153        if not hasattr(item.aq_base, 'left_slots'):
154            self._setProperty('left_slots', ['here/portlet_%s_actions/macros/portlet' % item.meta_type.lower(),], 'lines')
155        # Enable topic syndication by default
156        syn_tool = getToolByName(self, 'portal_syndication', None)
157        if syn_tool is not None:
158            if syn_tool.isSiteSyndicationAllowed():
159                try:
160                    syn_tool.enableSyndication(self)
161                except: # might get 'Syndication Information Exists'
162                    pass
163
164
165    def at_post_edit_script(self):
166        mtool = getToolByName(self, 'portal_membership')
167        memberfolder=mtool.getHomeFolder()
168        if memberfolder!=None:
169            memberfolder.note_action(self.UID(), self.portal_type, 'post_edit')
170        self.recalculateScore()
171           
172    def getGroupMaterials(self, n=False):
173        """ Get resources edited by this group """
174        pc=getToolByName(self, 'portal_catalog')       
175        results=pc({'getRawGroupEditing':self.UID()})
176        if n:
177            return len(results)
178        else:
179            return results
180
181    def getGroupMembersNamesAndUrls(self):
182        memberids= self.getGroupMembers()
183        reslist=[]
184        for mid in memberids:
185            memberfolder=self.getMemberFolderById(mid)
186            if memberfolder:
187                reslist.append((memberfolder.NiceName(), memberfolder.absolute_url()))
188        return reslist
189
190    def getLatestEditDate(self):
191        """Returns creation date, we don't want to notify about every edit"""
192        return DateTime(self.CreationDate())
193
194    def getSamples(self):
195        """ get n number of samples """
196        all = self.getGroupMaterials()
197        all = [a for a in all if a.review_state=='public' and a.portal_type in self.getFeaturedTypes() and a.getHasCoverImage==True]
198        import random
199        n = min(3, len(all))
200        return random.sample(all,n)
201
202    def getCoverImageURL(self, drafts=False):
203        """Returns the URL for the cover image."""
204        timestamp = time.time()
205        if self.getField('hasCoverImage').get(self)==True:
206            return self.absolute_url()+'/coverImage?newest='+str(timestamp)
207        return 'images/default_group.png'
208       
209    def isPost(self):
210        return False       
211       
212    def NiceName(self):
213        """ To make GroupBlog compatible with MemberFolders """
214        return self.getTitle()
215
216    def getLanguagelist(self):
217        languagelist=self.availableLanguages()
218        return DisplayList(languagelist)
219
220    def isMember(self, memberid):
221        return memberid in self.getGroupMembers()
222
223    def canIModerate(self):
224        roles = self.portal_membership.getAuthenticatedMember().getRolesInContext(self)
225        return 'Manager' in roles or 'Reviewer' in roles
226       
227    def getPosts(self, batch=True, b_size=30, b_start=0):
228        """ Return get posts as full objects, sort them by date and batch them """
229        wtool = getToolByName(self,'portal_workflow')
230        results= [(obj.CreationDate(), obj) for obj in self.objectValues() if obj.portal_type=='BlogPost' and wtool.getInfoFor(obj,'review_state',None)!='deleted']
231
232        results.sort(key=operator.itemgetter(0))
233        results.reverse()
234        results =[obj for (d,obj) in results]
235        if batch:
236            from Products.CMFPlone import Batch
237            results = Batch(results, b_size, int(b_start), orphan=0)       
238        return results
239
240    def getFullForumRSS(self):
241        """ Returns forum topics and comments for them """
242        full_results=[]
243        wtool = getToolByName(self,'portal_workflow')
244        results= [(obj.CreationDate(), obj) for obj in self.objectValues() if obj.portal_type=='BlogPost' and wtool.getInfoFor(obj,'review_state',None)!='deleted']
245        results.sort(key=operator.itemgetter(0))
246        results.reverse()
247        results =[obj for (d,obj) in results]
248        for result in results:
249            full_results.append(result)
250            for x in result.getComments():
251                full_results.append(x)
252        return full_results
253
254    def getBlog(self):
255        return self
256       
257    security.declareProtected(MODIFY_CONTENT,'addRecent_post')
258    def addRecent_post(self,postid):
259        """ Recent posts are lists of id's, because then picking the post objects when inside group is easy and quick """
260        # sometimes this method gets called before new blogposts get their proper id:s, so escape then:
261        if postid.startswith('blogpost.'):
262            return None
263        postlist= list(self.getField('recent_posts').get(self))
264        if postid in postlist:
265            postlist.remove(postid)
266        postlist=[postid]+postlist
267        self.setRecent_posts(postlist[:10])           
268
269    security.declareProtected(MODIFY_CONTENT,'removeRecent_post')
270    def removeRecent_post(self,postid):
271        postlist= list(self.getField('recent_posts').get(self))
272        if postid in postlist:
273            postlist.remove(postid)
274        self.setRecent_posts(postlist)           
275
276    security.declareProtected(MODIFY_CONTENT,'replaceRecent_post')
277    def replaceRecent_post(self, old, new):
278        postlist= list(self.getField('recent_posts').get(self))
279        postlist[postlist.index(old)]=new
280        self.setRecent_posts(postlist)           
281
282#    def do_joining(self, userid, groupid):
283#        try:
284#            self.acl_users.source_groups.addPrincipalToGroup(userid, groupid)
285#        except:
286#            return 1
287#        return 0
288#
289#       
290#    def do_leaving(self, userid, groupid):
291#        try:
292#            self.acl_users.source_groups.removePrincipalFromGroup(userid, groupid)
293#            if len(self.getGroupMembers()) <= 0:
294#                for x in self.getGroupMaterials():
295#                    x.getObject().setGroupsShared('no_group')
296#                groupTool = getToolByName(self, 'portal_groups')
297#                groupTool.removeGroups([self.getId()])
298#        except:
299#            return 1
300#        return 0
301
302
303    def addMember(self, memberid):
304        mtool = getToolByName(self, 'portal_membership')
305        memberfolder=mtool.getHomeFolder(memberid)
306        if not memberfolder:
307            print "We have a problem: %s cannot be found and is not added to group" % memberid
308            return False
309        mid=memberfolder.getMemberId()
310        users=list(self.getGroupMembers())
311        if mid in users:
312            return False
313        self.setGroupMembers(users+[mid])
314        return True
315           
316    def removeMember(self, memberid):
317        mtool = getToolByName(self, 'portal_membership')
318        memberfolder=mtool.getHomeFolder(memberid)
319        if memberfolder:
320            mid=memberfolder.getMemberId()
321        else:
322            print "We have a problem: %s cannot be found. removing him from group anyway" % memberid
323            mid=memberid
324        users=list(self.getGroupMembers())
325        if mid not in users:
326            return False
327        users=users.remove(mid)
328        self.setGroupMembers(users)
329        return True     
330       
331       
332           
333    def join_group(self):
334        """ The logged in user joins the current group """
335        REQUEST=self.REQUEST
336        mtool = getToolByName(self, 'portal_membership')
337        putils= getToolByName(self, 'plone_utils')
338        user = mtool.getAuthenticatedMember()       
339        userid = user.getId()
340        if self.addMember(userid):
341            msg = _("You have joined the group '${title}'.", mapping={u'title' : self.title_or_id()})
342        else:
343            msg= _(u"You are already member in this group.")
344        putils.addPortalMessage(msg)
345        return REQUEST.RESPONSE.redirect(self.absolute_url())
346   
347    def leave_group(self):
348        """ The logged in user leaves the current group """
349        REQUEST=self.REQUEST
350        mtool = getToolByName(self, 'portal_membership')
351        putils= getToolByName(self, 'plone_utils')
352        user = mtool.getAuthenticatedMember()
353        userid = user.getId()
354        if self.removeMember(userid):
355            msg = _("You have left the group '${title}'.", mapping={u'title' : self.title_or_id()})
356        else:
357            msg= _(u"You are not member in this group.")
358        putils.addPortalMessage(msg)
359        return REQUEST.RESPONSE.redirect(self.community.absolute_url())
360
361 
362       
363
364    # These have to be duplicated since its difficult to base these folderish objects on non-folderish Resources
365
366    security.declareProtected(MODIFY_CONTENT,'setCoverImage')
367    def setCoverImage(self, value, **kwargs):
368        """ Normal mutator, but flags object to have a coverImage (hasCoverImage = True) """
369        cover=self.getField('coverImage')
370        cover.set(self,value,**kwargs)
371        has_cover=self.getField('hasCoverImage')
372        has_cover.set(self,True)
373        self.reindexObject()
374
375    security.declareProtected(MODIFY_CONTENT,'delCoverImage')
376    def delCoverImage(self):
377        """ Reverse of setCoverImage """
378        cover=self.getField('coverImage')
379        cover.set(self,None)
380        has_cover=self.getField('hasCoverImage')
381        has_cover.set(self,False)
382        self.reindexObject()
383
384
385    def recalculateScore(self):
386        """ Calculates the score for Group Blog according to specifications """
387        lt = getToolByName(self, 'lemill_tool')
388        score = float(0)
389        members_score = len(self.getGroupMembers())
390        materials = self.getGroupMaterials()
391        posts = self.getPosts(batch=False)
392
393        score = score + members_score
394
395        for m in materials:
396            months_old = lt.getTimeDifference(m.getLatestEdit)
397            months_old = months_old['months']
398            score = score + 1 / float(months_old + 1)
399
400        for p in posts:
401            months_old = lt.getTimeDifference(p.getLatestEditDate())
402            months_old = months_old['months']
403            score = score + 1 / float(months_old + 1)
404
405        score = int(round(score))
406
407        # This one is not really needed, because Group must have at least one member
408        if score<1:
409            score = 1
410        # Now to set the value for field score
411        self.setScore(score)
412
413
414       
415
416registerType(GroupBlog, PROJECTNAME)
Note: See TracBrowser for help on using the repository browser.