root/trunk/CommonMixIn.py

Revision 3193, 34.1 kB (checked in by jukka, 2 weeks ago)

Fixed #2046, comments and commenting is included into main view.

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