source: trunk/SectionFolder.py @ 575

Revision 575, 18.5 KB checked in by vahur, 13 years ago (diff)

closes #606 spent 1h
image manipulations

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 config import PROJECTNAME, SHOW_METADATA_FIELDS, TEMPLATES, to_unicode, MIMETYPE_WHITELIST, CONTENT_TYPES, FEATURED_TYPES
20from Products.Archetypes.public import *
21from Products.ATContentTypes.content.folder import ATFolder, ATFolderSchema
22from Products.Archetypes.public import registerType
23from Products.CMFCore.utils import getToolByName
24from ZPublisher.HTTPRequest import FileUpload
25from DocumentTemplate import sequence
26from random import sample
27from DateTime import DateTime
28import re, datetime
29from AccessControl import ClassSecurityInfo
30
31
32communityschema= ATFolderSchema + Schema((
33    LinesField('collaboration_proposals',
34        default= [],
35        widget = LinesWidget(
36            visible = {'view':'invisible', 'edit':'invisible'},
37            )
38        )
39
40))
41
42
43
44class SectionFolder(ATFolder):
45    """Section Folder"""
46       
47    archetype_name = "Section Folder"
48    meta_type = "Section Folder"
49    security = ClassSecurityInfo()
50   
51    # Override initializeArchetype to turn on syndication by default
52    def initializeArchetype(self, **kwargs):
53        ret_val = ATFolder.initializeArchetype(self, **kwargs)
54        # Enable topic syndication by default
55        syn_tool = getToolByName(self, 'portal_syndication', None)
56        if syn_tool is not None:
57            if syn_tool.isSiteSyndicationAllowed():
58                try:
59                    syn_tool.enableSyndication(self)
60                except: # might get 'Syndication Information Exists'
61                    pass
62        return ret_val
63
64    def _lemill_invokeFactory(self, container, meta_type, id=None, title=''):
65        """ add new object, edit it's title and invoke _renameAfterCreation """
66        if id is None:
67            id = self.generateUniqueId(meta_type)
68        new_id = container.invokeFactory(meta_type, id)
69        new = getattr(container, new_id)
70        new.edit(title = title)
71        renamed = new._renameAfterCreation()
72        if not renamed:
73            renamed = new_id
74        obj = getattr(container, renamed)
75        return obj
76
77    def start_new_version(self, REQUEST, objId):
78        """
79        Start a new version of an object. This is done by creating new object and
80        then, based on schema, all data is copyied from an object to new object.
81        objId - relative URL to object like /activities/demo_activity
82                or /activities/remote/<remote_server_address>/activities/demo_activity
83        """
84        portal_url = getToolByName(self, 'portal_url')
85        base_obj = portal_url.getPortalObject().restrictedTraverse(objId)
86        meta_type = base_obj.meta_type
87        folder_path = objId.split('/')
88        folder_url = '/'.join(folder_path[:-1])
89        folder = portal_url.getPortalObject().restrictedTraverse(folder_url)
90        #new_id = folder.invokeFactory(meta_type, id=meta_type.lower()+str(id(base_obj)))
91        #new = getattr(folder, new_id)
92        #new.edit(title=base_obj.Title())
93        new = self._lemill_invokeFactory(folder, meta_type, id=meta_type.lower()+str(id(base_obj)), title=base_obj.Title())
94        for k in base_obj.schema.keys():
95            # list all fields here that shouldn't be copyied to new object
96            if k in ['id', 'effectiveDate', 'expirationDate', 'creation_date', 'modification_date']:
97                continue
98            old_accessor = base_obj.schema[k].getEditAccessor(base_obj)
99            new_mutator = new.schema[k].getMutator(new)
100            if k == 'parentVersion':
101                new_mutator(base_obj.absolute_url())
102                continue
103            val = old_accessor()
104            new_mutator(val)
105        mess = 'New %s has been created for you!' % meta_type.lower()
106        REQUEST.set('portal_status_message', mess)
107        return new.base_edit(portal_status_message=mess)
108
109    def getMetadaFieldsToShowInEditor(self, object):
110        """ gets fields which are shown in metadata edit area """
111        type = object.meta_type
112        shownFieldsList = SHOW_METADATA_FIELDS[type]
113        shownFields = []
114        fields = object.Schemata()['metadata'].fields()
115        # At this point, the method can return a list of all metadata
116        if 'all' in shownFieldsList:
117            return fields
118        else:
119            for field in fields:
120                if field.getName() in shownFieldsList:
121                    shownFields.append(field)
122            return shownFields
123
124    def amIManager(self):
125        """Check whether I'm a manager."""
126        return 'Manager' in self.portal_membership.getAuthenticatedMember().getRoles()
127
128    def whoami(self):
129        return self.portal_membership.getAuthenticatedMember().getId()       
130
131    def getMember(self,uname=None):
132        if not uname:
133            uname=self.whoami()
134        try:
135            return getattr(self.community,uname)
136        except AttributeError:
137            return None
138       
139    def getSamples(self, search_results):
140        """ Pick three random objects from search results that have non-default images to display in folders front pages. """
141        pic_results = [x for x in search_results if x.meta_type in FEATURED_TYPES and x.getHasCoverImage and x.review_state=='public' and x.Title]
142        n=min(3,len(pic_results))
143        samples=sample(pic_results,n)
144        return samples
145
146    def makeSortable_title(self, title):
147        """Helper method partially copied from CMFPlone.CatalogTool """
148        putils= getToolByName(self, 'plone_utils')
149        def_charset = putils.getSiteEncoding()
150        num_sort_regex = re.compile('\d+')
151        sortabletitle = title.lower().strip()
152        # Replace numbers with zero filled numbers
153        sortabletitle = num_sort_regex.sub(lambda matchobj: matchobj.group().zfill(8), sortabletitle)
154        # Truncate to prevent bloat
155        for charset in [def_charset, 'latin-1', 'utf-8']:
156            try:
157                sortabletitle = unicode(sortabletitle, charset)[:30]
158                sortabletitle = sortabletitle.encode(def_charset or 'utf-8')
159                break
160            except UnicodeError:
161                pass
162            except TypeError:
163                # If we get a TypeError if we already have a unicode string
164                sortabletitle = sortabletitle[:30]
165                break
166        return sortabletitle
167
168       
169    def getTopResults(self, search_results, index_type):
170        """ Should return top three populated subgroups of search results in given index  """
171        pc = getToolByName(self, 'portal_catalog')
172        if index_type in pc.indexes() and index_type in pc.schema():
173            uniques = pc.uniqueValuesFor(index_type)
174            if uniques == ():
175                return []
176            hits={}
177            for a in uniques:
178                hits[a]=0
179            for counter in search_results:
180                if counter.review_state!='hidden':   
181                    values=getattr(counter, index_type)
182                    if isinstance(values,str):
183                         hits[values]=hits[values]+1
184                    else:
185                        try:
186                            for a in values:
187                                hits[a]=hits[a]+1
188                        except TypeError:
189                            pass # something wrong with data - let's continue without it
190            topthree= [(x[1],x[0]) for x in hits.items() if x[1]>0]
191            topthree.sort()
192            topthree.reverse()
193            topthree= topthree[:min(3,len(topthree))]
194            topthree=[x[1] for x in topthree]
195           
196            return topthree
197        else:
198            return []
199
200    def getUniques(self, search_results, index_type='Date'):
201        """ Should return unique values of search results in given index, results are list of index-metadata-like tuples in alphabetical order """
202        pc = getToolByName(self, 'portal_catalog')
203        uniquetuplelist=[]
204        maxcount=0
205        def adjust(i):
206            # helper method to adjust hit count of this tag to relative size (1,...,8)
207            (a,b,c,d,e)=i
208            if b>1:
209                b=int((8*b)/maxcount)
210            i=(a,b,c,d,e)
211            return i
212
213        # -- Step 1 -- : Make list of unique values for this index. For members and groups this is time to collect their urls and title.
214        if index_type in pc.indexes() and index_type!='getSortable_nicename' and index_type!='sortable_title':
215            uniques = pc.uniqueValuesFor(index_type)
216        elif index_type=='getSortable_nicename':
217            people = pc.searchResults(portal_type='MemberFolder')
218            uniques = [x.id for x in people]
219        elif index_type=='sortable_title':
220            groups = pc.searchResults(portal_type='GroupBlog')
221            uniques = [x.id for x in groups]
222        if uniques == ():
223            return []
224        hits={}
225
226        # -- Step 2 -- : Use list to make a dictionary. Values of list are keys.
227        for a in uniques:
228            hits[a]=(0,'','','') # value : (hits, objectURL, sort_title, title)
229
230        # -- Step 3 -- : Go through the search_results and every time a certain key is found from selected index, add a hit to counter under that key. With people and group names the counter gets hits from different sources.
231        if index_type not in pc.schema() or index_type=="getSortable_nicename":
232
233            if index_type=="sortable_title":
234                # Just get activity score from group metadata
235                for group in groups:
236                    hits[group.id]=(max(1, group.getActivity_score), group.getURL(), self.makeSortable_title(group.Title), group.Title)
237
238            elif index_type=="getSortable_nicename":
239                # Just get activity score from member metadata
240                for member in people:
241                    hits[member.id]=(max(1, member.getActivity_score), member.getURL(), member.getSortable_nicename, member.getNicename)
242            else:
243                return []
244        else:
245            # Vanilla hits counter
246            spent_urls=[]
247            for counter in search_results:
248                c_url=counter.getURL()
249                if counter.review_state!='hidden' and not c_url in spent_urls:
250                    values=getattr(counter, index_type)
251                    if values!=None:
252                        if type(values)==str or type(values)==int:
253                            hits[values]=(hits[values][0]+1, c_url, values, values)
254                        else:
255                            for a in values:
256                                hits[a]=(hits[a][0]+1, c_url, a, a)
257                    spent_urls.append(c_url)
258        # OK, the story so far: hits = dictionary, where keys are the distinct values of current index, or memberid:s
259        # values of this dictionary are tuples, where:
260        # value[0]= n of objects that have this value,
261        # value[1]= url-of-object that has this value (sensible only if there is one object with this value, like names for users),
262        # value[2]= title, nicer representation of key's name (title for group names and nicename for member names)
263        # value[3]= another_title, because sortable_nicename already takes the title place for members. ugly...
264
265        # -- Step 4 -- : Build a list from previous dictionary. Objects in list are tuples. Order list alphabetically.
266        # This dictionary should be ordered alphabetically by title.
267        uniquetuplelist=[(x[1][2],x[1][0],x[1][1],x[0],x[1][3]) for x in hits.items() if x[1][0]>0]       
268        # uniquetuplelist now contains dictionary reordered: (sort_title, count, url, indexvalue, title)
269        if uniquetuplelist==[]:
270            return uniquetuplelist
271        maxcount=max(map(lambda x: x[1], uniquetuplelist))
272        uniquetuplelist=map(adjust, uniquetuplelist)
273        uniquetuplelist.sort()
274        return uniquetuplelist
275
276    def getTagURL(self,context,category,value):
277        value=to_unicode(value)
278        return u"%s?%s=%s" % (context.absolute_url(),category,value)
279
280    def js_queryForPieces(self, keyword):
281        """ javascript is querying for pieces that are images """
282        result = []
283        stool = getToolByName(self, 'lemill_search')
284        q = {'SearchableText': keyword,
285             'portal_type': ['Piece', ]
286        }
287        q_results = stool.local_search(q)
288        for r in q_results:
289            if not r.getObject().isImage(): continue
290            tmp = [r.getObject().UID(), r.getId, to_unicode(r.Title).encode('iso-8859-15')]
291            result.append(tmp)
292        return str(result)
293
294    security.declarePublic('public_getSortCriterion')
295    def public_getSortCriterion(self,topic):
296        """ Topics have this useful getSortCriterion -method, but it's available only if allowed to change content."""
297        if topic.hasSortCriterion():
298            return topic.getSortCriterion().field
299        else:
300            return False
301
302class ContentFolder(SectionFolder):
303
304    archetype_name = "Content Folder"
305    meta_type = "Content Folder"
306
307    allowed_content_types = CONTENT_TYPES +('Topic',)
308    default_view = ('lemill_content_view')
309    filter_content_types = True
310
311    def askPublish(self, REQUEST):
312        """ask the user for publish the content, in the add_content_wizard"""
313        obj_id = REQUEST.get('id')
314        publish = REQUEST.get('publish')
315        keep = REQUEST.get('keep')
316        obj = getattr(self, obj_id)
317        if keep is None:
318            obj.content_status_modify(workflow_action='publish')
319            return REQUEST.RESPONSE.redirect(obj.absolute_url_path())
320        if publish is None:
321            return REQUEST.RESPONSE.redirect(obj.absolute_url_path())
322   
323    def uploadIt(self, REQUEST):
324        """ gets file from upload and makes new object.
325            also does some content-type whitelist checking here.
326        """
327        file = REQUEST.get('file')
328        user_title = REQUEST.get('user_title')
329        user_description = REQUEST.get('user_description')
330        content_type = file.headers['Content-Type']
331        type = ''
332        if content_type in MIMETYPE_WHITELIST:
333            type = 'Piece'
334       
335        if not type or type != 'Piece':
336            return REQUEST.RESPONSE.redirect('lemill_explain_upload_fail')
337       
338        new = self._lemill_invokeFactory(self, type, id=type+str(id(self)), title=user_title)
339        new.edit(description=user_description, file=file.read())
340        if type=='Piece':
341            new.edit(language='')
342        new_id = new.getId()
343        return REQUEST.RESPONSE.redirect(new.absolute_url()+'/piece_edit')
344        #return REQUEST.RESPONSE.redirect(self.absolute_url()+'/lemill_check_content?id='+new_id+'&type='+type)
345
346    def getTemplates(self):
347        return TEMPLATES
348
349    def getTemplate(self, template_id):
350        return TEMPLATES.get(template_id, None)
351       
352class ActivityFolder(SectionFolder):
353
354    archetype_name = "Activity Folder"
355    meta_type = "Activity Folder"
356
357    allowed_content_types = ('Activity','KB', 'Topic')
358    default_view = ('lemill_activities_view')
359    filter_content_types = True
360   
361class ToolFolder(SectionFolder):
362
363    archetype_name = "Tool Folder"
364    meta_type = "Tool Folder"
365
366    allowed_content_types = ('Tool', 'Topic')
367    default_view = ('lemill_tools_view')
368    filter_content_types = True
369
370class CommunityFolder(SectionFolder):
371
372    archetype_name = "Community Folder"
373    meta_type = "Community Folder"
374
375    allowed_content_types = ('Topic')
376    default_view = ('lemill_community_view')
377    filter_content_types = True
378
379    schema=communityschema
380   
381    def my_page(self):
382        """ Checks if user has MemberFolder and creates one if not. Returns the folder url."""
383        mtool = getToolByName(self, "portal_membership")
384        member=mtool.getAuthenticatedMember()
385        if member.getHomeFolder()==None:
386            member.createMemberarea()
387        folder=member.getHomeFolder()   
388        return folder.absolute_url()   
389
390    def getCollaboration_proposals(self):
391        """ Because I prefer lists, not tuples """
392        return list(self.getField('collaboration_proposals').get(self))
393       
394    def addCollaboration_proposal(self, obj_uid):
395        """ Collaboration proposals are stored as list of UID:s, because then we don't have to care about group path when finding them """
396        uid_cat = getToolByName(self, "uid_catalog")
397
398        def post_date(uid):
399            post=uid_cat(UID=uid)
400            post=post[0].getObject()
401            return post.creation_date                       
402
403        current_date = DateTime()
404        cp= self.getCollaboration_proposals()
405        cp=[obj_uid]+cp
406        # pop out old collaboration proposals from tail of the list
407        while post_date(cp[-1])< (current_date-31):
408            cp.pop()
409        cp=cp[:100]
410        cp_field=self.getField('collaboration_proposals')
411        cp_field.set(self, cp)
412
413    def removeCollaboration_proposal(self,obj_uid):
414        cp= self.getCollaboration_proposals()
415        if obj_uid in cp:
416            cp.remove(obj_uid)
417        cp_field=self.getField('collaboration_proposals')
418        cp_field.set(self, cp)
419
420    def mergeLatestPostsInMyGroups(self):
421        mtool = getToolByName(self, "portal_membership")
422        gtool = getToolByName(self, "portal_groups")
423        member=mtool.getAuthenticatedMember()
424        memberid=member.getId()
425        glist = self.lemill_usertool.getGroupsList(memberid);
426        recents=[]
427        for group in glist:
428            gname= group.getGroupName()
429            garea= gtool.getGroupareaFolder(gname)
430            grecent= garea.getRecent_posts()
431            for postid in grecent:
432                try:
433                    post=garea._getOb(postid)
434                    recents.append((post.Date,post))
435                except AttributeError:
436                    # do some cleaning:
437                    garea.removeRecent_post(postid)
438        recents.sort()
439        recents = [x[1] for x in recents]
440        return recents[:5]
441                           
442       
443       
444       
445registerType(ContentFolder, PROJECTNAME)
446registerType(ActivityFolder, PROJECTNAME)
447registerType(ToolFolder, PROJECTNAME)
448registerType(CommunityFolder, PROJECTNAME)
Note: See TracBrowser for help on using the repository browser.