source: trunk/LargeSectionFolder.py @ 3018

Revision 3018, 45.5 KB checked in by jukka, 10 years ago (diff)

Refactored adding pieces to webpages to use jQuery. Nothing dramatic yet, mut easier to expand than previous one.

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 *
20from Products.Archetypes.public import *
21from Products.ATContentTypes.content.folder import ATFolder, ATBTreeFolderSchema, ATBTreeFolder
22from Products.Archetypes.public import registerType
23from Products.CMFDefault.exceptions import DiscussionNotAllowed
24from Products.CMFCore.utils import getToolByName
25from Products.CMFPlone.utils import transaction_note
26from ZPublisher.HTTPRequest import FileUpload
27from DocumentTemplate import sequence
28from random import sample, shuffle
29from DateTime import DateTime
30import re, datetime, urllib
31from AccessControl import ClassSecurityInfo
32from permissions import MODIFY_CONTENT, ADD_CONTENT_PERMISSION, ModerateContent
33from messagefactory_ import i18nme as _
34from CommonMixIn import CommonMixIn
35from Products.ZCatalog.Lazy import Lazy
36
37import time,copy,logging,transaction
38
39LOG = logging.getLogger('Zope.ZCatalog')
40
41# This is a hack since I can't figure a way to get the class object of our content types before creating an instance of that class. So we have a dictionary we can use instead.
42DEFAULT_LOCATIONS = {
43    'Piece':'content/pieces',
44    'LeMillReference':'content/references',
45    'LeMillPrintResource':'content/printresources',
46    'MultimediaMaterial':'content/webpages',
47    'ExerciseMaterial':'content/exercises',
48    'PILOTMaterial':'content/pilots',
49    'PresentationMaterial':'content/presentations',
50    'LessonPlan':'content/lessonplans',
51    'SchoolProjectMaterial':'content/schoolprojects',
52    'Activity':'methods',
53    'Tool':'tools',
54    'MemberFolder':'community/people',
55    'GroupBlog':'community/groups'
56    }
57
58
59communityschema= ATBTreeFolderSchema + Schema((
60    LinesField('collaboration_proposals',
61        default= [],
62        widget = LinesWidget(
63            visible = {'view':'invisible', 'edit':'invisible'},
64            )
65        )
66
67))
68
69
70class LeMillFolder:
71    """ Methods common to all folderish objects """
72
73    security = ClassSecurityInfo()
74
75    #security.declareProtected(ADD_CONTENT_PERMISSION, 'invokeFactory')
76    def invokeFactory(self, type_name, id, RESPONSE=None, *args, **kw):
77        """ Checks that the path of object creation is reasonable,
78        then invokes the portal_types tool and finally sets state.
79        """
80        pt = getToolByName(self, 'portal_types')
81        id=pt.constructContent(type_name, self, id, RESPONSE, *args, **kw)
82        if id:
83            obj= getattr(self, id)
84            if obj.hasComplexWorkflow():
85                if obj.getHideDrafts():
86                    obj.setState('private')
87                else:
88                    obj.setState('draft')
89            else:
90                try:
91                    obj.setState('public')
92                except:
93                    pass
94            # Setting permissions to newly created object
95            # This should override the ones set by workflow
96            obj.setPermissions()
97        return id
98
99    def getDefaultPathFor(self, portal_type, as_list=False):
100        """ Returns the path that this object should have if it is in its right place (not trashed) """       
101        #print 'physical: %s' % '/'.join( self.getPhysicalPath() )
102        path= DEFAULT_LOCATIONS.get(portal_type, None)
103        if not path:
104            return None
105        path='/%s/%s' % (self.getPhysicalPath()[1],path)
106        #print 'default: %s' % path
107        if as_list:
108            return path.split('/')
109        else:
110            return path
111
112    def getFolderContents(self):
113        """ This shouldn't be needed """
114        return []
115       
116
117class LargeSectionFolder(CommonMixIn, LeMillFolder, ATBTreeFolder):
118    """Section Folder"""
119       
120    archetype_name = "Large Section Folder"
121    meta_type = "Large Section Folder"
122    security = ClassSecurityInfo()
123    isAnObjectManager = 1
124    global_allow= 1
125    portlet=''
126    subsections=[]
127
128
129
130    ############### Not found redirector #####################
131
132#    def __bobo_traverse__(self, REQUEST, entry_name=None):
133#        """ try looking into subfolder if traverse is otherwise failing """
134#        try:
135#            l = BaseObject.__bobo_traverse__(self, REQUEST, entry_name)
136#            return l
137#        except AttributeError, entry_name:
138#            print 'looking for %s' % entry_name
139#            raise AttributeError(entry_name)       
140#
141#
142#
143#    def __aabobo_traverse__(self, REQUEST, entry_name=None):
144#        """ try looking into subfolder if traverse is otherwise failing """
145#        try:
146#            l = BaseObject.__bobo_traverse__(self, REQUEST, entry_name)
147#        except AttributeError:
148#            print 'looking for %s' % entry_name
149#            l = self.lookInSubsections(entry_name)
150#            if l:
151#                l.REQUEST.RESPONSE.redirect(l.absolute_url(), status=301)
152#            else:
153#                raise AttributeError(entry_name)
154#        #if hasattr(l, 'im_func'):
155#        #    return l()
156#        return l
157
158   
159    def lookInSubsections(self, obj_id):
160        """ Check if subsection has this object and redirect if does """
161        for sub in self.__class__.subsections:
162            if sub!=self.getId():
163                sub=getattr(self, sub)
164                if sub.has_key(obj_id):
165                    return getattr(sub, obj_id)
166        raise AttributeError(obj_id)
167
168
169    ############### Misc. useful getters #####################
170
171
172    def fixMissingCatalogObjects(self, portal_type, start=0, end=10000):
173        """ check if cataloged uid matches with object uid """
174        pc = getToolByName(self, 'portal_catalog')
175        results = pc({'portal_type':portal_type})
176        start=int(start)
177        end=int(end)
178        c=0
179        f=0
180        for r in list(results):
181            c=c+1
182            if c<start:
183                continue
184            if c>end:
185                break
186            o=None
187            try:
188                o= r.getObject()
189            except:
190                f=f+1
191                print '%s failed to get object %s' % (str(c), r.getPath())
192                pc.uncatalog_object(r.getPath())
193            if o and o.UID()!=r.UID:
194                print "%s UID mismatch: object %s's uid is %s but metadata is %s" % (str(c), r.getId, o.UID(), r.UID)
195            else:
196                print ".",
197        print '%s failures out of %s catalog objects' % (str(f), str(c))
198
199    def getAllContentTypes(self):
200        """ returns a list of all content types """
201        return ALL_CONTENT_TYPES
202
203    def getMaterialTypes(self, no_references=False):
204        """ returns a list of material types """
205        if no_references:
206            return [x for x in MATERIAL_TYPES if x!='LeMillReference']
207        else:
208            return MATERIAL_TYPES
209
210    def getContentTypes(self):
211        """ returns a list of material types """
212        return CONTENT_TYPES
213
214    def getActivityTypes(self):
215        """ returns a list of activity types """
216        return ACTIVITY_TYPES
217
218    def getToolsTypes(self):
219        """ returns a list of activity types """
220        return TOOLS_TYPES
221
222    def getFeaturedTypes(self):
223        """ returns a list of featured types """
224        return MATERIAL_TYPES + ACTIVITY_TYPES + TOOLS_TYPES
225
226    def canDeleteOnCancel(self):
227        """ Should be called on resource contexts, so NO """
228        return False
229
230
231    def prettyMethodNameDictionary(self):
232        """ Helper method for finding displayable names for search fields """
233        # better to do this in config..
234        return FIELD_METHODS_DISPLAY_STRINGS                     
235
236
237    def getNameOfResourceType(self,restype, lower=True):
238        """Get human-readable name of resource - first try TYPE_NAMES, then TEMPLATES"""
239        if restype in TYPE_NAMES.keys():
240            if lower:
241                return TYPE_NAMES[restype][0].lower()
242            else:
243                return TYPE_NAMES[restype][0]
244        else:
245            for t in TEMPLATES.values():
246                if t['meta_type'] == restype:
247                    if t['title'].isupper():
248                        return t['title']
249                    else:
250                        return t['title'].lower()
251        return ""
252
253    def getSectionFolder(self, bottom=False):
254        """ return this or if bottom, then tries to look if there is a section folders beyond this """
255        if not bottom:
256            return self
257        if isinstance(self.aq_parent,LargeSectionFolder):
258            return self.aq_parent.getSectionFolder(bottom=True)
259        else:
260            return self
261
262
263    def getMetadataFieldsToShowInEditor(self, object):
264        """ gets fields which are shown in metadata edit area """
265        type = object.meta_type
266        shownFieldsList = SHOW_METADATA_FIELDS[type]
267        shownFields = []
268        fields = object.Schemata()['metadata'].fields()
269        # At this point, the method can return a list of all metadata
270        if 'all' in shownFieldsList:
271            return fields
272        else:
273            for field in fields:
274                if field.getName() in shownFieldsList:
275                    shownFields.append(field)
276            return shownFields
277
278    def amIManager(self):
279        """Check whether I'm a manager."""
280        lutool=getToolByName(self, 'lemill_usertool')       
281        roles = lutool.getAuthenticatedMember().getRolesInContext(self)
282        return 'Manager' in roles
283
284    def canIModerate(self):
285        lutool=getToolByName(self, 'lemill_usertool')       
286        roles = lutool.getAuthenticatedMember().getRolesInContext(self)
287        return 'Manager' in roles or 'Reviewer' in roles
288
289    def url_quote(self,word):
290        return urllib.quote(word)
291
292
293    def getDefaultIcon(self, meta_type, obj=None):
294        """ general method for getting proper icon for object, used when only catalog-metadata is available """       
295        address=DEFAULT_ICONS[meta_type]
296        if address!='piece':
297            return address
298        else:
299            try:
300                # getObject verified
301                obj=obj.getObject()
302                return obj.getDefaultIcon()
303            except (AttributeError, KeyError):
304                return DEFAULT_ICONS['Piece']
305
306
307    def getPortletPath(self):
308        """ Return a path to portlet, defined in object's class """
309        return self.__class__.portlet
310
311
312    def getFolderContents(self):
313        """ get contents by using catalog, faster than actual listing methods """
314        catalog = getToolByName(self,'portal_catalog')
315        results = catalog.searchResults(path="/".join(self.getPhysicalPath()), sort_on='modified', sort_order='reverse', getState="public")
316        return results
317
318
319    ################# Object creation ####################
320
321
322    # Fixes unicode ids causing problems with copypaste
323    def _checkId(self, id, allow_dup=0):
324        id=str(id)
325        ATBTreeFolder._checkId(self, id, allow_dup)
326       
327
328    # Override initializeArchetype to turn on syndication by default
329    security.declarePrivate('initializeArchetype')
330    def initializeArchetype(self, **kwargs):
331        ret_val = ATBTreeFolder.initializeArchetype(self, **kwargs)
332        # Enable topic syndication by default
333        syn_tool = getToolByName(self, 'portal_syndication', None)
334        if syn_tool is not None:
335            if syn_tool.isSiteSyndicationAllowed():
336                try:
337                    syn_tool.enableSyndication(self)
338                except: # might get 'Syndication Information Exists'
339                    pass
340        return ret_val       
341
342    security.declareProtected(ADD_CONTENT_PERMISSION,'createPiece')
343    def createPiece(self, container, id=None, file=None):
344        new=self.lemill_invokeFactory(container, 'Piece', id, do_create=True)
345        new.edit(file=file.read(), language='')
346        return new
347
348    security.declareProtected(ADD_CONTENT_PERMISSION,'lemill_invokeFactory')
349    def lemill_invokeFactory(self, container, meta_type, id=None, do_create=False):
350        """ add new object, edit it's title and invoke _renameAfterCreation """
351        ft=getToolByName(self, 'portal_factory')
352        if id is None:
353            id = self.generateUniqueId(meta_type)
354        path_as_list=self.getDefaultPathFor(meta_type, as_list=True)
355        if path_as_list and path_as_list != list(container.getPhysicalPath()):
356            container=self.restrictedTraverse(self.getDefaultPathFor(meta_type))       
357
358        if ft.getFactoryTypes().has_key(meta_type):
359            o = container.restrictedTraverse('portal_factory/' + meta_type + '/' + id)
360            message = None
361            transaction_note('Initiated creation of %s with id %s in %s' % (o.getTypeInfo().getId(), id, container.absolute_url()))
362        else:
363            new_id = container.invokeFactory(id=id, type_name=meta_type)
364            if new_id is None or new_id == '':
365               new_id = id
366            o=getattr(container, new_id, None)
367            tname = o.getTypeInfo().Title()
368            message = _(u'text_message_object_created', u'${tname} has been created.', mapping={u'tname' : tname})
369            transaction_note('Created %s with id %s in %s' % (o.getTypeInfo().getId(), new_id, container.absolute_url()))
370        if do_create: # finalizes object
371            o=ft.doCreate(o)
372        #print 'Created %s' % o
373        return o
374
375
376    def checkTitle(self, obj=None ,title='', objtype=''):
377        """ check if title is not used anywhere in not(deleted, redirector) object, return false if it is """
378        lt=getToolByName(self, 'lemill_tool')
379        return lt.checkTitle(self,obj=obj, title=title, objtype=objtype)
380
381
382
383
384    ##############         Resource conversion      ######################
385
386    security.declareProtected(ModerateContent, 'convertResource')
387    def convertResource(self, REQUEST):
388        """ Copy values of some fields to new object and redirect old references """
389        allowed_types={'LeMillReference':['content','references'],'Tool':'tools','Activity':'methods','MultimediaMaterial':['content','webpages'], 'ExerciseMaterial':['content','exercises']}
390        lt=getToolByName(self, 'lemill_tool')
391
392        # Check if we have everything we need and raise error if not
393        from_obj_id=REQUEST.get('from_obj_id', '')
394        to_obj_type=REQUEST.get('to_obj_type', '')
395        trial_run=REQUEST.get('trial_run', True)
396
397        if trial_run:
398            if trial_run=='False' or trial_run=='0':
399                trial_run=False
400
401        from_obj=getattr(self, from_obj_id,None)
402        if not from_obj:
403            raise "base object for resource conversion doesn't exist"
404
405        from_obj_type=from_obj.portal_type
406        if from_obj_type not in allowed_types.keys():
407            raise "cannot convert from type %s" % from_obj_type
408        if to_obj_type not in allowed_types.keys():
409            raise "cannot transfer to type %s" % to_obj_type
410
411        # Create to_obj
412        if to_obj_type in ['Tool','Activity']:
413            to_folder = getattr(self.aq_parent, allowed_types[to_obj_type])
414        else:
415            section = getattr(self.aq_parent, allowed_types[to_obj_type][0])
416            to_folder = getattr(section, allowed_types[to_obj_type][1])
417        to_obj = self.lemill_invokeFactory(to_folder, to_obj_type, do_create=True)
418
419        from_schema=from_obj.schema
420        to_schema=to_obj.schema
421         
422        import copy
423        ignored_fields=['translation', 'translation_of','id','groups']
424
425        # trial run, report fields that have content but cannot be converted
426        if trial_run:
427            problems=False
428            for k in from_schema.keys():
429                from_accessor = from_schema[k].getEditAccessor(from_obj)
430                if k not in ignored_fields and  k not in to_schema.keys() and from_accessor and from_accessor():
431                    #stupid exceptions
432                    if k=='address' and from_accessor()=='http://':
433                        continue
434                    if k=='address' and 'location' in to_schema.keys():
435                        continue
436                    if k=='location' and 'address' in to_schema.keys():
437                        continue
438                    if k=='video' and len(from_accessor())<5:
439                        continue
440                    problems=True
441                    lt.addPortalMessage(_('text_message_cannot_convert'), default='Cannot convert ${obj}.', mapping={'obj':k})
442            to_folder._delObject(to_obj.id)
443            if not problems:
444                lt.addPortalMessage(_('text_message_conversion_will_be_fine'), default='Checked conversion and it will be fine. Click \'Convert resource\' to do the conversion.')
445            if problems:
446                lt.addPortalMessage(_("text_message_conversion_will_have_problems"), default="Indicated fields have text that cannot be copied to other resource type. Click 'Convert resource' if you want to do the conversion anyway.")
447            return REQUEST.RESPONSE.redirect('%s/manage_convert?trial_run=False&from_obj_id=%s&to_obj_type=%s' % (from_obj.absolute_url(), from_obj.id, to_obj_type))
448
449
450        # Helper method for copying fields
451        def copyvalue(from_accessor, to_mutator, k):
452            if not from_accessor:
453                return
454            val = from_accessor()
455            if k=='bodyText':
456                if from_obj_type == 'ExerciseMaterial':
457                    if to_obj_type == 'MultimediaMaterial':
458                        new_val=[]
459                        for (chapter, c_type) in val:
460                            if c_type in ['choice','multiple_choices','fill_in_the_blanks','open_ended']:
461                                new_val.append((chapter, 'text_block'))
462                            else:
463                                new_val.append((chapter, c_type))
464                        val=new_val
465                    else:
466                        val=from_obj.getOnlyRawText()
467                elif from_obj_type == 'MultimediaMaterial':
468                    if to_obj_type == 'ExerciseMaterial':
469                        if val[0][0]=='text_block':
470                            val[0][0]='guidelines'
471                    else:
472                        val=from_obj.getOnlyRawText()
473            copied_val = None
474            try:
475                copied_val = copy.copy(val)
476            except TypeError:
477                copied_val = copy.copy(val.aq_base)
478            to_mutator(copied_val)
479
480        # Copy fields with same name
481        for k in from_schema.keys():
482            # list all fields here that shouldn't be copyied to new object
483            if k not in to_schema.keys() or k in ignored_fields:
484                continue
485            from_accessor = from_schema[k].getEditAccessor(from_obj)
486            to_mutator = to_schema[k].getMutator(to_obj)
487            copyvalue(from_accessor, to_mutator, k)
488
489        # Check convertable pairs of exceptions:
490        for (k,l) in [('address','location')]:
491            if k in from_schema.keys() and l in to_schema.keys():
492                from_accessor = from_schema[k].getEditAccessor(from_obj)
493                to_mutator = to_schema[l].getMutator(to_obj)
494                copyvalue(from_accessor, to_mutator, k)
495            elif l in from_schema.keys() and k in to_schema.keys():
496                from_accessor = from_schema[l].getEditAccessor(from_obj)
497                to_mutator = to_schema[k].getMutator(to_obj)
498                copyvalue(from_accessor, to_mutator, k)
499
500        # LeMillReferences use descriptions instead of bodytexts while they still have bodytexts:
501        if to_obj_type=='LeMillReference':
502            k='bodyText'
503            from_accessor=from_schema['bodyText'].getEditAccessor(from_obj)
504            to_mutator = to_schema['description'].getMutator(to_obj)
505            copyvalue(from_accessor, to_mutator, k)
506        if from_obj_type=='LeMillReference':
507            k='description'
508            from_accessor=from_schema['description'].getEditAccessor(from_obj)
509            to_mutator = to_schema['bodyText'].getMutator(to_obj)
510            copyvalue(from_accessor, to_mutator, k)
511
512
513        # Look for references to this object
514        rc=getToolByName(self, 'reference_catalog')
515        uc=getToolByName(self, 'uid_catalog')
516        results=rc({'targetUID':from_obj.UID()})
517
518        # Get object with certain reference and fix it
519        for refobj in results:
520            res=uc({'UID':refobj.sourceUID})
521            if res:
522                # getObject verified
523                obj=res[0].getObject()
524                if refobj.relationship in ['relatesToContent', 'relatesToMethods', 'relatesToTools']:
525                    obj.delete(from_obj)
526                if obj.portal_type=='Collection':
527                    obj.add(to_obj)
528
529        # Copy references to pieces
530        if from_obj_type in ['MultimediaMaterial','ExerciseMaterial'] and to_obj_type in ['MultimediaMaterial','ExerciseMaterial']:
531            results=rc({'sourceUID':from_obj.UID()})
532            for refobj in results:
533                rc.addReference(to_obj, refobj.targetUID, 'uses')       
534
535        # Copy history
536        from_history=from_obj.getHistory()
537        to_obj.setHistory(copy.copy(from_history))
538
539        # Run post_edits
540        to_obj.post_edit_rename()
541        to_obj.recalculateAuthors()
542        to_obj.recalculateScore()
543        to_obj.reindexObject()
544
545        # Delete old object
546        self._delObject(from_obj_id)
547
548        # Return new object
549        lt.addPortalMessage(_('Resource converted succesfully.'))       
550        if to_obj.portal_type in MATERIAL_TYPES:
551            return REQUEST.RESPONSE.redirect('%s/view' % to_obj.absolute_url())           
552        else:
553            return REQUEST.RESPONSE.redirect(to_obj.absolute_url())           
554
555       
556
557    def wakeLazy(self, lazy):
558        new=[]
559        for l in lazy._seq:
560            if isinstance(l, Lazy):
561                new.extend(self.wakeLazy(l))
562            else:
563                new.append(l)
564        return new
565
566
567
568
569    ################      Featured items      ###########################
570
571    def _getSamples(self, content_types):
572        lutool=getToolByName(self, 'lemill_usertool')
573        lt=getToolByName(self,'lemill_tool')
574        pc=getToolByName(self,'portal_catalog')
575        languages=lutool.getLanguages()
576        good_results=[]
577        while languages:
578            lang=languages.pop(0)
579            results=pc(portal_type=content_types, Language=lang, getState='public', getHasCoverImage=True, sort_on='getScore', sort_order='descending')
580            results=results[:30]
581            shuffle(results)
582            if len(results)>2:
583                return results[:3]
584            else:
585                good_results+=results
586            if len(good_results)>2:
587                return good_results[:3]
588        return good_results
589
590    security.declareProtected(MANAGE_PORTAL, 'rebuildTop3s')
591    def rebuildTop3s(self):
592        """ set values for sections' top 3 tags, languages etc. (do this daily, or weekly: very expensive) """
593        t=time.time()
594        section_toplists={
595            'content':['top3languages','top3subject_areas','top3target_groups','top3tags'],
596            'methods':['top3languages','top3tags'],
597            'tools':['top3languages','top3tags'],
598            'community':['top3language_skills','top3locations','top3skills','top3interests','top3subject_areas']               
599        }
600       
601        type_mapping = {'content':CONTENT_TYPES, 'methods':'Activity','tools':'Tool','community':'MemberFolder'} # Groups are handled separately         
602        category_mapping={'top3languages':'Language', 'top3subject_areas':'getSubject_area', 'top3target_groups':'getTarget_group',  'top3tags':'getTags', 'top3language_skills':'getLanguage_skills', 'top3locations':'getLocation_country', 'top3skills':'getSkills', 'top3interests':'getInterests', 'top3group_languages':'getLanguage_skills', 'top3group_tags':'getTags','top3group_subject_areas':'getSubject_area'}
603        pc=getToolByName(self, 'portal_catalog')
604        # Others
605        for section_name, toplist in section_toplists.items():
606            results=pc(portal_type=type_mapping[section_name], getState='public', getHasCoverImage=True)
607            results=self.wakeLazy(results)
608            section=getattr(self.aq_parent, section_name)
609            props={}
610            for top3_id in toplist:
611                props[top3_id]=self._getTopResults(results, category_mapping[top3_id])
612            section.manage_editProperties(props)
613        # Groups
614        results=pc(portal_type='GroupBlog', getState='public')
615        results=self.wakeLazy(results)
616        for top3_id in ['top3group_languages','top3group_tags','top3group_subject_areas']:
617            props[top3_id]=self._getTopResults(results, category_mapping[top3_id])
618        self.aq_parent.community.manage_editProperties(props)
619        return str(time.time()-t)       
620
621       
622    def _getTopResults(self, results, index_type):
623        pc = getToolByName(self, 'portal_catalog')
624        if index_type not in pc.indexes() or index_type not in pc.schema():
625            return []
626        res={}
627        index=pc._catalog.getIndex(index_type)
628        r=[]
629        for value in results:
630            value=index.getEntryForObject(value, "")
631            if value and (isinstance(value, list) or isinstance(value, tuple)):
632                for v in value:
633                    res[v]=res.get(v,0)+1
634            elif value:
635                res[value]=res.get(value,0)+1
636        topthree= [(n,key) for key, n in res.items() if n>1 and key]       
637        topthree.sort(reverse=True)
638        topthree=[key for n,key in topthree[:3]]
639        return topthree
640
641    ##############################      Clouds      ######################## 
642
643    def getTitleCloud(self, search_results, browse_type):
644        """ Build a cloud based on popularity score for that resource """
645        pc=getToolByName(self,'portal_catalog')
646        # uniquetuplelist contains result metadata reordered: (sort_title, count, url, indexvalue, title)
647        from math import log
648        if not search_results:
649            return []
650
651        def adjust(i):
652            # helper method to adjust hit count of this tag to relative size (1,...,8)
653            (a,b,c,d,e)=i
654            b=int((8*log(b,2))/log(maxscore,2))
655            if b==0:
656                b=1
657            i=(a,b,c,d,e)
658            return i       
659        def isDefaultTitle(x):
660            """ some heuristic to recognize default titles """
661            return re.match(r'.*\.(...)$', x) or re.match(r'.*\.(....)$', x)
662                   
663        popularity = pc.fastMetadata(search_results, ('getScore','rid','getNicename','sortable_title'))
664        popularity.sort(reverse=True)
665        popularity=popularity[:100]
666        titlecloud=[(sortable_title, getScore, self.REQUEST.physicalPathToURL(pc.getpath(rid)), sortable_title, getNicename or sortable_title) for (getScore, rid, getNicename, sortable_title) in popularity if sortable_title]
667       
668        if not titlecloud:
669            return []
670        titlecloud.sort()
671        maxscore=max([x[1] for x in titlecloud])
672        if maxscore>1:
673            titlecloud=map(adjust, titlecloud)
674        return titlecloud
675
676    def getTagCloud(self, search_results, index_type):
677        """ Build a cloud based on how many occurences of this item are in results """
678        if not search_results:
679            return []
680        lemill_tool = getToolByName(self, 'lemill_tool')
681        pc = getToolByName(self, 'portal_catalog')
682        from math import log
683        maxcount=0
684
685        def adjust(i):
686            # helper method to adjust hit count of this tag to relative size (1,...,8)
687            (a,b,c,d,e)=i
688            try:
689                b=int((8*log(b-mincount,2))/log(maxcount-mincount,2))
690            except (OverflowError, ZeroDivisionError):
691                b=0
692            if b==0:
693                b=1
694            i=(a,b,c,d,e)
695            return i
696        hits={}
697        hits=pc.fastCount(search_results, index_type)
698        resultlist=zip(hits.values(),hits.keys())
699        if not resultlist:
700            return []
701        resultlist.sort()
702        resultlist.reverse()
703        maxcount=resultlist[0][0] # first!
704        # if the first cut score for tag is x, we want to cut off all of the tags with score x.
705        if len(resultlist)>100:
706            #cutpoint = [x[0] for x in resultlist].index(resultlist[100]) can't figure this now, fix later
707            cutpoint = 100
708            resultlist = resultlist[:cutpoint]
709        mincount=resultlist[-1][0]
710        resultlist=[(x[1], x[0], '',x[1],x[1]) for x in resultlist]
711
712        # adjust to 1-8. We don't have to worry about score 0, they're already removed.
713        if maxcount>1:
714            resultlist=map(adjust, resultlist)
715        # prettify language names
716        if index_type=='Language' or index_type=='getLanguage_skills':
717            resultlist=[(x[0],x[1],x[2],x[3],lemill_tool.getPrettyLanguage(x[4])) for x in resultlist]           
718        if index_type=='getTarget_group':
719            def compfunc(t2,t1):
720                if t2[0] in TARGET_GROUP and t1[0] in TARGET_GROUP:
721                    return  TARGET_GROUP.index(t2[0]) - TARGET_GROUP.index(t1[0])
722                else:
723                    return -1
724            resultlist.sort(cmp=compfunc)
725        else:   
726            resultlist.sort()
727        return resultlist
728
729
730    def js_queryForPieces(self, keyword='', search_type='image'):
731        """ javascript is querying for pieces that are images """
732        # when this method is called by javascript, all arguments are packed to string 'keyword'
733        # typical value for keyword: 'foobar,audio=False'
734        result = []
735        q = {'SearchableText': keyword,
736             'portal_type': ['Piece', ],
737             'getState': 'public',
738        }
739        if search_type=='image' or search_type=='cover_image':
740            q['getPiece_type']='image'
741        elif search_type=='audio':
742            q['getPiece_type']='audio'
743
744        ltool = getToolByName(self, 'lemill_tool')
745        q_results = ltool.searchResultsWrapper(q)
746        for r in q_results:
747            if not r.getPiece_type:
748                continue
749            if r.getHasCoverImage:
750                cover_url='%s/coverImage' % r.getURL()
751            else:
752                if r.getPiece_type=='audio':
753                    cover_url='images/default_soundclip.png'
754                elif r.getPiece_type=='video':
755                    cover_url='images/default_movieclip.png'
756                else:
757                    cover_url='images/default_movieclip.png'
758            image_url=''
759            piece_type=search_type
760            if search_type!='cover_image':
761                piece_type=r.getPiece_type
762                if piece_type=='image':
763                    image_url='%s/image_large' % r.getURL()
764            # r.Title is utf_8-encoded str, so it is changed back to unicode
765            pseudometadata = [r.UID, cover_url, r.Title.decode('utf_8'), piece_type, image_url]
766            result.append(pseudometadata)
767        # javascript can handle unicode, but not utf_8-encoded stuff. Unicode shouldn't be preceded with u.
768        result=str(result) # when list is turned to string, unicode strings inside are left as [..., u'titleishere', ]
769        result=result.replace("u'","'") # and now unicode markers "u'" are removed 
770        return result 
771
772
773    ################# RSS ######################################
774
775    def getRSSResults(self):
776        """ Returns list of tuples (discussion_obj, display_macro_path, url) """
777        topic_id=self.REQUEST.get('URL1').split('/')[-1]
778        topic=getattr(self, topic_id, None)
779        results=[]
780        if topic:
781            results = topic.queryCatalogForTopics()[:30]                                   
782            return [(x, 'here/rss_macros/macros/brain_object', x.getURL()) for x in results]
783        return []
784
785
786##################### Subclasses        ################################
787
788
789class LargeContentFolder(LargeSectionFolder):
790
791    archetype_name = "Large Content Folder"
792    meta_type = "Large Content Folder"
793
794    allowed_content_types = CONTENT_TYPES +('Topic','Redirector')
795    default_view = ('lemill_content_view')
796    portlet='here/portlet_add_content/macros/portlet'
797    filter_content_types = True
798    subsections=['webpages','presentations','pilots','exercises','pieces','lessonplans','schoolprojects','printresources']
799    security = ClassSecurityInfo()
800
801
802    def getSamples(self):
803        """ Returns content types """
804        return self._getSamples(MATERIAL_TYPES)
805
806    def getSelectables(self, results):
807        """takes search results as input and returns a dictionary that tells what values there are available for each selection box and how many results there are of each value."""
808        pc=getToolByName(self, 'portal_catalog')
809        lang_dict = getToolByName(self, 'lemill_tool').language_dict
810        if len(results)<101:
811            langs, targs, types, subjs, tags = pc.fastCount(results, ('Language','getTarget_group','portal_type','getSubject_area','getTags'))
812        else:
813            langs, targs, types, subjs= pc.fastCount(results, ('Language','getTarget_group','portal_type','getSubject_area'))
814            tags={}
815        # Convert them to tuples where (value, display value)
816        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
817        targlist=zip(targs.keys(),targs.keys(),targs.values())
818        typelist=zip(map(lambda x: TYPE_NAMES[x][0], types.keys()), types.keys(), types.values())
819        subjslist=zip(subjs.keys(),subjs.keys(),subjs.values())
820        tagslist=zip(tags.keys(),tags.keys(),tags.values())
821        langlist.sort()
822        targlist.sort()
823        typelist.sort()
824        subjslist.sort()
825        tagslist.sort()
826        return {'language':langlist, 'target_group':targlist, 'type':typelist, 'subject_area':subjslist, 'tag':tagslist, 'drafts':[('Show drafts', 'no', 0)]}
827       
828    def hasBrowseArguments(self, source):
829        """ Checks if we should browse or show tagcloud """
830        goodargs=['language','target_group','tag','type','subject_area','drafts']
831        for (key, value) in source.items():
832            if key in goodargs and value:
833                return True
834        return False
835
836    def getTemplate(self, template_id):
837        return TEMPLATES.get(template_id, None)
838
839
840    security.declareProtected(ADD_CONTENT_PERMISSION,'lemidlet_post')
841    def lemidlet_post(self, REQUEST):
842        """ LeMidlet will post image here...."""
843        #print REQUEST.file.read()
844        file = REQUEST.get('file')
845        if file==None:
846            return 'file not found'       
847        description = REQUEST.get('description')
848        type = 'Piece'
849        new = self.lemill_invokeFactory(self, type, id=type+str(id(self)), do_create=True)
850        new.edit(description=description, file=file.read(),language='',title=REQUEST.get('title'),tags=REQUEST.get('tags'))
851        return 0
852
853       
854class LargeActivityFolder(LargeSectionFolder):
855
856    archetype_name = "Large Activity Folder"
857    meta_type = "Large Activity Folder"
858
859    allowed_content_types = ('Activity','KB', 'Topic','Redirector')
860    default_view = ('lemill_activities_view')
861    portlet='here/portlet_add_activity/macros/portlet'
862    subsections=[]
863    filter_content_types = True
864
865    def getSamples(self):
866        """ Returns 3 methods/activities """
867        return self._getSamples('Activity')
868
869    def hasBrowseArguments(self, source):
870        """ Checks if we should browse or show tagcloud """
871        goodargs=['language','tag']
872        for (key, value) in source.items():
873            if key in goodargs and value:
874                return True
875        return False
876
877    def getSelectables(self, results):
878        """Try to find which of languages and tags are found in results"""
879        pc=getToolByName(self, 'portal_catalog')
880        lang_dict = getToolByName(self, 'lemill_tool').language_dict
881        langs, tags = pc.fastCount(results, ('Language','getTags'))
882        # Convert them to tuples where (value, display value)
883        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
884        tagslist=zip(tags.keys(),tags.keys(),tags.values())
885        langlist.sort()
886        tagslist.sort()
887        return {'language':langlist,'tag':tagslist}
888   
889class LargeToolFolder(LargeSectionFolder):
890
891    archetype_name = "Large Tool Folder"
892    meta_type = "Large Tool Folder"
893
894    allowed_content_types = ('Tool', 'Topic','Redirector')
895    default_view = ('lemill_tools_view')
896    portlet='here/portlet_add_tool/macros/portlet'
897    subsections=[]
898    filter_content_types = True
899
900    def getSamples(self):
901        """ Returns 3 tools """
902        return self._getSamples('Tool')
903
904
905    def hasBrowseArguments(self, source):
906        """ Checks if we should browse or show tagcloud """
907        goodargs=['language','tag']
908        for (key, value) in source.items():
909            if key in goodargs and value:
910                return True
911        return False
912
913    def getSelectables(self, results):
914        """Try to find which of languages and tags are found in results"""
915        pc=getToolByName(self, 'portal_catalog')
916        lang_dict = getToolByName(self, 'lemill_tool').language_dict
917        langs, tags = pc.fastCount(results, ('Language','getTags'))
918        # Convert them to tuples where (value, display value)
919        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
920        tagslist=zip(tags.keys(),tags.keys(),tags.values())
921        langlist.sort()
922        tagslist.sort()
923        return {'language':langlist,'tag':tagslist}
924
925
926class LargeCommunityFolder(LargeSectionFolder):
927
928    archetype_name = "Large Community Folder"
929    meta_type = "Large Community Folder"
930
931    allowed_content_types = ('Topic','Redirector','MemberFolder','GroupBlog','ATBTreeFolder','Large Plone Folder')
932    default_view = ('lemill_community_view')
933    portlet='here/portlet_add_community/macros/portlet'
934
935    filter_content_types = False
936    security = ClassSecurityInfo()
937    subsections=['people','groups']
938    schema=communityschema
939
940
941    ### If MemberFolder for logged in user does not exist, it is created here
942   
943    def my_page(self):
944        """ Checks if user has MemberFolder and creates one if not. Returns the folder url."""       
945        lutool = getToolByName(self, "lemill_usertool")
946        mf_url = lutool.getMemberFolderURL()
947        #print 'my_page called'
948        if not mf_url:
949            lutool.createMemberFolder()           
950            mf_url = lutool.getMemberFolderURL()
951        return mf_url
952
953
954    def getMemberFolderById(self,id):
955        """ sometimes it is easier to just get it directly from btreefolder """
956        if self.id=='people':
957            return getattr(self, id, None)
958        elif hasattr(self, 'people'):
959            people=self.people
960            return getattr(people, id, None)
961        else:
962            return getattr(self, id, None)
963
964    def getGroupById(self,id):
965        """ sometimes it is easier to just get it directly from btreefolder """
966        if self.id=='groups':           
967            return getattr(self, id, None)
968        elif hasattr(self, 'groups'):
969            groups=self.groups
970            return getattr(groups, id, None)
971        else:
972            return getattr(self, id, None)
973
974    def getSamples(self):
975        """ Get 3 random members or groups """ 
976        lutool=getToolByName(self, 'lemill_usertool')
977        lt=getToolByName(self,'lemill_tool')
978        pc=getToolByName(self,'portal_catalog')
979        languages=lutool.getLanguages()
980        good_results=[]
981        while languages:
982            lang=languages.pop(0)
983            results=pc(portal_type=['MemberFolder','GroupBlog'], getLanguage_skills=lang, getState='public', getHasCoverImage=True, sort_on='getScore', sort_order='descending')
984            results=results[:30]
985            shuffle(results)
986            if len(results)>2:
987                return results[:3]
988            else:
989                good_results+=results
990            if len(good_results)>2:
991                return good_results[:3]
992        return good_results
993       
994
995    def mergeLatestThreadsInMyGroups(self):
996        """ Method to help displaying latest threads (BlogPost) in section front page """
997        lutool = getToolByName(self, "lemill_usertool")
998        memberfolder = lutool.getMemberFolder()
999        if not memberfolder:
1000            return []
1001        glist=[g.getId for g in memberfolder.getGroups(objects=False)]
1002        pc=getToolByName(self, 'portal_catalog')
1003        posts=pc({'getParentBlog':glist, 'getState':'public', 'sort_on':'created', 'sort_order':'descending','meta_type':'BlogPost'})
1004        return posts[:20]
1005                                   
1006    def getCollections(self, obj_uid=None):
1007        """ Show collections where object is used."""
1008        if not obj_uid:
1009            return []
1010        res = []
1011        q = { 'targetUID': obj_uid }
1012        qres = self.reference_catalog(q)
1013        for q in qres:
1014            v = self.reference_catalog.lookupObject(q.UID)
1015            if v:
1016                source = v.getSourceObject()
1017                if source.meta_type == 'Collection':
1018                    res.append(source)
1019        return res
1020
1021
1022    def getSelectablesForMembers(self, results):
1023        """Try to find which of languages and target groups are selectable"""
1024        pc=getToolByName(self, 'portal_catalog')
1025        langs, subjs, locas, skills, interests = pc.fastCount(results, ('getLanguage_skills','getSubject_area','getLocation_country','getSkills','getInterests'))
1026        lang_dict = getToolByName(self, 'lemill_tool').language_dict
1027        if langs.has_key('No country specified'):
1028            del langs['No country specified']
1029        # Convert them to tuples where (value, display value)
1030        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
1031        subjslist=zip(subjs.keys(),subjs.keys(),subjs.values())
1032        locaslist=zip(locas.keys(),locas.keys(),locas.values())
1033        skillslist=zip(skills.keys(),skills.keys(),skills.values())
1034        interestslist=zip(interests.keys(),interests.keys(),interests.values())
1035        langlist.sort()
1036        subjslist.sort()
1037        locaslist.sort()
1038        skillslist.sort()
1039        interestslist.sort()
1040        return {'language_skills':langlist, 'subject_area':subjslist, 'location':locaslist,'skills':skillslist,'interests':interestslist}
1041       
1042    def hasBrowseArguments(self, source):
1043        """ Checks if we should browse or show tagcloud """
1044        goodargs=['type','language_skills','skills','interests','location','subject_area','tag','subject_area']
1045        for key in source.keys():
1046            if key in goodargs and source[key]:
1047                return True
1048        return False
1049
1050    def getSelectablesForGroups(self, results):
1051        """Try to find which of languages are found in results"""
1052        pc=getToolByName(self, 'portal_catalog')
1053        langs, subjs, tags = pc.fastCount(results, ('getLanguage_skills','getSubject_area','getTags'))
1054
1055        lang_dict = getToolByName(self, 'lemill_tool').language_dict
1056        # Convert them to tuples where (value, display value)
1057        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
1058        subjslist=zip(subjs.keys(),subjs.keys(),subjs.values())
1059        tagslist=zip(tags.keys(),tags.keys(),tags.values())
1060        langlist.sort()
1061        subjslist.sort()
1062        tagslist.sort()
1063        return {'language_skills':langlist, 'group_subject_area':subjslist,'tag':tagslist}
1064
1065    def getCreatorstring(self, source):
1066        """ return string to attach to url to limit results for one person or group """
1067        if 'Creator' in source:
1068            return '&Creator=%s' % source['Creator']
1069        elif 'getRawGroupEditing' in source:
1070            return '&getRawGroupEditing=%s' % source['getRawGroupEditing']
1071        else:
1072            return ''
1073
1074    def getSelectablesForPortfolio(self, results):
1075        """Try to find which types are found in results and show them as selectables """
1076        pc=getToolByName(self, 'portal_catalog')
1077        lt=getToolByName(self, 'lemill_tool')
1078        types, tags = pc.fastCount(results, ('portal_type','getTags'))
1079        # Convert them to tuples where (value, display value)
1080        types=zip(map(lambda x: lt.getTypeName(x), types.keys()), types.keys(), types.values())
1081        tagslist=zip(tags.keys(),tags.keys(),tags.values())
1082        tagslist.sort()
1083        return {'type':types,'tag':tagslist}
1084
1085    security.declareProtected(ADD_CONTENT_PERMISSION,'addGroup')
1086    def addGroup(self, title, join=True):
1087        """ Create a new group. """
1088        if self.getId()!='groups':
1089            return self.community.groups.addGroup(title, join)
1090        lt = getToolByName(self, 'lemill_tool')
1091        group_id = lt.normalizeString(title)
1092        group_id=self.invokeFactory('GroupBlog',group_id)
1093        group=getattr(self, group_id, None)
1094        group.edit(title=title, description="")
1095        if join:
1096            group.join_group()
1097        # Group object in uid_catalog gets title
1098        group.helper_updateCatalogUID()
1099        # Need to manually set _at_creation_flag to False, as processForm does not get called
1100        group.unmarkCreationFlag()
1101        return group           
1102
1103
1104class LargeTrashFolder(LargeSectionFolder):
1105
1106    archetype_name = "Large Trash Folder"
1107    meta_type = "Large Trash Folder"
1108
1109    allowed_content_types = ALL_CONTENT_TYPES
1110    filter_content_types = False
1111    default_view = ('lemill_trash_view')
1112    security = ClassSecurityInfo()
1113
1114       
1115registerType(LargeContentFolder, PROJECTNAME)
1116registerType(LargeActivityFolder, PROJECTNAME)
1117registerType(LargeToolFolder, PROJECTNAME)
1118registerType(LargeCommunityFolder, PROJECTNAME)
1119registerType(LargeTrashFolder, PROJECTNAME)
Note: See TracBrowser for help on using the repository browser.