source: trunk/LargeSectionFolder.py @ 3045

Revision 3045, 46.6 KB checked in by jukka, 10 years ago (diff)

There are so many changes coming that I better start commiting even these unfinished versions. ExerciseTemplate? is the most broken thing now, other places that try to use bodytext from webpages/exercises may have issues too.

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        print 'LargeSectionFolder.initializeArchetype called', kwargs
332        t=time.time()
333
334        ret_val = ATBTreeFolder.initializeArchetype(self, **kwargs)
335        # Enable topic syndication by default
336        syn_tool = getToolByName(self, 'portal_syndication', None)
337        if syn_tool is not None:
338            if syn_tool.isSiteSyndicationAllowed():
339                try:
340                    syn_tool.enableSyndication(self)
341                except: # might get 'Syndication Information Exists'
342                    pass
343        print '...Archetype Initialized ', time.time()-t
344        return ret_val       
345
346    security.declareProtected(ADD_CONTENT_PERMISSION,'createPiece')
347    def createPiece(self, container, id=None, file=None):
348        new=self.lemill_invokeFactory(container, 'Piece', id, do_create=True)
349        new.edit(file=file.read(), language='')
350        return new
351
352    security.declareProtected(ADD_CONTENT_PERMISSION,'lemill_invokeFactory')
353    def lemill_invokeFactory(self, container, meta_type, id=None, do_create=False):
354        """ add new object, edit it's title and invoke _renameAfterCreation """
355        ft=getToolByName(self, 'portal_factory')
356        if id is None:
357            id = self.generateUniqueId(meta_type)
358        path_as_list=self.getDefaultPathFor(meta_type, as_list=True)
359        if path_as_list and path_as_list != list(container.getPhysicalPath()):
360            container=self.restrictedTraverse(self.getDefaultPathFor(meta_type))       
361
362        if ft.getFactoryTypes().has_key(meta_type):
363            o = container.restrictedTraverse('portal_factory/' + meta_type + '/' + id)
364            message = None
365            transaction_note('Initiated creation of %s with id %s in %s' % (o.getTypeInfo().getId(), id, container.absolute_url()))
366        else:
367            new_id = container.invokeFactory(id=id, type_name=meta_type)
368            if new_id is None or new_id == '':
369               new_id = id
370            o=getattr(container, new_id, None)
371            tname = o.getTypeInfo().Title()
372            message = _(u'text_message_object_created', u'${tname} has been created.', mapping={u'tname' : tname})
373            transaction_note('Created %s with id %s in %s' % (o.getTypeInfo().getId(), new_id, container.absolute_url()))
374        if do_create: # finalizes object
375            o=ft.doCreate(o)
376        #print 'Created %s' % o
377        return o
378
379
380    def checkTitle(self, obj=None ,title='', objtype=''):
381        """ check if title is not used anywhere in not(deleted, redirector) object, return false if it is """
382        lt=getToolByName(self, 'lemill_tool')
383        return lt.checkTitle(self,obj=obj, title=title, objtype=objtype)
384
385
386
387
388    ##############         Resource conversion      ######################
389
390    security.declareProtected(ModerateContent, 'convertResource')
391    def convertResource(self, REQUEST):
392        """ Copy values of some fields to new object and redirect old references """
393        allowed_types={'LeMillReference':['content','references'],'Tool':'tools','Activity':'methods','MultimediaMaterial':['content','webpages'], 'ExerciseMaterial':['content','exercises']}
394        lt=getToolByName(self, 'lemill_tool')
395
396        # Check if we have everything we need and raise error if not
397        from_obj_id=REQUEST.get('from_obj_id', '')
398        to_obj_type=REQUEST.get('to_obj_type', '')
399        trial_run=REQUEST.get('trial_run', True)
400
401        if trial_run:
402            if trial_run=='False' or trial_run=='0':
403                trial_run=False
404
405        from_obj=getattr(self, from_obj_id,None)
406        if not from_obj:
407            raise "base object for resource conversion doesn't exist"
408
409        from_obj_type=from_obj.portal_type
410        if from_obj_type not in allowed_types.keys():
411            raise "cannot convert from type %s" % from_obj_type
412        if to_obj_type not in allowed_types.keys():
413            raise "cannot transfer to type %s" % to_obj_type
414
415        # Create to_obj
416        if to_obj_type in ['Tool','Activity']:
417            to_folder = getattr(self.aq_parent, allowed_types[to_obj_type])
418        else:
419            section = getattr(self.aq_parent, allowed_types[to_obj_type][0])
420            to_folder = getattr(section, allowed_types[to_obj_type][1])
421        to_obj = self.lemill_invokeFactory(to_folder, to_obj_type, do_create=True)
422
423        from_schema=from_obj.schema
424        to_schema=to_obj.schema
425         
426        import copy
427        ignored_fields=['translation', 'translation_of','id','groups']
428
429        # trial run, report fields that have content but cannot be converted
430        if trial_run:
431            problems=False
432            for k in from_schema.keys():
433                from_accessor = from_schema[k].getEditAccessor(from_obj)
434                if k not in ignored_fields and  k not in to_schema.keys() and from_accessor and from_accessor():
435                    #stupid exceptions
436                    if k=='address' and from_accessor()=='http://':
437                        continue
438                    if k=='address' and 'location' in to_schema.keys():
439                        continue
440                    if k=='location' and 'address' in to_schema.keys():
441                        continue
442                    if k=='video' and len(from_accessor())<5:
443                        continue
444                    problems=True
445                    lt.addPortalMessage(_('text_message_cannot_convert'), default='Cannot convert ${obj}.', mapping={'obj':k})
446            to_folder._delObject(to_obj.id)
447            if not problems:
448                lt.addPortalMessage(_('text_message_conversion_will_be_fine'), default='Checked conversion and it will be fine. Click \'Convert resource\' to do the conversion.')
449            if problems:
450                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.")
451            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))
452
453
454        # Helper method for copying fields
455        def copyvalue(from_accessor, to_mutator, k):
456            if not from_accessor:
457                return
458            val = from_accessor()
459            if k=='bodyText':
460                if from_obj_type == 'ExerciseMaterial':
461                    if to_obj_type == 'MultimediaMaterial':
462                        new_val=[]
463                        for (chapter, c_type) in val:
464                            if c_type in ['choice','multiple_choices','fill_in_the_blanks','open_ended']:
465                                new_val.append((chapter, 'text_block'))
466                            else:
467                                new_val.append((chapter, c_type))
468                        val=new_val
469                    else:
470                        val=from_obj.getOnlyRawText()
471                elif from_obj_type == 'MultimediaMaterial':
472                    if to_obj_type == 'ExerciseMaterial':
473                        if val[0][0]=='text_block':
474                            val[0][0]='guidelines'
475                    else:
476                        val=from_obj.getOnlyRawText()
477            copied_val = None
478            try:
479                copied_val = copy.copy(val)
480            except TypeError:
481                copied_val = copy.copy(val.aq_base)
482            to_mutator(copied_val)
483
484        # Copy fields with same name
485        for k in from_schema.keys():
486            # list all fields here that shouldn't be copyied to new object
487            if k not in to_schema.keys() or k in ignored_fields:
488                continue
489            from_accessor = from_schema[k].getEditAccessor(from_obj)
490            to_mutator = to_schema[k].getMutator(to_obj)
491            copyvalue(from_accessor, to_mutator, k)
492
493        # Check convertable pairs of exceptions:
494        for (k,l) in [('address','location')]:
495            if k in from_schema.keys() and l in to_schema.keys():
496                from_accessor = from_schema[k].getEditAccessor(from_obj)
497                to_mutator = to_schema[l].getMutator(to_obj)
498                copyvalue(from_accessor, to_mutator, k)
499            elif l in from_schema.keys() and k in to_schema.keys():
500                from_accessor = from_schema[l].getEditAccessor(from_obj)
501                to_mutator = to_schema[k].getMutator(to_obj)
502                copyvalue(from_accessor, to_mutator, k)
503
504        # LeMillReferences use descriptions instead of bodytexts while they still have bodytexts:
505        if to_obj_type=='LeMillReference':
506            k='bodyText'
507            from_accessor=from_schema['bodyText'].getEditAccessor(from_obj)
508            to_mutator = to_schema['description'].getMutator(to_obj)
509            copyvalue(from_accessor, to_mutator, k)
510        if from_obj_type=='LeMillReference':
511            k='description'
512            from_accessor=from_schema['description'].getEditAccessor(from_obj)
513            to_mutator = to_schema['bodyText'].getMutator(to_obj)
514            copyvalue(from_accessor, to_mutator, k)
515
516
517        # Look for references to this object
518        rc=getToolByName(self, 'reference_catalog')
519        uc=getToolByName(self, 'uid_catalog')
520        results=rc({'targetUID':from_obj.UID()})
521
522        # Get object with certain reference and fix it
523        for refobj in results:
524            res=uc({'UID':refobj.sourceUID})
525            if res:
526                # getObject verified
527                obj=res[0].getObject()
528                if refobj.relationship in ['relatesToContent', 'relatesToMethods', 'relatesToTools']:
529                    obj.delete(from_obj)
530                if obj.portal_type=='Collection':
531                    obj.add(to_obj)
532
533        # Copy references to pieces
534        if from_obj_type in ['MultimediaMaterial','ExerciseMaterial'] and to_obj_type in ['MultimediaMaterial','ExerciseMaterial']:
535            results=rc({'sourceUID':from_obj.UID()})
536            for refobj in results:
537                rc.addReference(to_obj, refobj.targetUID, 'uses')       
538
539        # Copy history
540        from_history=from_obj.getHistory()
541        to_obj.setHistory(copy.copy(from_history))
542
543        # Run post_edits
544        to_obj.post_edit_rename()
545        to_obj.recalculateAuthors()
546        to_obj.recalculateScore()
547        to_obj.reindexObject()
548
549        # Delete old object
550        self._delObject(from_obj_id)
551
552        # Return new object
553        lt.addPortalMessage(_('Resource converted succesfully.'))       
554        if to_obj.portal_type in MATERIAL_TYPES:
555            return REQUEST.RESPONSE.redirect('%s/view' % to_obj.absolute_url())           
556        else:
557            return REQUEST.RESPONSE.redirect(to_obj.absolute_url())           
558
559       
560
561    def wakeLazy(self, lazy):
562        new=[]
563        for l in lazy._seq:
564            if isinstance(l, Lazy):
565                new.extend(self.wakeLazy(l))
566            else:
567                new.append(l)
568        return new
569
570
571
572
573    ################      Featured items      ###########################
574
575    def _getSamples(self, content_types):
576        lutool=getToolByName(self, 'lemill_usertool')
577        lt=getToolByName(self,'lemill_tool')
578        pc=getToolByName(self,'portal_catalog')
579        languages=lutool.getLanguages()
580        good_results=[]
581        while languages:
582            lang=languages.pop(0)
583            results=pc(portal_type=content_types, Language=lang, getState='public', getHasCoverImage=True, sort_on='getScore', sort_order='descending')
584            results=results[:30]
585            shuffle(results)
586            if len(results)>2:
587                return results[:3]
588            else:
589                good_results+=results
590            if len(good_results)>2:
591                return good_results[:3]
592        return good_results
593
594    security.declareProtected(MANAGE_PORTAL, 'rebuildTop3s')
595    def rebuildTop3s(self):
596        """ set values for sections' top 3 tags, languages etc. (do this daily, or weekly: very expensive) """
597        t=time.time()
598        section_toplists={
599            'content':['top3languages','top3subject_areas','top3target_groups','top3tags'],
600            'methods':['top3languages','top3tags'],
601            'tools':['top3languages','top3tags'],
602            'community':['top3language_skills','top3locations','top3skills','top3interests','top3subject_areas']               
603        }
604       
605        type_mapping = {'content':CONTENT_TYPES, 'methods':'Activity','tools':'Tool','community':'MemberFolder'} # Groups are handled separately         
606        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'}
607        pc=getToolByName(self, 'portal_catalog')
608        # Others
609        for section_name, toplist in section_toplists.items():
610            results=pc(portal_type=type_mapping[section_name], getState='public', getHasCoverImage=True)
611            results=self.wakeLazy(results)
612            section=getattr(self.aq_parent, section_name)
613            props={}
614            for top3_id in toplist:
615                props[top3_id]=self._getTopResults(results, category_mapping[top3_id])
616            section.manage_editProperties(props)
617        # Groups
618        results=pc(portal_type='GroupBlog', getState='public')
619        results=self.wakeLazy(results)
620        for top3_id in ['top3group_languages','top3group_tags','top3group_subject_areas']:
621            props[top3_id]=self._getTopResults(results, category_mapping[top3_id])
622        self.aq_parent.community.manage_editProperties(props)
623        return str(time.time()-t)       
624
625       
626    def _getTopResults(self, results, index_type):
627        pc = getToolByName(self, 'portal_catalog')
628        if index_type not in pc.indexes() or index_type not in pc.schema():
629            return []
630        res={}
631        index=pc._catalog.getIndex(index_type)
632        r=[]
633        for value in results:
634            value=index.getEntryForObject(value, "")
635            if value and (isinstance(value, list) or isinstance(value, tuple)):
636                for v in value:
637                    res[v]=res.get(v,0)+1
638            elif value:
639                res[value]=res.get(value,0)+1
640        topthree= [(n,key) for key, n in res.items() if n>1 and key]       
641        topthree.sort(reverse=True)
642        topthree=[key for n,key in topthree[:3]]
643        return topthree
644
645    ##############################      Clouds      ######################## 
646
647    def getTitleCloud(self, search_results, browse_type):
648        """ Build a cloud based on popularity score for that resource """
649        pc=getToolByName(self,'portal_catalog')
650        # uniquetuplelist contains result metadata reordered: (sort_title, count, url, indexvalue, title)
651        from math import log
652        if not search_results:
653            return []
654
655        def adjust(i):
656            # helper method to adjust hit count of this tag to relative size (1,...,8)
657            (a,b,c,d,e)=i
658            b=int((8*log(b,2))/log(maxscore,2))
659            if b==0:
660                b=1
661            i=(a,b,c,d,e)
662            return i       
663        def isDefaultTitle(x):
664            """ some heuristic to recognize default titles """
665            return re.match(r'.*\.(...)$', x) or re.match(r'.*\.(....)$', x)
666                   
667        popularity = pc.fastMetadata(search_results, ('getScore','rid','getNicename','sortable_title'))
668        popularity.sort(reverse=True)
669        popularity=popularity[:100]
670        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]
671       
672        if not titlecloud:
673            return []
674        titlecloud.sort()
675        maxscore=max([x[1] for x in titlecloud])
676        if maxscore>1:
677            titlecloud=map(adjust, titlecloud)
678        return titlecloud
679
680    def getTagCloud(self, search_results, index_type):
681        """ Build a cloud based on how many occurences of this item are in results """
682        if not search_results:
683            return []
684        lemill_tool = getToolByName(self, 'lemill_tool')
685        pc = getToolByName(self, 'portal_catalog')
686        from math import log
687        maxcount=0
688
689        def adjust(i):
690            # helper method to adjust hit count of this tag to relative size (1,...,8)
691            (a,b,c,d,e)=i
692            try:
693                b=int((8*log(b-mincount,2))/log(maxcount-mincount,2))
694            except (OverflowError, ZeroDivisionError):
695                b=0
696            if b==0:
697                b=1
698            i=(a,b,c,d,e)
699            return i
700        hits={}
701        hits=pc.fastCount(search_results, index_type)
702        resultlist=zip(hits.values(),hits.keys())
703        if not resultlist:
704            return []
705        resultlist.sort()
706        resultlist.reverse()
707        maxcount=resultlist[0][0] # first!
708        # if the first cut score for tag is x, we want to cut off all of the tags with score x.
709        if len(resultlist)>100:
710            #cutpoint = [x[0] for x in resultlist].index(resultlist[100]) can't figure this now, fix later
711            cutpoint = 100
712            resultlist = resultlist[:cutpoint]
713        mincount=resultlist[-1][0]
714        resultlist=[(x[1], x[0], '',x[1],x[1]) for x in resultlist]
715
716        # adjust to 1-8. We don't have to worry about score 0, they're already removed.
717        if maxcount>1:
718            resultlist=map(adjust, resultlist)
719        # prettify language names
720        if index_type=='Language' or index_type=='getLanguage_skills':
721            resultlist=[(x[0],x[1],x[2],x[3],lemill_tool.getPrettyLanguage(x[4])) for x in resultlist]           
722        if index_type=='getTarget_group':
723            def compfunc(t2,t1):
724                if t2[0] in TARGET_GROUP and t1[0] in TARGET_GROUP:
725                    return  TARGET_GROUP.index(t2[0]) - TARGET_GROUP.index(t1[0])
726                else:
727                    return -1
728            resultlist.sort(cmp=compfunc)
729        else:   
730            resultlist.sort()
731        return resultlist
732
733
734    def js_queryForPieces(self, keyword='', search_type='image', user='', collection=False):
735        """ javascript is querying for pieces that are images """
736        result = []
737        q = {'portal_type':'Piece', 'getState': 'public'}
738        if not (keyword or user):
739            return []
740        if keyword:
741            q['SearchableText']=keyword
742        if user:
743            lutool=getToolByName(self, 'lemill_usertool')
744            q['listCreators']=lutool.getAuthenticatedId()
745            q['sort_on']='modified'
746            q['sort_order']='descending'
747        if search_type=='image' or search_type=='cover_image':
748            q['getPiece_type']='image'
749        elif search_type=='audio':
750            q['getPiece_type']='audio'
751
752        ltool = getToolByName(self, 'lemill_tool')
753        if collection and user:
754            query={'portal_type':'Piece', 'getState':'public', 'Creator':q['listCreators']}
755            if 'getPiece_type' in q:
756                query['getPiece_type']=q['getPiece_type']
757            reftool = getToolByName(self, 'reference_catalog')
758            catalog = getToolByName(self, 'portal_catalog')
759            q_results=[]
760            cols=ltool.searchResultsWrapper(portal_type='Collection', getState='public', Creator=q['listCreators'])
761            for col in cols:
762                refs=reftool(sourceUID=col.UID, relationship='relatesToContent')
763                resource_uids=[x.targetUID for x in refs]
764                query['UID']=resource_uids
765                res=catalog(query)
766                if res:
767                   q_results+=res
768        else:
769            q_results = ltool.searchResultsWrapper(q)
770        for r in q_results:
771            if not r.getPiece_type:
772                continue
773            if r.getHasCoverImage:
774                cover_url='%s/coverImage' % r.getURL()
775            else:
776                if r.getPiece_type=='audio':
777                    cover_url='images/default_soundclip.png'
778                elif r.getPiece_type=='video':
779                    cover_url='images/default_movieclip.png'
780                else:
781                    cover_url='images/default_movieclip.png'
782            image_url=''
783            piece_type=search_type
784            if search_type!='cover_image':
785                piece_type=r.getPiece_type
786                if piece_type=='image':
787                    image_url='%s/image_large' % r.getURL()
788            # r.Title is utf_8-encoded str, so it is changed back to unicode
789            pseudometadata = [r.UID, cover_url, r.Title.decode('utf_8'), piece_type, image_url]
790            result.append(pseudometadata)
791        # javascript can handle unicode, but not utf_8-encoded stuff. Unicode shouldn't be preceded with u.
792        result=str(result) # when list is turned to string, unicode strings inside are left as [..., u'titleishere', ]
793        result=result.replace(" u'"," '")
794        result=result.replace(' u"',' "') # and now unicode markers "u'" are removed
795        return result 
796
797
798    ################# RSS ######################################
799
800    def getRSSResults(self):
801        """ Returns list of tuples (discussion_obj, display_macro_path, url) """
802        topic_id=self.REQUEST.get('URL1').split('/')[-1]
803        topic=getattr(self, topic_id, None)
804        results=[]
805        if topic:
806            results = topic.queryCatalogForTopics()[:30]                                   
807            return [(x, 'here/rss_macros/macros/brain_object', x.getURL()) for x in results]
808        return []
809
810
811##################### Subclasses        ################################
812
813
814class LargeContentFolder(LargeSectionFolder):
815
816    archetype_name = "Large Content Folder"
817    meta_type = "Large Content Folder"
818
819    allowed_content_types = CONTENT_TYPES +('Topic','Redirector')
820    default_view = ('lemill_content_view')
821    portlet='here/portlet_add_content/macros/portlet'
822    filter_content_types = True
823    subsections=['webpages','presentations','pilots','exercises','pieces','lessonplans','schoolprojects','printresources']
824    security = ClassSecurityInfo()
825
826
827    def getSamples(self):
828        """ Returns content types """
829        return self._getSamples(MATERIAL_TYPES)
830
831    def getSelectables(self, results):
832        """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."""
833        pc=getToolByName(self, 'portal_catalog')
834        lang_dict = getToolByName(self, 'lemill_tool').language_dict
835        if len(results)<101:
836            langs, targs, types, subjs, tags = pc.fastCount(results, ('Language','getTarget_group','portal_type','getSubject_area','getTags'))
837        else:
838            langs, targs, types, subjs= pc.fastCount(results, ('Language','getTarget_group','portal_type','getSubject_area'))
839            tags={}
840        # Convert them to tuples where (value, display value)
841        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
842        targlist=zip(targs.keys(),targs.keys(),targs.values())
843        typelist=zip(map(lambda x: TYPE_NAMES[x][0], types.keys()), types.keys(), types.values())
844        subjslist=zip(subjs.keys(),subjs.keys(),subjs.values())
845        tagslist=zip(tags.keys(),tags.keys(),tags.values())
846        langlist.sort()
847        targlist.sort()
848        typelist.sort()
849        subjslist.sort()
850        tagslist.sort()
851        return {'language':langlist, 'target_group':targlist, 'type':typelist, 'subject_area':subjslist, 'tag':tagslist, 'drafts':[('Show drafts', 'no', 0)]}
852       
853    def hasBrowseArguments(self, source):
854        """ Checks if we should browse or show tagcloud """
855        goodargs=['language','target_group','tag','type','subject_area','drafts']
856        for (key, value) in source.items():
857            if key in goodargs and value:
858                return True
859        return False
860
861    def getTemplate(self, template_id):
862        return TEMPLATES.get(template_id, None)
863
864
865    security.declareProtected(ADD_CONTENT_PERMISSION,'lemidlet_post')
866    def lemidlet_post(self, REQUEST):
867        """ LeMidlet will post image here...."""
868        #print REQUEST.file.read()
869        file = REQUEST.get('file')
870        if file==None:
871            return 'file not found'       
872        description = REQUEST.get('description')
873        type = 'Piece'
874        new = self.lemill_invokeFactory(self, type, id=type+str(id(self)), do_create=True)
875        new.edit(description=description, file=file.read(),language='',title=REQUEST.get('title'),tags=REQUEST.get('tags'))
876        return 0
877
878       
879class LargeActivityFolder(LargeSectionFolder):
880
881    archetype_name = "Large Activity Folder"
882    meta_type = "Large Activity Folder"
883
884    allowed_content_types = ('Activity','KB', 'Topic','Redirector')
885    default_view = ('lemill_activities_view')
886    portlet='here/portlet_add_activity/macros/portlet'
887    subsections=[]
888    filter_content_types = True
889
890    def getSamples(self):
891        """ Returns 3 methods/activities """
892        return self._getSamples('Activity')
893
894    def hasBrowseArguments(self, source):
895        """ Checks if we should browse or show tagcloud """
896        goodargs=['language','tag']
897        for (key, value) in source.items():
898            if key in goodargs and value:
899                return True
900        return False
901
902    def getSelectables(self, results):
903        """Try to find which of languages and tags are found in results"""
904        pc=getToolByName(self, 'portal_catalog')
905        lang_dict = getToolByName(self, 'lemill_tool').language_dict
906        langs, tags = pc.fastCount(results, ('Language','getTags'))
907        # Convert them to tuples where (value, display value)
908        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
909        tagslist=zip(tags.keys(),tags.keys(),tags.values())
910        langlist.sort()
911        tagslist.sort()
912        return {'language':langlist,'tag':tagslist}
913   
914class LargeToolFolder(LargeSectionFolder):
915
916    archetype_name = "Large Tool Folder"
917    meta_type = "Large Tool Folder"
918
919    allowed_content_types = ('Tool', 'Topic','Redirector')
920    default_view = ('lemill_tools_view')
921    portlet='here/portlet_add_tool/macros/portlet'
922    subsections=[]
923    filter_content_types = True
924
925    def getSamples(self):
926        """ Returns 3 tools """
927        return self._getSamples('Tool')
928
929
930    def hasBrowseArguments(self, source):
931        """ Checks if we should browse or show tagcloud """
932        goodargs=['language','tag']
933        for (key, value) in source.items():
934            if key in goodargs and value:
935                return True
936        return False
937
938    def getSelectables(self, results):
939        """Try to find which of languages and tags are found in results"""
940        pc=getToolByName(self, 'portal_catalog')
941        lang_dict = getToolByName(self, 'lemill_tool').language_dict
942        langs, tags = pc.fastCount(results, ('Language','getTags'))
943        # Convert them to tuples where (value, display value)
944        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
945        tagslist=zip(tags.keys(),tags.keys(),tags.values())
946        langlist.sort()
947        tagslist.sort()
948        return {'language':langlist,'tag':tagslist}
949
950
951class LargeCommunityFolder(LargeSectionFolder):
952
953    archetype_name = "Large Community Folder"
954    meta_type = "Large Community Folder"
955
956    allowed_content_types = ('Topic','Redirector','MemberFolder','GroupBlog','ATBTreeFolder','Large Plone Folder')
957    default_view = ('lemill_community_view')
958    portlet='here/portlet_add_community/macros/portlet'
959
960    filter_content_types = False
961    security = ClassSecurityInfo()
962    subsections=['people','groups']
963    schema=communityschema
964
965
966    ### If MemberFolder for logged in user does not exist, it is created here
967   
968    def my_page(self):
969        """ Checks if user has MemberFolder and creates one if not. Returns the folder url."""       
970        lutool = getToolByName(self, "lemill_usertool")
971        mf_url = lutool.getMemberFolderURL()
972        #print 'my_page called'
973        if not mf_url:
974            lutool.createMemberFolder()           
975            mf_url = lutool.getMemberFolderURL()
976        return mf_url
977
978
979    def getMemberFolderById(self,id):
980        """ sometimes it is easier to just get it directly from btreefolder """
981        if self.id=='people':
982            return getattr(self, id, None)
983        elif hasattr(self, 'people'):
984            people=self.people
985            return getattr(people, id, None)
986        else:
987            return getattr(self, id, None)
988
989    def getGroupById(self,id):
990        """ sometimes it is easier to just get it directly from btreefolder """
991        if self.id=='groups':           
992            return getattr(self, id, None)
993        elif hasattr(self, 'groups'):
994            groups=self.groups
995            return getattr(groups, id, None)
996        else:
997            return getattr(self, id, None)
998
999    def getSamples(self):
1000        """ Get 3 random members or groups """ 
1001        lutool=getToolByName(self, 'lemill_usertool')
1002        lt=getToolByName(self,'lemill_tool')
1003        pc=getToolByName(self,'portal_catalog')
1004        languages=lutool.getLanguages()
1005        good_results=[]
1006        while languages:
1007            lang=languages.pop(0)
1008            results=pc(portal_type=['MemberFolder','GroupBlog'], getLanguage_skills=lang, getState='public', getHasCoverImage=True, sort_on='getScore', sort_order='descending')
1009            results=results[:30]
1010            shuffle(results)
1011            if len(results)>2:
1012                return results[:3]
1013            else:
1014                good_results+=results
1015            if len(good_results)>2:
1016                return good_results[:3]
1017        return good_results
1018       
1019
1020    def mergeLatestThreadsInMyGroups(self):
1021        """ Method to help displaying latest threads (BlogPost) in section front page """
1022        lutool = getToolByName(self, "lemill_usertool")
1023        memberfolder = lutool.getMemberFolder()
1024        if not memberfolder:
1025            return []
1026        glist=[g.getId for g in memberfolder.getGroups(objects=False)]
1027        pc=getToolByName(self, 'portal_catalog')
1028        posts=pc({'getParentBlog':glist, 'getState':'public', 'sort_on':'created', 'sort_order':'descending','meta_type':'BlogPost'})
1029        return posts[:20]
1030                                   
1031    def getCollections(self, obj_uid=None):
1032        """ Show collections where object is used."""
1033        if not obj_uid:
1034            return []
1035        res = []
1036        q = { 'targetUID': obj_uid }
1037        qres = self.reference_catalog(q)
1038        for q in qres:
1039            v = self.reference_catalog.lookupObject(q.UID)
1040            if v:
1041                source = v.getSourceObject()
1042                if source.meta_type == 'Collection':
1043                    res.append(source)
1044        return res
1045
1046
1047    def getSelectablesForMembers(self, results):
1048        """Try to find which of languages and target groups are selectable"""
1049        pc=getToolByName(self, 'portal_catalog')
1050        langs, subjs, locas, skills, interests = pc.fastCount(results, ('getLanguage_skills','getSubject_area','getLocation_country','getSkills','getInterests'))
1051        lang_dict = getToolByName(self, 'lemill_tool').language_dict
1052        if langs.has_key('No country specified'):
1053            del langs['No country specified']
1054        # Convert them to tuples where (value, display value)
1055        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
1056        subjslist=zip(subjs.keys(),subjs.keys(),subjs.values())
1057        locaslist=zip(locas.keys(),locas.keys(),locas.values())
1058        skillslist=zip(skills.keys(),skills.keys(),skills.values())
1059        interestslist=zip(interests.keys(),interests.keys(),interests.values())
1060        langlist.sort()
1061        subjslist.sort()
1062        locaslist.sort()
1063        skillslist.sort()
1064        interestslist.sort()
1065        return {'language_skills':langlist, 'subject_area':subjslist, 'location':locaslist,'skills':skillslist,'interests':interestslist}
1066       
1067    def hasBrowseArguments(self, source):
1068        """ Checks if we should browse or show tagcloud """
1069        goodargs=['type','language_skills','skills','interests','location','subject_area','tag','subject_area']
1070        for key in source.keys():
1071            if key in goodargs and source[key]:
1072                return True
1073        return False
1074
1075    def getSelectablesForGroups(self, results):
1076        """Try to find which of languages are found in results"""
1077        pc=getToolByName(self, 'portal_catalog')
1078        langs, subjs, tags = pc.fastCount(results, ('getLanguage_skills','getSubject_area','getTags'))
1079
1080        lang_dict = getToolByName(self, 'lemill_tool').language_dict
1081        # Convert them to tuples where (value, display value)
1082        langlist=zip(map(lambda x: lang_dict[x], langs.keys()), langs.keys(),langs.values())
1083        subjslist=zip(subjs.keys(),subjs.keys(),subjs.values())
1084        tagslist=zip(tags.keys(),tags.keys(),tags.values())
1085        langlist.sort()
1086        subjslist.sort()
1087        tagslist.sort()
1088        return {'language_skills':langlist, 'group_subject_area':subjslist,'tag':tagslist}
1089
1090    def getCreatorstring(self, source):
1091        """ return string to attach to url to limit results for one person or group """
1092        if 'Creator' in source:
1093            return '&Creator=%s' % source['Creator']
1094        elif 'getRawGroupEditing' in source:
1095            return '&getRawGroupEditing=%s' % source['getRawGroupEditing']
1096        else:
1097            return ''
1098
1099    def getSelectablesForPortfolio(self, results):
1100        """Try to find which types are found in results and show them as selectables """
1101        pc=getToolByName(self, 'portal_catalog')
1102        lt=getToolByName(self, 'lemill_tool')
1103        types, tags = pc.fastCount(results, ('portal_type','getTags'))
1104        # Convert them to tuples where (value, display value)
1105        types=zip(map(lambda x: lt.getTypeName(x), types.keys()), types.keys(), types.values())
1106        tagslist=zip(tags.keys(),tags.keys(),tags.values())
1107        tagslist.sort()
1108        return {'type':types,'tag':tagslist}
1109
1110    security.declareProtected(ADD_CONTENT_PERMISSION,'addGroup')
1111    def addGroup(self, title, join=True):
1112        """ Create a new group. """
1113        if self.getId()!='groups':
1114            return self.community.groups.addGroup(title, join)
1115        lt = getToolByName(self, 'lemill_tool')
1116        group_id = lt.normalizeString(title)
1117        group_id=self.invokeFactory('GroupBlog',group_id)
1118        group=getattr(self, group_id, None)
1119        group.edit(title=title, description="")
1120        if join:
1121            group.join_group()
1122        # Group object in uid_catalog gets title
1123        group.helper_updateCatalogUID()
1124        # Need to manually set _at_creation_flag to False, as processForm does not get called
1125        group.unmarkCreationFlag()
1126        return group           
1127
1128
1129class LargeTrashFolder(LargeSectionFolder):
1130
1131    archetype_name = "Large Trash Folder"
1132    meta_type = "Large Trash Folder"
1133
1134    allowed_content_types = ALL_CONTENT_TYPES
1135    filter_content_types = False
1136    default_view = ('lemill_trash_view')
1137    security = ClassSecurityInfo()
1138
1139       
1140registerType(LargeContentFolder, PROJECTNAME)
1141registerType(LargeActivityFolder, PROJECTNAME)
1142registerType(LargeToolFolder, PROJECTNAME)
1143registerType(LargeCommunityFolder, PROJECTNAME)
1144registerType(LargeTrashFolder, PROJECTNAME)
Note: See TracBrowser for help on using the repository browser.