source: trunk/CommonMixIn.py @ 3024

Revision 3024, 29.8 KB checked in by jukka, 10 years ago (diff)

Fixed #1979

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            'tags':[],
529            'lenCollections':0,
530            'showCollections':False,
531            'showBranches':False,
532            'branches':[],
533            'showTranslations':False,
534            'translateLink':'',
535            'translations':[],
536            'hasViews':False,
537            'studentViewLink':'',
538            'slideShowLink':'',
539            'embedCode':''}
540       
541        ################ Mandatory flags   
542
543        view_id=REQUEST['URL'].split('/')[-1]
544        version=REQUEST.get('version', '')
545        url=self.absolute_url()
546
547
548        v['isDeleted'] = isDeleted = self.isDeleted()
549        v['isPublic'] = isPublic = self.isPublic()
550        v['isDraft'] = isDraft = self.isDraft()
551        v['isPrivate'] = isPrivate = self.isPrivate()
552
553       
554        v['isAnon'] = isAnon
555        if not isAnon:
556            roles = member.getRolesInContext(self)
557            v['canManage'] = canManage = 'Manager' in roles
558            v['owner'] = owner = 'Owner' in roles
559            v['canModerate'] = canModerate = canManage or owner or 'Reviewer' in roles
560            v['canEdit'] = canEdit = self.canIEdit(member)
561        else:
562            canManage = owner = canModerate = canEdit = False
563            roles =[]
564       
565        v['subView'] = subView = (view_id in ['discussion', 'about_view', 'history_view','collections_list','manage_translations','manage_convert', 'feedback_view'] or version) and not isDeleted
566        v['mainView'] = mainView = not (subView or isDeleted or (isPrivate and not (owner or canManage) ))
567        v['canUndelete'] = canModerate and isDeleted
568
569        ################ Editing flags   
570        isMaterial= self.portal_type in MATERIAL_TYPES       
571       
572        ######### Links that should be visible in subpages
573
574        v['historyView'] = view_id == 'history_view'               
575        v['aboutView'] = aboutView =  view_id=='about_view'
576        if aboutView:
577            if isAnon:
578                v['editMetadataLink'] = 'login_form'
579            elif canEdit:
580                v['editMetadataLink'] = '%s/base_metadata' % url
581            else:
582                v['editMetadataLink'] = '%s/join_a_group' % url       
583        else:
584            v['editMetadataLink']=''
585        if version and canEdit:
586            v['versionView']= version
587
588        if version or isDeleted or not mainView:
589            return v
590
591        ###### Links that should be visible only in main pages
592
593        v['canChangeCoverImage'] = canEdit and isPublic and mainView and self.hasEditableCoverImage()
594        v['canPublish'] = canModerate and (isDraft or isPrivate)
595        v['canRetract'] = canModerate and isPublic and isMaterial and self.hasComplexWorkflow()
596        if isAnon:
597            v['editLink'] = 'login_form'
598        elif canEdit:
599            v['editLink'] = '%s/edit' % url
600        elif self.allowOnlyBranch():
601            v['editLink'] = '%s/createBranch' % url
602        else:
603            v['editLink'] = '%s/join_a_group' % url
604        v['hasAbout'] = isMaterial and not isDeleted
605        v['canConvert'] = canManage and self.portal_type in ['ExerciseMaterial','MultimediaMaterial','LeMillReference','Tool','Activity']
606        discussable = self.isDiscussable()
607        if discussable:
608            v['discussionLink'] = '%s/discussion' % url
609            v['lenDiscussion'] = self.postCount()
610        else:
611            v['discussionLink']= ''
612            v['lenDiscussion'] = 0
613
614        if hasattr(self, 'tags'):
615            v['tags']=self.getTags()
616
617        v['lenCollections'] = lenCollections = self.getCollectionsLen()
618        v['showCollections'] = lenCollections or not isAnon
619
620        # Only Material types can have branches
621        if self.hasComplexWorkflow(): # This is getting ugly
622            original=self.getBranch_of()
623            if original:
624                original=original.absolute_url()
625            v['original'] = original or False
626
627            v['branches'] = [br.absolute_url() for br in self.getBranches() if br and (br.state=='public' or br.state=='draft')]
628            v['showBranches'] = showBranches = original or v['branches']
629        else:
630            v['showBranches'] = showBranches = False
631           
632
633        v['hasViews'] = isMaterial
634        if isMaterial:
635            if self.portal_type=='PresentationMaterial':
636                v['studentViewLink']='%s/fullscreen_view' % url
637                v['slideShowLink']=url
638            else:
639                v['studentViewLink']=url
640                v['slideShowLink']=''
641           
642        v['showTranslations'] = showTranslations = (isMaterial or self.portal_type in ACTIVITY_TYPES+TOOLS_TYPES) and self.portal_type!='PresentationMaterial' # This is getting ugly
643        if self.isEmbeddable():
644            v['embedCode']=self.getEmbedCode()           
645        if not showTranslations:
646            return v # rest of the variables are useful only for translatable types, others can leave now
647        ################# Translation and student view related flags
648        if isAnon:
649            v['translateLink']= 'login_form'
650        else:
651            v['translateLink'] = '%s/translate_resource' % url
652        translations=[]
653        for md in self.getTranslationsOfOriginal(include_original=True):
654            resource={}
655            resource['isThis'] = self.id==md.getId
656            resource['url'] = md.absolute_url()
657            resource['languageName'] = self.lemill_tool.getPrettyLanguage(md.Language())
658            translations.append(resource)
659
660        v['translations']=translations
661
662        return v
663
664    ############### Setting permissions ###################
665
666    def setPermissions(self):
667        # Setting View permission
668        self.manage_permission(VIEW, ('Manager','Owner','Member','Authenticated','Anonymous'), acquire=0)
669        # Setting Edit permission
670        self.manage_permission(MODIFY_CONTENT, ('Manager','Owner','Member'), acquire=0)
671        # Setting Review permission
672        self.manage_permission(ModerateContent, ('Manager','Owner','Reviewer'), acquire=0)
673        # Setting Access permission
674        self.manage_permission(ACCESS_CONTENT, ('Manager','Owner','Member','Authenticated','Anonymous'), acquire=0)
675
676InitializeClass(CommonMixIn)
677
678
679################    Cover images class    #########################################
680
681class CoverImageMixIn:
682    """Mix-in class for resources with cover images."""
683    security = ClassSecurityInfo()
684    def getCoverOrDefault(self):
685        if self.getHasCoverImage() and self.state!='draft':
686            return self.coverImage
687        return eval('default_%s.png' % self.meta_type.lower())
688       
689       
690    def getCoverImageURL(self, drafts=False):
691        """Returns the URL for the cover image. If drafts=True, also allow drafts to show cover image"""
692        field = self.getField('hasCoverImage')
693        if field and field.get(self) and (drafts or (self.state!='draft' and self.state!='private')):
694            return self.absolute_url()+'/coverImage'
695        return self.getDefaultIcon()
696
697    security.declareProtected(MODIFY_CONTENT,'setCoverImage')
698    def setCoverImage(self, value, **kwargs):
699        """ Normal mutator, but flags object to have a coverImage (hasCoverImage = True) """
700        cover=self.getField('coverImage')
701        cover.set(self,value,**kwargs)
702        has_cover=self.getField('hasCoverImage')
703        if value==None:
704            has_cover.set(self,False)
705        else:
706            has_cover.set(self,True)
707        self.reindexCollections()
708
709    security.declareProtected(MODIFY_CONTENT,'delCoverImage')
710    def delCoverImage(self):
711        """ Reverse of setCoverImage """
712        cover=self.getField('coverImage')
713        cover.set(self, "DELETE_IMAGE")
714        has_cover=self.getField('hasCoverImage')
715        has_cover.set(self,False)
716
717    def hasEditableCoverImage(self):
718        """ Cover Image is not created automatically """
719        return True
720
721    def reindexCollections(self):
722        """ not implemented here, but called by setCoverImage... """
723        pass
724
725InitializeClass(CoverImageMixIn)
726
727####################  Redirector    #################################
728
729class Redirector(BaseContent,CommonMixIn):
730    """Redirects to new URLs of renamed resources."""
731
732    meta_type = "Redirector"
733    archetype_name = "Redirector"
734   
735    aliases = {
736        '(Default)' : 'redirect',
737        'view'      : 'redirect',
738    #    'edit'      : 'redirect',
739    #    'base_view' : 'redirect',
740    #    'history_view': 'redirect',
741    }
742
743    def __bobo_traverse__(self, REQUEST, entry_name=None):
744        """ redirect to correct object """
745        obj = self.redirect()
746        if entry_name is None:
747            return obj
748        try:
749            l = getattr(obj, entry_name)
750        except AttributeError:
751            return BaseContent.__bobo_traverse__(self, REQUEST, entry_name)
752        if hasattr(l, 'im_func'):
753            return l()
754        return l
755
756    def redirect(self, REQUEST=None):
757        """Redirect to current location."""
758        rc = getToolByName(self, 'reference_catalog')
759        return rc.lookupObject(self.redirect_to, REQUEST)
760
761registerType(Redirector)
762
763
Note: See TracBrowser for help on using the repository browser.