source: trunk/CommonMixIn.py @ 3057

Revision 3057, 31.3 KB checked in by jukka, 9 years ago (diff)

Worked with community section and portfolios.

Line 
1#
2# This file is part of LeMill.
3#
4# LeMill is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# LeMill is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with LeMill; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
18from Products.Archetypes.public import *
19from Products.Archetypes.atapi import DisplayList
20from Products.Archetypes.utils import getRelURL, shasattr
21from Products.ZCatalog.ZCatalog import ZCatalog
22from Globals import InitializeClass
23from Products.CMFCore.utils import getToolByName
24from AccessControl import ClassSecurityInfo, Unauthorized
25from config import PROJECTNAME, MATERIAL_TYPES, DEFAULT_ICONS, TYPE_ABBREVIATIONS, TOOLS_TYPES, ACTIVITY_TYPES
26from Products.LeMill import LeMillMessageFactory as _
27from Products.CMFPlone import PloneMessageFactory as PMF
28from permissions import ModerateContent, MODIFY_CONTENT, MANAGE_PORTAL, ACCESS_CONTENT, VIEW, ADD_CONTENT_PERMISSION
29from DateTime import DateTime
30from Acquisition import aq_inner, aq_parent
31from types import MethodType as instancemethod
32import time, re, traceback, transaction
33
34# For sortable_title -method
35def zero_fill(matchobj):
36    return matchobj.group().zfill(8)
37
38# For sortable_title -method
39num_sort_regex = re.compile('\d+')
40
41default_ir_regexp = re.compile(r'^[A-Z]?[a-z]+[-.]?[-.0-9]+[0-9]+$')
42
43class CommonMixIn:
44    """Superclass for all objects."""
45
46    ############## Getters and feature checks  ##################################################
47    security = ClassSecurityInfo()
48    portlet = ''
49
50    def getDefaultIcon(self, meta_type='', obj=None):
51        """ general method for getting proper icon for object, used when only catalog-metadata is available """
52        # this combines folderish getDefaultIcon(for-this-type, object) and resource-specific object.getDefaultIcon()
53        # folderish behaviour is needed because members have these created resources-pages. 
54        if meta_type=='':
55            return  DEFAULT_ICONS[self.meta_type]
56        else:     
57            address=DEFAULT_ICONS[meta_type]
58            if address!='piece':
59                return address
60            else:
61                # getObject verified
62                obj=obj.getObject()
63                return obj.getDefaultIcon()
64
65    def hasComplexWorkflow(self):
66        """ Can have drafts or versions """
67        return False
68
69    def canDeleteOnCancel(self):
70        """ Check if object is just created and has default title and id"""
71        o_type=self.meta_type.lower()
72        title=self.title.lower()       
73        default_id= self.getId().lower().startswith(o_type)
74        no_title= not title.startswith(o_type)
75        object_age=DateTime()-DateTime(self.CreationDate())
76        return (object_age<0.1 and default_id and no_title) or not title
77
78
79    def sortable_title(self):
80        """ Helper method for to provide FieldIndex for Title.
81        """
82        title = self.Title()
83        if isinstance(title, basestring):
84            sortabletitle = title.lower().strip()
85            # Replace numbers with zero filled numbers
86            sortabletitle = num_sort_regex.sub(zero_fill, sortabletitle)
87            # Truncate to prevent bloat
88            if not isinstance(sortabletitle, unicode):
89                try:
90                    sortabletitle= unicode(sortabletitle, 'utf-8')
91                except (UnicodeDecodeError):
92                    sortabletitle=sortabletitle.decode('utf-8','replace')
93            sortabletitle = sortabletitle[:30].encode('utf-8')
94            return sortabletitle
95        return ''
96
97
98    def pretty_title_or_id(self):
99        """ add type abbreviation like [MP] before title """
100        if TYPE_ABBREVIATIONS.has_key(self.portal_type):
101            return '[%s] %s' % (TYPE_ABBREVIATIONS[self.portal_type], self.title_or_id())
102        else:
103            return '[%s] %s' % (self.portal_type, self.title_or_id())       
104
105    def getNicename(self):
106        """ this way all resources can have their titles in indexes, not only community resources """
107        return self.Title()
108
109    def isDiscussable(self):
110        """ We are not, but can be overrided by more complex resources """
111        return False
112
113    def isBranchable(self):
114        return False
115   
116    def isEmbeddable(self):
117        return False
118
119    def isDeleted(self):
120        """ returns True if state is 'deleted' """
121        return self.state=='deleted'
122
123    def isPublic(self):
124        """ returns True if state is 'public' """
125        return self.state=='public'
126
127    def isDraft(self):
128        """ returns True if state is 'draft' """
129        return self.state=='draft'
130
131    def isPrivate(self):
132        """ returns True if state is 'private' """
133        return self.state=='private'
134
135    def canIEdit(self, member=None, group=None):
136        """ Wikish workflow, so yes you can """
137        if not member:
138            lutool = getToolByName(self, 'lemill_usertool')
139            member = lutool.getAuthenticatedMember()
140        return member.getUserName() != 'Anonymous User'
141
142
143    def canIManage(self):
144        """ return True if user has a Manager role """
145        lutool = getToolByName(self, 'lemill_usertool')
146        roles = lutool.getAuthenticatedMember().getRolesInContext(self)
147        return 'Manager' in roles
148
149    def amIOwner(self):
150        """ check owner of object """
151        lutool = getToolByName(self, 'lemill_usertool')
152        roles = lutool.getAuthenticatedMember().getRolesInContext(self)
153        return 'Owner' in roles
154
155    def canIModerate(self):
156        lutool = getToolByName(self, 'lemill_usertool')
157        roles = lutool.getAuthenticatedMember().getRolesInContext(self)
158        return 'Manager' in roles or 'Reviewer' in roles
159
160    def allowOnlyBranch(self):
161        """ Branching is only for learning resources """
162        return False
163
164
165    def getMetaDescription(self):
166        """ Default, resources should override this and give something useful """
167        return "LeMill is a web community for finding, authoring and sharing learning resources."
168
169    def getDefaultPath(self, as_list=False):
170        """ Returns the path that this object should have if it is in its right place (not trashed) """       
171        #print 'physical: %s' % '/'.join( self.getPhysicalPath() )
172        path= getattr(self.__class__, 'default_location','')
173        if not path:
174            return path
175        path='/%s/%s/%s' % (self.getPhysicalPath()[1],path,self.id)
176        #print 'default: %s' % path
177        if as_list:
178            return path.split('/')
179        else:
180            return path
181
182    def getFieldHistory(self, field_name, version):
183        """ Not implemented here, but may be called by test-structures in widgets """
184        pass
185
186    def hasCorrectPath(self):
187        """ Returns true if objects path corresponds to its default path or if the object is trashed and deleted """
188        default_path=self.getDefaultPath(as_list=True)
189        actual_path=list(self.getPhysicalPath())
190        return default_path==actual_path or (actual_path[-2]=='trash' and self.getState()=='deleted')
191
192    def getMessages(self):
193        """ Returns permanent messages related to this resource (draft, is missing a language etc.) """
194        messages=[]
195        if self.isDeleted():
196            messages.append('deleted')
197        return messages
198
199
200    ####### Creating & moving ##############################
201
202    # Disabled this, as this may be the cause of our reference problems.
203    # Falls back to BaseObjects manage_afterAdd.
204    security.declarePrivate('manage_afterAdd_DISABLED')
205    def manage_afterAdd_DISABLED(self, item, container):       
206        # manage_afterAdd combines several inherited manage_afterAdds of BaseObject that deal mostly with catalogs.
207        # getRelURL seems to return unicode for some of our content and this causes catalog error.
208        # so all different catalog tools that usually use _updateCatalog as interface method instead use
209        # their catalog_object and fixed str(url) is explicitly given to them.
210
211        # method to catalog all references recursively
212        def catalogRefs(item, uc, rc):
213            if hasattr(item, '_getReferenceAnnotations'):
214                annotations = item._getReferenceAnnotations()
215            else:
216                return
217            if annotations:
218                for ref in annotations.objectValues():
219                    url = str(getRelURL(uc, ref.getPhysicalPath()))
220                    ZCatalog.catalog_object(uc, ref, url)
221                    ZCatalog.catalog_object(rc, ref, url)
222                    catalogRefs(ref, uc, rc)
223
224        # from CatalogMultiplex manage_afterAdd:
225        # this adds index to the main catalog
226        self.indexObject()
227        __traceback_info__ = (self, item, container)
228        # from Referenceable manage_afterAdd:
229        # these add indexes to uid_catalog and reference_catalog
230        rc = getToolByName(self, 'reference_catalog')
231        uc = getToolByName(self, 'uid_catalog')
232        self._register(reference_manager=rc)
233        # path needs to be fixed to str, sometimes it is unicode and this results a CatalogError
234        url = str(getRelURL(self, item.getPhysicalPath()))
235        ZCatalog.catalog_object(uc, item, url)
236        ZCatalog.catalog_object(rc, item, url)
237        catalogRefs(item, uc, rc)
238        # from BaseObject manage_afterAdd:
239        # I'm not sure what this does.
240        self.initializeLayers(item, container)       
241
242
243    security.declareProtected(MODIFY_CONTENT, 'reindexSecondaryCatalogs')
244    def reindexSecondaryCatalogs(self):
245        """ Update reference catalog and uid catalog """
246        # method to catalog all references recursively
247        item=self
248        def catalogRefs(item, uc, rc):
249            if hasattr(item, '_getReferenceAnnotations'):
250                annotations = item._getReferenceAnnotations()
251            else:
252                return
253            if annotations:
254                for ref in annotations.objectValues():
255                    url = str(getRelURL(uc, ref.getPhysicalPath()))
256                    ZCatalog.catalog_object(uc, ref, url)
257                    ZCatalog.catalog_object(rc, ref, url)
258                    catalogRefs(ref, uc, rc)
259
260        # from Referenceable manage_afterAdd:
261        # these add indexes to uid_catalog and reference_catalog
262        rc = getToolByName(self, 'reference_catalog')
263        uc = getToolByName(self, 'uid_catalog')
264        self._register(reference_manager=rc)
265        # path needs to be fixed to str, sometimes it is unicode and this results a CatalogError
266        url = str(getRelURL(self, item.getPhysicalPath()))
267        ZCatalog.catalog_object(uc, item, url)
268        ZCatalog.catalog_object(rc, item, url)
269        catalogRefs(item, uc, rc)
270   
271
272
273    security.declareProtected(MODIFY_CONTENT, 'setId')
274    def setId(self, value):
275        """Sets the object id.
276        """
277        value=str(value)
278        old_id=self.getId()
279        if (value != old_id) or isinstance(old_id, unicode):
280            parent = aq_parent(aq_inner(self))
281            if parent is not None:
282                # See Referenceable, keep refs on what is a move/rename
283                self._v_cp_refs = 1
284                parent.manage_renameObject(self.id, value)
285            self._setId(value)
286        elif type(self.getId())!=str:
287            #print 'Renaming %s' % value
288            parent = aq_parent(aq_inner(self))
289            if parent is not None:
290                # See Referenceable, keep refs on what is a move/rename
291                self._v_cp_refs = 1
292                try:
293                    # Flip
294                    parent.manage_renameObject(str(self.id), value+"flip")
295                    # Flop               
296                    parent.manage_renameObject(str(self.id), value)
297                    self._setId(value)
298                except KeyError:
299                    print '!!!!! Obj %s is so broken it has to be deleted' % self.id
300                    parent._delOb(old_id)
301                except AttributeError:
302                    pass               
303                    #print 'ok.'
304                except KeyError:
305                    delattr(parent, old_id)
306                   
307       
308    def generateNewId(self):
309        """Suggest an id for this object.
310        This id is used when automatically renaming an object after creation.
311        """
312        lemill_tool = getToolByName(self,'lemill_tool')
313        title = self.Title()
314        if not title:
315            # Can't work w/o a title
316            return None
317        return lemill_tool.normalizeString(title)
318
319
320    # Makes sure that the id is string and not unicode.
321    security.declarePrivate('_renameAfterCreation')
322    def _renameAfterCreation(self, check_auto_id=False):
323        """Renames an object like its normalized title.
324        """
325        old_id = self.getId()
326        if check_auto_id and not self._isIDAutoGenerated(old_id):
327            # No auto generated id
328            return False
329
330        new_id = str(self.generateNewId())
331        if new_id is None:
332            return False
333
334        invalid_id = True
335        check_id = getattr(self, 'check_id', None)
336        if check_id is not None:
337            invalid_id = check_id(new_id, required=1)
338
339        # If check_id told us no, or if it was not found, make sure we have an
340        # id unique in the parent folder.
341        if invalid_id:
342            unique_id = self._findUniqueId(new_id)
343            if unique_id is not None:
344                if check_id is None or check_id(new_id, required=1):
345                    new_id = unique_id
346                    invalid_id = False
347        if not invalid_id:
348            # Can't rename without a subtransaction commit when using
349            # portal_factory!
350            if new_id!=old_id:
351                transaction.savepoint(optimistic=True)
352                self.setId(str(new_id))
353            return new_id
354        return False
355
356
357    security.declarePublic('getRequiredFieldNames')
358    def getRequiredFieldNames(self):
359        """ Returns field names, not actual field objects """
360        a=[field.getName() for field in self.schema.fields() if field.required]
361        print 'getRequiredFieldNames: ', a
362        return a
363           
364
365
366    ####### Schema update ###############
367   
368    security.declareProtected(MANAGE_PORTAL, 'updateSchema')
369    def updateSchema(self):
370        """ Wrapper to make schema updates easier. Also checks if there are bad values and fixes them. Also reindexes. """
371        o=self
372        print 'checking if schema for %s is up to date...' % o.id
373        retry=False
374        # Fix too long id:s
375        if len(str(o.getId()))>220:
376            print 'id is too long. fixing it before proceeding further.'
377            id_base=str(o.getId())[:220]
378            id_candidate=id_base
379            c=0
380            while hasattr(o.aq_parent, id_candidate):
381                c+=1
382                id_candidate='%s_%s' % (id_base, c)
383            try:
384                o.setId(id_candidate)
385            except AttributeError:
386                retry=True
387            except KeyError:
388                retry=True
389        if not o._isSchemaCurrent():
390            print 'schema needs updating'
391            o._updateSchema()
392            # Fix instancemethods from field values
393#            for f in o.schema.fields():
394#                value=f.get(o)
395#                if type(value)==instancemethod:
396#                    print 'InstanceMethod found in field %s in object %s (type should be: %s)' % (f.getName(), o.getId(), f.type)
397#                    if f.type=='string':
398#                        f.set(o, '')
399#                    elif f.type=='boolean':
400#                        f.set(o, False)
401#                    elif f.type=='int':
402#                        f.set(o, 0)
403#                    elif f.type=='tuple':
404#                        f.set(o, ())
405#                    elif f.type=='list':
406#                        f.set(o, [])
407#                    else:
408#                        f.set(o, None)                                 
409            o.reindexObject()
410            print 'updated & reindexed.'
411        if retry:
412            try:
413                o.setId(id_candidate)
414            except:
415                pass       
416
417    ####### Cataloging ###################################
418
419
420
421    security.declareProtected(MODIFY_CONTENT, 'indexObject')
422    def indexObject(self):
423        print 'CommonMixIn.indexObject called.'
424        t=time.time()
425        pc = getToolByName(self, 'portal_catalog')
426        url = '/'.join( self.getPhysicalPath() )
427        #print 'indexObject called for %s (%s)' % (self.id, str(url))
428        if not isinstance(url, str):
429            url=str(url) # This gets rid of CatalogError: The object unique id must be a string.
430        ZCatalog.catalog_object(pc, self, url, [], update_metadata=1, pghandler=None)
431        print '...CommonMixIn.indexObject finished:', time.time()-t
432
433       
434    security.declareProtected(MODIFY_CONTENT, 'unindexObject')
435    def unindexObject(self):
436        pc = getToolByName(self, 'portal_catalog')
437        url = '/'.join( self.getPhysicalPath() )       
438        #print 'unindexObject called for %s (%s)' % (self.id, str(url))
439        # XXX This is an ugly workaround. This method shouldn't be called
440        # twice for an object in the first place, so we don't have to check
441        # if it is still cataloged.
442        rid = pc.getrid(url)
443        if rid is not None:
444            pc.uncatalog_object(url)
445
446
447    security.declareProtected(MODIFY_CONTENT, 'reindexObject')
448    def reindexObject(self, idxs=[]):
449        """update indexes of this object in all registered catalogs.
450        This uses ZCatalog's catalog_object instead of Plone's CatalogTool and passes workflow while at it
451        this also assumes that all LeMill objects only use portal_catalog and doesn't do multiple catalogs."""
452        print 'CommonMixIn.reindexObject called.', idxs
453        t=time.time()
454        # Never index trashed objects
455        if self.aq_parent.getId()=='trash':
456            return
457
458        if (not idxs) and shasattr(self, 'notifyModified'):
459            # Archetypes default setup has this defined in ExtensibleMetadata
460            # mixin. note: this refreshes the 'etag ' too.
461            self.notifyModified()
462        #self.http__refreshEtag()
463        url = '/'.join( self.getPhysicalPath() )
464        #print url, idxs
465        pc = getToolByName(self, 'portal_catalog')
466        if not isinstance(url, str):
467            url=str(url) # This gets rid of CatalogError: The object unique id must be a string.
468        ZCatalog.catalog_object(pc, self, url, idxs, update_metadata=1, pghandler=None)
469
470        # Remember to manually remove object from UID catalog when necessary!
471        # This is done with:
472        # self._catalogUID(self)
473        print '...CommonMixIn.reindexObject finished:', time.time()-t
474
475    security.declareProtected(MANAGE_PORTAL, 'showCatalogObject')
476    def showCatalogObject(self):
477        """ A maintenance/developer method to show the catalog data associated with this object.
478         Uses UID to find the object, so if that is broken, nothing is found."""
479         
480        pc = getToolByName(self, 'portal_catalog')
481        results = pc({'UID':self.UID()})
482        out=""
483        for catalog_object in results:
484            out+="================================================\n"
485            out+="getPath() = %s\n" % catalog_object.getPath()
486            out+="getURL() = %s\n" % catalog_object.getURL()
487            out+="getRID() = %s\n" % catalog_object.getRID()
488            out+="-------------Indexes-----------------------------\n"
489            for index in pc.indexes():
490                value= getattr(catalog_object, index, None)
491                if callable(value):
492                    value=value()
493                out+="%s = %s\n" % (index,value)
494            out+="-------------Metadata---------------------------\n"
495            for md in pc.schema():
496                out+="%s = %s\n" % (md, getattr(catalog_object, md, None))
497            out+=str(catalog_object.get_size())
498        return out
499
500    def getRawRelatedItems(self):
501        return None
502
503    ################# Collections & Stories      ###########################
504
505    def getRelatedStories(self):
506        """ fallback method if resource doesn't have this method implemented, return empty array"""
507        return []
508
509
510    def getCollectionsLen(self):
511        """ Show collections where object is used."""
512        # Need to use relationship type to get the real number of results, as there might be some additional references of some unwanted type
513        q = { 'targetUID': self.UID(), 'relationship':['relatesToContent','relatesToMethods','relatesToTools','relatesToCollections'] }
514        return len(self.reference_catalog(q))
515
516
517    ############ Discussions         #####################################
518
519    def getDiscussionMD(self):
520        """ get discussion metadata from catalog, but basic objects don't have discussions """
521        return None
522
523       
524    ############ Scoring         #####################################
525
526    def recalculateScore(self):
527        """ fallback method if resource doesn't have this method implemented, do nothing """
528        pass
529
530
531    ############ Portlets       #########################################
532
533    def getPortletPath(self):
534        """ Return a path to portlet, defined in object's class """
535        return self.__class__.portlet
536   
537    def getPortletDetails(self, REQUEST, isAnon, member):
538        """ Return an object of booleans to determine what to show and what to hide in portlet view """
539
540        v={'isDeleted':False,
541            'isPublic':False,
542            'isDraft':False,
543            'isPrivate':False,
544            'canEdit':False,
545            'canManage':False,
546            'canModerate':False,
547            'owner':False,
548            'subView':False,
549            'mainView':False,
550            'canUndelete':False,
551            'versionView':'',
552            'historyView':False,
553            'canChangeCoverImage':False,
554            'canPublish':False,
555            'canRetract':False,
556            'editLink':'',
557            'hasAbout':False,
558            'canConvert':False,
559            'aboutView':False,
560            'editMetadataLink':'',
561            'tags':[],
562            'lenCollections':0,
563            'showCollections':False,
564            'showBranches':False,
565            'branches':[],
566            'showTranslations':False,
567            'translateLink':'',
568            'translations':[],
569            'hasViews':False,
570            'studentViewLink':'',
571            'slideShowLink':'',
572            'embedCode':''}
573       
574        ################ Mandatory flags   
575
576        view_id=REQUEST['URL'].split('/')[-1]
577        version=REQUEST.get('version', '')
578        url=self.absolute_url()
579
580
581        v['isDeleted'] = isDeleted = self.isDeleted()
582        v['isPublic'] = isPublic = self.isPublic()
583        v['isDraft'] = isDraft = self.isDraft()
584        v['isPrivate'] = isPrivate = self.isPrivate()
585
586       
587        v['isAnon'] = isAnon
588        if not isAnon:
589            roles = member.getRolesInContext(self)
590            v['canManage'] = canManage = 'Manager' in roles
591            v['owner'] = owner = 'Owner' in roles
592            v['canModerate'] = canModerate = canManage or owner or 'Reviewer' in roles
593            v['canEdit'] = canEdit = self.canIEdit(member)
594        else:
595            canManage = owner = canModerate = canEdit = False
596            roles =[]
597       
598        v['subView'] = subView = (view_id in ['discussion', 'about_view', 'history_view','collections_list','manage_translations','manage_convert', 'feedback_view'] or version) and not isDeleted
599        v['mainView'] = mainView = not (subView or isDeleted or (isPrivate and not (owner or canManage) ))
600        v['canUndelete'] = canModerate and isDeleted
601
602        ################ Editing flags   
603        isMaterial= self.portal_type in MATERIAL_TYPES       
604       
605        ######### Links that should be visible in subpages
606
607        v['historyView'] = view_id == 'history_view'               
608        v['aboutView'] = aboutView =  view_id=='about_view'
609        if aboutView:
610            if isAnon:
611                v['editMetadataLink'] = 'login_form'
612            elif canEdit:
613                v['editMetadataLink'] = '%s/base_metadata' % url
614            else:
615                v['editMetadataLink'] = '%s/join_a_group' % url       
616        else:
617            v['editMetadataLink']=''
618        if version and canEdit:
619            v['versionView']= version
620
621        if version or isDeleted or not mainView:
622            return v
623
624        ###### Links that should be visible only in main pages
625
626        v['canChangeCoverImage'] = canEdit and isPublic and mainView and self.hasEditableCoverImage()
627        v['canPublish'] = canModerate and (isDraft or isPrivate)
628        v['canRetract'] = canModerate and isPublic and isMaterial and self.hasComplexWorkflow()
629        if isAnon:
630            v['editLink'] = 'login_form'
631        elif canEdit:
632            v['editLink'] = '%s/edit' % url
633        elif self.allowOnlyBranch():
634            v['editLink'] = '%s/createBranch' % url
635        else:
636            v['editLink'] = '%s/join_a_group' % url
637        v['hasAbout'] = isMaterial and not isDeleted
638        v['canConvert'] = canManage and self.portal_type in ['ExerciseMaterial','MultimediaMaterial','LeMillReference','Tool','Activity']
639        discussable = self.isDiscussable()
640        if discussable:
641            v['discussionLink'] = '%s/discussion' % url
642            v['lenDiscussion'] = self.postCount()
643        else:
644            v['discussionLink']= ''
645            v['lenDiscussion'] = 0
646
647        if hasattr(self, 'tags'):
648            v['tags']=self.getTags()
649
650        v['lenCollections'] = lenCollections = self.getCollectionsLen()
651        v['showCollections'] = lenCollections or not isAnon
652
653        # Only Material types can have branches
654        if self.hasComplexWorkflow(): # This is getting ugly
655            original=self.getBranch_of()
656            if original:
657                original=original.absolute_url()
658            v['original'] = original or False
659
660            v['branches'] = [br.absolute_url() for br in self.getBranches() if br and (br.state=='public' or br.state=='draft')]
661            v['showBranches'] = showBranches = original or v['branches']
662        else:
663            v['showBranches'] = showBranches = False
664           
665
666        v['hasViews'] = isMaterial
667        if isMaterial:
668            if self.portal_type=='PresentationMaterial':
669                v['studentViewLink']='%s/fullscreen_view' % url
670                v['slideShowLink']=url
671            else:
672                v['studentViewLink']=url
673                v['slideShowLink']=''
674           
675        v['showTranslations'] = showTranslations = (isMaterial or self.portal_type in ACTIVITY_TYPES+TOOLS_TYPES) and self.portal_type!='PresentationMaterial' # This is getting ugly
676        if self.isEmbeddable():
677            v['embedCode']=self.getEmbedCode()           
678        if not showTranslations:
679            return v # rest of the variables are useful only for translatable types, others can leave now
680        ################# Translation and student view related flags
681        if isAnon:
682            v['translateLink']= 'login_form'
683        else:
684            v['translateLink'] = '%s/translate_resource' % url
685        translations=[]
686        for md in self.getTranslationsOfOriginal(include_original=True):
687            resource={}
688            resource['isThis'] = self.id==md.getId
689            resource['url'] = md.absolute_url()
690            resource['languageName'] = self.lemill_tool.getPrettyLanguage(md.Language())
691            translations.append(resource)
692
693        v['translations']=translations
694
695        return v
696
697    ############### Setting permissions ###################
698
699    def setPermissions(self):
700        # Setting View permission
701        self.manage_permission(VIEW, ('Manager','Owner','Member','Authenticated','Anonymous'), acquire=0)
702        # Setting Edit permission
703        self.manage_permission(MODIFY_CONTENT, ('Manager','Owner','Member'), acquire=0)
704        # Setting Review permission
705        self.manage_permission(ModerateContent, ('Manager','Owner','Reviewer'), acquire=0)
706        # Setting Access permission
707        self.manage_permission(ACCESS_CONTENT, ('Manager','Owner','Member','Authenticated','Anonymous'), acquire=0)
708
709InitializeClass(CommonMixIn)
710
711
712################    Cover images class    #########################################
713
714class CoverImageMixIn:
715    """Mix-in class for resources with cover images."""
716    security = ClassSecurityInfo()
717    def getCoverOrDefault(self):
718        if self.getHasCoverImage() and self.state!='draft':
719            return self.coverImage
720        return eval('default_%s.png' % self.meta_type.lower())
721       
722       
723    def getCoverImageURL(self, drafts=False):
724        """Returns the URL for the cover image. If drafts=True, also allow drafts to show cover image"""
725        field = self.getField('hasCoverImage')
726        if field and field.get(self) and (drafts or (self.state!='draft' and self.state!='private')):
727            return self.absolute_url()+'/coverImage'
728        return self.getDefaultIcon()
729
730    security.declareProtected(MODIFY_CONTENT,'setCoverImage')
731    def setCoverImage(self, value, **kwargs):
732        """ Normal mutator, but flags object to have a coverImage (hasCoverImage = True) """
733        cover=self.getField('coverImage')
734        cover.set(self,value,**kwargs)
735        has_cover=self.getField('hasCoverImage')
736        if value==None:
737            has_cover.set(self,False)
738        else:
739            has_cover.set(self,True)
740        self.reindexCollections()
741
742    security.declareProtected(MODIFY_CONTENT,'delCoverImage')
743    def delCoverImage(self):
744        """ Reverse of setCoverImage """
745        cover=self.getField('coverImage')
746        cover.set(self, "DELETE_IMAGE")
747        has_cover=self.getField('hasCoverImage')
748        has_cover.set(self,False)
749
750    def hasEditableCoverImage(self):
751        """ Cover Image is not created automatically """
752        return True
753
754    def reindexCollections(self):
755        """ not implemented here, but called by setCoverImage... """
756        pass
757
758InitializeClass(CoverImageMixIn)
759
760####################  Redirector    #################################
761
762class Redirector(BaseContent,CommonMixIn):
763    """Redirects to new URLs of renamed resources."""
764
765    meta_type = "Redirector"
766    archetype_name = "Redirector"
767   
768    aliases = {
769        '(Default)' : 'redirect',
770        'view'      : 'redirect',
771    #    'edit'      : 'redirect',
772    #    'base_view' : 'redirect',
773    #    'history_view': 'redirect',
774    }
775
776    def __bobo_traverse__(self, REQUEST, entry_name=None):
777        """ redirect to correct object """
778        obj = self.redirect()
779        if entry_name is None:
780            return obj
781        try:
782            l = getattr(obj, entry_name)
783        except AttributeError:
784            return BaseContent.__bobo_traverse__(self, REQUEST, entry_name)
785        if hasattr(l, 'im_func'):
786            return l()
787        return l
788
789    def redirect(self, REQUEST=None):
790        """Redirect to current location."""
791        rc = getToolByName(self, 'reference_catalog')
792        obj = rc._objectByUUID(self.redirect_to)
793        if REQUEST and obj:
794            return REQUEST.RESPONSE.redirect(obj.absolute_url())
795        #return rc.lookupObject(self.redirect_to, REQUEST)
796        # rc.lookupObject returns an error if none found, so we won't use it
797
798
799registerType(Redirector)
800
801
Note: See TracBrowser for help on using the repository browser.