source: trunk/CommonMixIn.py @ 3010

Revision 3010, 29.7 KB checked in by jukka, 10 years ago (diff)

PILOTs can now be embedded. Unified to use same portlet macro as embedding collections.

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
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    def getDefaultPath(self, as_list=False):
165        """ Returns the path that this object should have if it is in its right place (not trashed) """       
166        #print 'physical: %s' % '/'.join( self.getPhysicalPath() )
167        path= getattr(self.__class__, 'default_location','')
168        if not path:
169            return path
170        path='/%s/%s/%s' % (self.getPhysicalPath()[1],path,self.id)
171        #print 'default: %s' % path
172        if as_list:
173            return path.split('/')
174        else:
175            return path
176
177    def getFieldHistory(self, field_name, version):
178        """ Not implemented here, but may be called by test-structures in widgets """
179        pass
180
181    def hasCorrectPath(self):
182        """ Returns true if objects path corresponds to its default path or if the object is trashed and deleted """
183        default_path=self.getDefaultPath(as_list=True)
184        actual_path=list(self.getPhysicalPath())
185        return default_path==actual_path or (actual_path[-2]=='trash' and self.getState()=='deleted')
186
187
188    ####### Creating & moving ##############################
189
190    # Disabled this, as this may be the cause of our reference problems.
191    # Falls back to BaseObjects manage_afterAdd.
192    security.declarePrivate('manage_afterAdd_DISABLED')
193    def manage_afterAdd_DISABLED(self, item, container):       
194        # manage_afterAdd combines several inherited manage_afterAdds of BaseObject that deal mostly with catalogs.
195        # getRelURL seems to return unicode for some of our content and this causes catalog error.
196        # so all different catalog tools that usually use _updateCatalog as interface method instead use
197        # their catalog_object and fixed str(url) is explicitly given to them.
198
199        # method to catalog all references recursively
200        def catalogRefs(item, uc, rc):
201            if hasattr(item, '_getReferenceAnnotations'):
202                annotations = item._getReferenceAnnotations()
203            else:
204                return
205            if annotations:
206                for ref in annotations.objectValues():
207                    url = str(getRelURL(uc, ref.getPhysicalPath()))
208                    ZCatalog.catalog_object(uc, ref, url)
209                    ZCatalog.catalog_object(rc, ref, url)
210                    catalogRefs(ref, uc, rc)
211
212        # from CatalogMultiplex manage_afterAdd:
213        # this adds index to the main catalog
214        self.indexObject()
215        __traceback_info__ = (self, item, container)
216        # from Referenceable manage_afterAdd:
217        # these add indexes to uid_catalog and reference_catalog
218        rc = getToolByName(self, 'reference_catalog')
219        uc = getToolByName(self, 'uid_catalog')
220        self._register(reference_manager=rc)
221        # path needs to be fixed to str, sometimes it is unicode and this results a CatalogError
222        url = str(getRelURL(self, item.getPhysicalPath()))
223        ZCatalog.catalog_object(uc, item, url)
224        ZCatalog.catalog_object(rc, item, url)
225        catalogRefs(item, uc, rc)
226        # from BaseObject manage_afterAdd:
227        # I'm not sure what this does.
228        self.initializeLayers(item, container)       
229
230
231    security.declareProtected(MODIFY_CONTENT, 'reindexSecondaryCatalogs')
232    def reindexSecondaryCatalogs(self):
233        """ Update reference catalog and uid catalog """
234        # method to catalog all references recursively
235        item=self
236        def catalogRefs(item, uc, rc):
237            if hasattr(item, '_getReferenceAnnotations'):
238                annotations = item._getReferenceAnnotations()
239            else:
240                return
241            if annotations:
242                for ref in annotations.objectValues():
243                    url = str(getRelURL(uc, ref.getPhysicalPath()))
244                    ZCatalog.catalog_object(uc, ref, url)
245                    ZCatalog.catalog_object(rc, ref, url)
246                    catalogRefs(ref, uc, rc)
247
248        # from Referenceable manage_afterAdd:
249        # these add indexes to uid_catalog and reference_catalog
250        rc = getToolByName(self, 'reference_catalog')
251        uc = getToolByName(self, 'uid_catalog')
252        self._register(reference_manager=rc)
253        # path needs to be fixed to str, sometimes it is unicode and this results a CatalogError
254        url = str(getRelURL(self, item.getPhysicalPath()))
255        ZCatalog.catalog_object(uc, item, url)
256        ZCatalog.catalog_object(rc, item, url)
257        catalogRefs(item, uc, rc)
258   
259
260
261    security.declareProtected(MODIFY_CONTENT, 'setId')
262    def setId(self, value):
263        """Sets the object id.
264        """
265        value=str(value)
266        old_id=self.getId()
267        if (value != old_id) or isinstance(old_id, unicode):
268            parent = aq_parent(aq_inner(self))
269            if parent is not None:
270                # See Referenceable, keep refs on what is a move/rename
271                self._v_cp_refs = 1
272                parent.manage_renameObject(self.id, value)
273            self._setId(value)
274        elif type(self.getId())!=str:
275            #print 'Renaming %s' % value
276            parent = aq_parent(aq_inner(self))
277            if parent is not None:
278                # See Referenceable, keep refs on what is a move/rename
279                self._v_cp_refs = 1
280                try:
281                    # Flip
282                    parent.manage_renameObject(str(self.id), value+"flip")
283                    # Flop               
284                    parent.manage_renameObject(str(self.id), value)
285                    self._setId(value)
286                except KeyError:
287                    print '!!!!! Obj %s is so broken it has to be deleted' % self.id
288                    parent._delOb(old_id)
289                except AttributeError:
290                    pass               
291                    #print 'ok.'
292                except KeyError:
293                    delattr(parent, old_id)
294                   
295       
296    def generateNewId(self):
297        """Suggest an id for this object.
298        This id is used when automatically renaming an object after creation.
299        """
300        lemill_tool = getToolByName(self,'lemill_tool')
301        title = self.Title()
302        if not title:
303            # Can't work w/o a title
304            return None
305        return lemill_tool.normalizeString(title)
306
307
308    # Makes sure that the id is string and not unicode.
309    security.declarePrivate('_renameAfterCreation')
310    def _renameAfterCreation(self, check_auto_id=False):
311        """Renames an object like its normalized title.
312        """
313        old_id = self.getId()
314        if check_auto_id and not self._isIDAutoGenerated(old_id):
315            # No auto generated id
316            return False
317
318        new_id = str(self.generateNewId())
319        if new_id is None:
320            return False
321
322        invalid_id = True
323        check_id = getattr(self, 'check_id', None)
324        if check_id is not None:
325            invalid_id = check_id(new_id, required=1)
326
327        # If check_id told us no, or if it was not found, make sure we have an
328        # id unique in the parent folder.
329        if invalid_id:
330            unique_id = self._findUniqueId(new_id)
331            if unique_id is not None:
332                if check_id is None or check_id(new_id, required=1):
333                    new_id = unique_id
334                    invalid_id = False
335        if not invalid_id:
336            # Can't rename without a subtransaction commit when using
337            # portal_factory!
338            if new_id!=old_id:
339                transaction.savepoint(optimistic=True)
340                self.setId(str(new_id))
341            return new_id
342        return False
343
344    ####### Schema update ###############
345   
346    security.declareProtected(MANAGE_PORTAL, 'updateSchema')
347    def updateSchema(self):
348        """ Wrapper to make schema updates easier. Also checks if there are bad values and fixes them. Also reindexes. """
349        o=self
350        retry=False
351        # Fix too long id:s
352        if len(str(o.getId()))>220:
353            id_base=str(o.getId())[:220]
354            id_candidate=id_base
355            c=0
356            while hasattr(o.aq_parent, id_candidate):
357                c+=1
358                id_candidate='%s_%s' % (id_base, c)
359            try:
360                o.setId(id_candidate)
361            except AttributeError:
362                retry=True
363            except KeyError:
364                retry=True
365        if not o._isSchemaCurrent():
366            o._updateSchema()
367            # Fix instancemethods from field values
368#            for f in o.schema.fields():
369#                value=f.get(o)
370#                if type(value)==instancemethod:
371#                    print 'InstanceMethod found in field %s in object %s (type should be: %s)' % (f.getName(), o.getId(), f.type)
372#                    if f.type=='string':
373#                        f.set(o, '')
374#                    elif f.type=='boolean':
375#                        f.set(o, False)
376#                    elif f.type=='int':
377#                        f.set(o, 0)
378#                    elif f.type=='tuple':
379#                        f.set(o, ())
380#                    elif f.type=='list':
381#                        f.set(o, [])
382#                    else:
383#                        f.set(o, None)                                 
384            o.reindexObject()
385        if retry:
386            try:
387                o.setId(id_candidate)
388            except:
389                pass       
390
391    ####### Cataloging ###################################
392
393
394
395    security.declareProtected(MODIFY_CONTENT, 'indexObject')
396    def indexObject(self):
397        pc = getToolByName(self, 'portal_catalog')
398        url = '/'.join( self.getPhysicalPath() )
399        #print 'indexObject called for %s (%s)' % (self.id, str(url))
400        if not isinstance(url, str):
401            url=str(url) # This gets rid of CatalogError: The object unique id must be a string.
402        ZCatalog.catalog_object(pc, self, url, [], update_metadata=1, pghandler=None)
403       
404    security.declareProtected(MODIFY_CONTENT, 'unindexObject')
405    def unindexObject(self):
406        pc = getToolByName(self, 'portal_catalog')
407        url = '/'.join( self.getPhysicalPath() )       
408        #print 'unindexObject called for %s (%s)' % (self.id, str(url))
409        # XXX This is an ugly workaround. This method shouldn't be called
410        # twice for an object in the first place, so we don't have to check
411        # if it is still cataloged.
412        rid = pc.getrid(url)
413        if rid is not None:
414            pc.uncatalog_object(url)
415
416
417    security.declareProtected(MODIFY_CONTENT, 'reindexObject')
418    def reindexObject(self, idxs=[]):
419        """update indexes of this object in all registered catalogs.
420        This uses ZCatalog's catalog_object instead of Plone's CatalogTool and passes workflow while at it
421        this also assumes that all LeMill objects only use portal_catalog and doesn't do multiple catalogs."""
422        # Never index trashed objects
423        if self.aq_parent.getId()=='trash':
424            return
425
426        if (not idxs) and shasattr(self, 'notifyModified'):
427            # Archetypes default setup has this defined in ExtensibleMetadata
428            # mixin. note: this refreshes the 'etag ' too.
429            self.notifyModified()
430        #self.http__refreshEtag()
431        url = '/'.join( self.getPhysicalPath() )
432        #print url, idxs
433        pc = getToolByName(self, 'portal_catalog')
434        if not isinstance(url, str):
435            url=str(url) # This gets rid of CatalogError: The object unique id must be a string.
436        ZCatalog.catalog_object(pc, self, url, idxs, update_metadata=1, pghandler=None)
437
438        # Remember to manually remove object from UID catalog when necessary!
439        # This is done with:
440        # self._catalogUID(self)
441
442    security.declareProtected(MANAGE_PORTAL, 'showCatalogObject')
443    def showCatalogObject(self):
444        """ A maintenance/developer method to show the catalog data associated with this object.
445         Uses UID to find the object, so if that is broken, nothing is found."""
446         
447        pc = getToolByName(self, 'portal_catalog')
448        results = pc({'UID':self.UID()})
449        out=""
450        for catalog_object in results:
451            out+="================================================\n"
452            out+="getPath() = %s\n" % catalog_object.getPath()
453            out+="getURL() = %s\n" % catalog_object.getURL()
454            out+="getRID() = %s\n" % catalog_object.getRID()
455            out+="-------------Indexes-----------------------------\n"
456            for index in pc.indexes():
457                value= getattr(catalog_object, index, None)
458                if callable(value):
459                    value=value()
460                out+="%s = %s\n" % (index,value)
461            out+="-------------Metadata---------------------------\n"
462            for md in pc.schema():
463                out+="%s = %s\n" % (md, getattr(catalog_object, md, None))
464            out+=str(catalog_object.get_size())
465        return out
466
467    def getRawRelatedItems(self):
468        return None
469
470    ################# Collections & Stories      ###########################
471
472    def getRelatedStories(self):
473        """ fallback method if resource doesn't have this method implemented, return empty array"""
474        return []
475
476
477    def getCollectionsLen(self):
478        """ Show collections where object is used."""
479        # Need to use relationship type to get the real number of results, as there might be some additional references of some unwanted type
480        q = { 'targetUID': self.UID(), 'relationship':['relatesToContent','relatesToMethods','relatesToTools','relatesToCollections'] }
481        return len(self.reference_catalog(q))
482
483
484    ############ Discussions         #####################################
485
486    def getDiscussionMD(self):
487        """ get discussion metadata from catalog, but basic objects don't have discussions """
488        return None
489
490       
491    ############ Scoring         #####################################
492
493    def recalculateScore(self):
494        """ fallback method if resource doesn't have this method implemented, do nothing """
495        pass
496
497
498    ############ Portlets       #########################################
499
500    def getPortletPath(self):
501        """ Return a path to portlet, defined in object's class """
502        return self.__class__.portlet
503   
504    def getPortletDetails(self, REQUEST, isAnon, member):
505        """ Return an object of booleans to determine what to show and what to hide in portlet view """
506
507        v={'isDeleted':False,
508            'isPublic':False,
509            'isDraft':False,
510            'isPrivate':False,
511            'canEdit':False,
512            'canManage':False,
513            'canModerate':False,
514            'owner':False,
515            'subView':False,
516            'mainView':False,
517            'canUndelete':False,
518            'versionView':'',
519            'historyView':False,
520            'canChangeCoverImage':False,
521            'canPublish':False,
522            'canRetract':False,
523            'editLink':'',
524            'hasAbout':False,
525            'canConvert':False,
526            'aboutView':False,
527            'editMetadataLink':'',
528            'lenCollections':0,
529            'showCollections':False,
530            'showBranches':False,
531            'branches':[],
532            'showTranslations':False,
533            'translateLink':'',
534            'translations':[],
535            'hasViews':False,
536            'studentViewLink':'',
537            'slideShowLink':'',
538            'embedCode':''}
539       
540        ################ Mandatory flags   
541
542        view_id=REQUEST['URL'].split('/')[-1]
543        version=REQUEST.get('version', '')
544        url=self.absolute_url()
545
546
547        v['isDeleted'] = isDeleted = self.isDeleted()
548        v['isPublic'] = isPublic = self.isPublic()
549        v['isDraft'] = isDraft = self.isDraft()
550        v['isPrivate'] = isPrivate = self.isPrivate()
551
552       
553        v['isAnon'] = isAnon
554        if not isAnon:
555            roles = member.getRolesInContext(self)
556            v['canManage'] = canManage = 'Manager' in roles
557            v['owner'] = owner = 'Owner' in roles
558            v['canModerate'] = canModerate = canManage or owner or 'Reviewer' in roles
559            v['canEdit'] = canEdit = self.canIEdit(member)
560        else:
561            canManage = owner = canModerate = canEdit = False
562            roles =[]
563       
564        v['subView'] = subView = (view_id in ['discussion', 'about_view', 'history_view','collections_list','manage_translations','manage_convert'] or version) and not isDeleted
565        v['mainView'] = mainView = not (subView or isDeleted or (isPrivate and not (owner or canManage) ))
566        v['canUndelete'] = canModerate and isDeleted
567
568        ################ Editing flags   
569        isMaterial= self.portal_type in MATERIAL_TYPES       
570       
571        ######### Links that should be visible in subpages
572
573        v['historyView'] = view_id == 'history_view'               
574        v['aboutView'] = aboutView =  view_id=='about_view'
575        if aboutView:
576            if isAnon:
577                v['editMetadataLink'] = 'login_form'
578            elif canEdit:
579                v['editMetadataLink'] = '%s/base_metadata' % url
580            else:
581                v['editMetadataLink'] = '%s/join_a_group' % url       
582        else:
583            v['editMetadataLink']=''
584        if version and canEdit:
585            v['versionView']= version
586
587        if version or isDeleted or not mainView:
588            return v
589
590        ###### Links that should be visible only in main pages
591
592        v['canChangeCoverImage'] = canEdit and isPublic and mainView and self.hasEditableCoverImage()
593        v['canPublish'] = canModerate and (isDraft or isPrivate)
594        v['canRetract'] = canModerate and isPublic and isMaterial and self.hasComplexWorkflow()
595        if isAnon:
596            v['editLink'] = 'login_form'
597        elif canEdit:
598            v['editLink'] = '%s/edit' % url
599        elif self.allowOnlyBranch():
600            v['editLink'] = '%s/createBranch' % url
601        else:
602            v['editLink'] = '%s/join_a_group' % url
603        v['hasAbout'] = isMaterial and not isDeleted
604        v['canConvert'] = canManage and self.portal_type in ['ExerciseMaterial','MultimediaMaterial','LeMillReference','Tool','Activity']
605        discussable = self.isDiscussable()
606        if discussable:
607            v['discussionLink'] = '%s/discussion' % url
608            v['lenDiscussion'] = self.postCount()
609        else:
610            v['discussionLink']= ''
611            v['lenDiscussion'] = 0
612
613        v['lenCollections'] = lenCollections = self.getCollectionsLen()
614        v['showCollections'] = lenCollections or not isAnon
615
616        # Only Material types can have branches
617        if self.hasComplexWorkflow(): # This is getting ugly
618            original=self.getBranch_of()
619            if original:
620                original=original.absolute_url()
621            v['original'] = original or False
622
623            v['branches'] = [br.absolute_url() for br in self.getBranches() if br and (br.state=='public' or br.state=='draft')]
624            v['showBranches'] = showBranches = original or v['branches']
625        else:
626            v['showBranches'] = showBranches = False
627           
628
629        v['hasViews'] = isMaterial
630        if isMaterial:
631            if self.portal_type=='PresentationMaterial':
632                v['studentViewLink']='%s/fullscreen_view' % url
633                v['slideShowLink']=url
634            else:
635                v['studentViewLink']=url
636                v['slideShowLink']=''
637           
638        v['showTranslations'] = showTranslations = (isMaterial or self.portal_type in ACTIVITY_TYPES+TOOLS_TYPES) and self.portal_type!='PresentationMaterial' # This is getting ugly
639        if self.isEmbeddable():
640            v['embedCode']=self.getEmbedCode()           
641        if not showTranslations:
642            return v # rest of the variables are useful only for translatable types, others can leave now
643        ################# Translation and student view related flags
644        if isAnon:
645            v['translateLink']= 'login_form'
646        else:
647            v['translateLink'] = '%s/translate_resource' % url
648        translations=[]
649        for md in self.getTranslationsOfOriginal(include_original=True):
650            resource={}
651            resource['isThis'] = self.id==md.getId
652            resource['url'] = md.absolute_url()
653            resource['languageName'] = self.lemill_tool.getPrettyLanguage(md.Language())
654            translations.append(resource)
655
656        v['translations']=translations
657
658        return v
659
660    ############### Setting permissions ###################
661
662    def setPermissions(self):
663        # Setting View permission
664        self.manage_permission(VIEW, ('Manager','Owner','Member','Authenticated','Anonymous'), acquire=0)
665        # Setting Edit permission
666        self.manage_permission(MODIFY_CONTENT, ('Manager','Owner','Member'), acquire=0)
667        # Setting Review permission
668        self.manage_permission(ModerateContent, ('Manager','Owner','Reviewer'), acquire=0)
669        # Setting Access permission
670        self.manage_permission(ACCESS_CONTENT, ('Manager','Owner','Member','Authenticated','Anonymous'), acquire=0)
671
672InitializeClass(CommonMixIn)
673
674
675################    Cover images class    #########################################
676
677class CoverImageMixIn:
678    """Mix-in class for resources with cover images."""
679    security = ClassSecurityInfo()
680    def getCoverOrDefault(self):
681        if self.getHasCoverImage() and self.state!='draft':
682            return self.coverImage
683        return eval('default_%s.png' % self.meta_type.lower())
684       
685       
686    def getCoverImageURL(self, drafts=False):
687        """Returns the URL for the cover image. If drafts=True, also allow drafts to show cover image"""
688        field = self.getField('hasCoverImage')
689        if field and field.get(self) and (drafts or (self.state!='draft' and self.state!='private')):
690            return self.absolute_url()+'/coverImage'
691        return self.getDefaultIcon()
692
693    security.declareProtected(MODIFY_CONTENT,'setCoverImage')
694    def setCoverImage(self, value, **kwargs):
695        """ Normal mutator, but flags object to have a coverImage (hasCoverImage = True) """
696        cover=self.getField('coverImage')
697        cover.set(self,value,**kwargs)
698        has_cover=self.getField('hasCoverImage')
699        if value==None:
700            has_cover.set(self,False)
701        else:
702            has_cover.set(self,True)
703        self.reindexCollections()
704
705    security.declareProtected(MODIFY_CONTENT,'delCoverImage')
706    def delCoverImage(self):
707        """ Reverse of setCoverImage """
708        cover=self.getField('coverImage')
709        cover.set(self, "DELETE_IMAGE")
710        has_cover=self.getField('hasCoverImage')
711        has_cover.set(self,False)
712
713    def hasEditableCoverImage(self):
714        """ Cover Image is not created automatically """
715        return True
716
717    def reindexCollections(self):
718        """ not implemented here, but called by setCoverImage... """
719        pass
720
721InitializeClass(CoverImageMixIn)
722
723####################  Redirector    #################################
724
725class Redirector(BaseContent,CommonMixIn):
726    """Redirects to new URLs of renamed resources."""
727
728    meta_type = "Redirector"
729    archetype_name = "Redirector"
730   
731    aliases = {
732        '(Default)' : 'redirect',
733        'view'      : 'redirect',
734    #    'edit'      : 'redirect',
735    #    'base_view' : 'redirect',
736    #    'history_view': 'redirect',
737    }
738
739    def __bobo_traverse__(self, REQUEST, entry_name=None):
740        """ redirect to correct object """
741        obj = self.redirect()
742        if entry_name is None:
743            return obj
744        try:
745            l = getattr(obj, entry_name)
746        except AttributeError:
747            return BaseContent.__bobo_traverse__(self, REQUEST, entry_name)
748        if hasattr(l, 'im_func'):
749            return l()
750        return l
751
752    def redirect(self, REQUEST=None):
753        """Redirect to current location."""
754        rc = getToolByName(self, 'reference_catalog')
755        return rc.lookupObject(self.redirect_to, REQUEST)
756
757registerType(Redirector)
758
759
Note: See TracBrowser for help on using the repository browser.