root/trunk/LearningResource.py

Revision 3123, 16.7 kB (checked in by jukka, 1 year ago)

Removed branching and added links to collections in member page.

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 Globals import InitializeClass
20 from Products.CMFCore.utils import getToolByName
21 from AccessControl import ClassSecurityInfo, Unauthorized
22 from messagefactory_ import i18nme as _
23 from permissions import ModerateContent, MODIFY_CONTENT, DELETE_CONTENT, MANAGE_PORTAL, ACCESS_CONTENT, VIEW, ADD_CONTENT_PERMISSION
24 from Products.CMFPlone.utils import transaction_note
25 from Acquisition import aq_inner, aq_parent
26 import re, copy
27 from Resource import Resource
28 from Discussable import Discussable
29 from config import LANGUAGES
30
31 # This is used by pdf cleaning method prepareForPDF
32 #visualNoPrint = re.compile('(<h1>.*?)<div class="visualNoPrint">', re.DOTALL | re.IGNORECASE | re.MULTILINE)
33 visualNoPrint = re.compile('<div class="visualNoPrint">.*?</div>', re.DOTALL | re.IGNORECASE | re.MULTILINE)
34 visualClear = re.compile('<div class="visualClear">.*?</div>', re.DOTALL | re.IGNORECASE | re.MULTILINE)
35 embedContent = re.compile('<div class="embed_content">.*?</div>',re.DOTALL | re.IGNORECASE | re.MULTILINE)
36
37 t_license_pattern = re.compile('<p id="license_text_box">.*?</p>', re.MULTILINE | re.DOTALL | re.IGNORECASE)
38 license_pattern = re.compile('<!--Creative Commons License-->.*?<!--/Creative Commons License-->', re.DOTALL | re.IGNORECASE | re.MULTILINE)
39
40
41 ################## Learning Resource #################################################
42
43 class LearningResource(Discussable, Resource):
44     """Superclass for all learning resources (material, activity, tool)."""
45
46     security = ClassSecurityInfo()
47     allow_discussion = True
48
49     ################ Messages #################################################
50
51     def getMessages(self):
52         """ Returns permanent messages related to this resource (draft, is missing a language etc.) """
53         messages=Resource.getMessages(self)
54         if self.isDraft():
55             messages.append('draft')
56         if self.isPrivate():
57             messages.append('private')
58         return messages
59
60
61     ################ After add/edit #################################################
62
63     def manage_afterAdd(self, item, container):
64         Resource.manage_afterAdd(self, item, container)
65
66
67     security.declarePrivate('manage_beforeDelete')
68     def manage_beforeDelete(self, item, container):
69         """ When deleting, remove self from lists of translations and remove related discussions. """       
70         storeRefs = getattr(item, '_v_cp_refs', None)
71         if self.Schema().has_key('translation_of'):
72             translation_parent= self.getTranslation_of()
73             if translation_parent and not storeRefs:
74                 translation_parent.removeTranslation(self.UID())
75         # Currently it is a bit improbable that just created
76         # object has discussions, but keep it robust for further changes.
77         discussion=self.getDiscussion(do_create=False)
78         if discussion and not storeRefs:
79             discussion.aq_parent.manage_delObjects([discussion.id])
80         # Groups do not refer back to objects, only objects refer to groups, so that doesn't
81         # require additional work
82         Resource.manage_beforeDelete(self, item, container)
83
84     security.declarePrivate('at_post_create_script')
85     def at_post_create_script(self):
86         # check if this is a translation of some object
87         if self.Schema().has_key('translation_of'):
88             trans_of=self.getTranslation_of()
89             if trans_of:
90                 trans_of.addToTranslations(self.UID())
91                 # by default translation will get state draft and resource is visible to others
92                 if self.meta_type in self.getMaterialTypes():
93                     self.setState('draft')
94                     self.setHideDrafts(False)
95                 else:
96                     self.setState('public')
97         self.at_post_edit_script()
98         if self.getState()=='public':
99             self.notifyPublishedResource()
100
101
102     ############ Cover image        #############################################
103
104     def __updateCoverImage(self):
105         # Set cover image
106         refs = None
107         try:
108             refs = self.getField('refsToImages').get(self)
109         except AttributeError: # just in case...
110             return
111         cover=self.getField('coverImage')
112         has_cover=self.getField('hasCoverImage')
113         if refs:
114             first=refs[0]
115             value = first.getField('file').get(first)
116             cover.set(self,value)
117             has_cover.set(self,True)
118         else:
119             cover.set(self,None)
120             has_cover.set(self,False)
121
122
123
124
125     ############ Workflow methods #################################
126
127     def canDeleteOnCancel(self):
128         """ LearningResources have translations that should be deleted if they have been just created,
129             also anything that has no history past the author's changes in ten minutes. """
130         if self.isJustCreated():
131             return True
132         history=self.getHistory()
133         if not history:
134             return True
135         summary=history[0].get('_summary','')
136         if summary.startswith('Translation of '):  # this is a newly created translation
137             return True 
138         return Resource.canDeleteOnCancel(self)
139
140
141     ################# Authorship and groups  ###################################################################
142
143     security.declarePublic('canIEdit')
144     def canIEdit(self, member=None, group=None):
145         """ Optimized version, doesn't use easier get-methods as this gets called so often """
146         lutool = getToolByName(self, 'lemill_usertool')
147         if lutool.isAnonymousUser():
148             return False       
149         if member:
150             member_id=member.getId()
151         else:
152             member_id = lutool.getAuthenticatedId()
153         if not getattr(self, 'getGroupEditing', False):
154             return True
155         if group:
156             return member_id in group.getGroupMembers
157         group_md=self.getGroupInfo()
158         if not group_md:
159             return True
160         for group in group_md:
161             if member_id in group.getGroupMembers:
162                 return True
163         return False
164
165     def getGroupInfo(self):
166         """ Returns catalog metadata of groups editing this content """
167         field=self.getField('groupEditing')
168         if not field:
169             return []
170         uid_list=field.getRaw(self)       
171         if not uid_list:
172             return []
173         pc = getToolByName(self, 'portal_catalog')
174         return pc(UID=uid_list, portal_type='GroupBlog', sort_on='sortable_title')
175                
176     def joinGroupEditing(self, group_uid=None):
177         """ join to a group working on this material """
178         group=None
179         if group_uid:
180             utool = getToolByName(self, 'uid_catalog')
181             results=utool({'UID':group_uid})
182             if results:
183                 group=results[0].getObject()           
184         else:
185             groups=self.getGroupEditing()
186             if groups:
187                 group=groups[0]
188            
189         if group:
190             group.join_group()
191         return self.REQUEST.RESPONSE.redirect('%s/edit' % self.absolute_url())
192
193     def allowSettingOfEditingPermissions(self):
194         """ Only authors and moderators can hide drafts or prevent editing """
195         # if translation, don't show editing_settings fields
196         return (self.amIOwner() or self.canIModerate()) and not self.getRawTranslation_of()
197
198
199     ############## Translations  ##############################################################
200
201     # getTranslations, getTranslation_of and setTranslations, setTranslation_of are basic archetype generated methods
202
203     security.declareProtected(ADD_CONTENT_PERMISSION, 'createTranslation')
204     def createTranslation(self, REQUEST):
205         lutool=getToolByName(self, 'lemill_usertool')
206         parent = aq_parent(aq_inner(self))
207         new = parent.lemill_invokeFactory(parent, self.meta_type, id=self.meta_type.lower()+str(id(self)), do_create=True)
208         to_language=REQUEST.form.get('translanguage','')
209         new.setState('deleted')
210         base_obj=self
211
212         for field in base_obj.schema.values():
213             # list all fields here that shouldn't be copyied to new object
214             if not getattr(field, 'copied_in_translation', False) or field.getName() in ['id', 'groupEditing']:
215                 continue
216             old_accessor = field.getEditAccessor(base_obj)
217             new_mutator = field.getMutator(new)
218             if not old_accessor:
219                 continue
220             val = old_accessor()
221             copied_val = None
222             try:
223                 copied_val = copy.copy(val)
224             except TypeError:
225                 copied_val = copy.copy(val.aq_base)
226             new_mutator(copied_val)
227
228         realbase=base_obj.getTranslation_of() # if this is a translation of translation
229         if not realbase:
230             realbase=base_obj
231         baseuid=realbase.UID()
232         realbase.addToTranslations(new.UID())
233         new.setTranslation_of(baseuid)
234         new.setLanguage(to_language)
235         author=lutool.getAuthenticatedId()
236         new.setCreators([author])
237         new.storeInHistory({}, summary='Translation of %s created' % base_obj.title_or_id())
238                
239         return REQUEST.RESPONSE.redirect('%s/base_translate' % new.absolute_url())       
240
241
242     security.declarePublic('languagesNotTranslated')
243     def languagesNotTranslated(self):
244         """ List of languages minus list of existing translations """
245         transcodes = set([x.Language() for x in self.getTranslationsOfOriginal(include_original=True)])
246         return [x for x in LANGUAGES[1:] if x[0] not in transcodes]
247
248
249     security.declarePublic('getTranslationsOfOriginal')
250     def getTranslationsOfOriginal(self, include_original=True):
251         """ Returns translations from mother or if no such thing, translations from self. """
252         if not hasattr(self, 'getTranslation_of'):
253             return []
254         original=self.getTranslation_of() or self
255         translations=original.getTranslations()       
256         good_translations = [tr for tr in translations if tr and tr.state!='deleted']
257         # fix lists if there were problems
258         if len(good_translations)<len(translations):
259             original.setTranslations(good_translations)               
260         # include original or self into results
261         if include_original:
262             good_translations.append(original)
263         return good_translations
264
265     def getOriginal(self):
266         """ accessor plus check if deleted """
267         if hasattr(self, 'getTranslation_of'):
268             mother=self.getTranslation_of()
269             if (mother and mother.state != 'deleted'):
270                 return mother
271         return False
272
273     def addToTranslations(self, UID):
274         """ Makes sure that obj UID is in translations, but doesn't check if this is mother of translations"""
275         #print 'add to translations with obj %s and UID %s' % (self, UID)
276         trans_list = self.getRawTranslations()
277         if UID not in trans_list:
278             trans_list.append(UID)
279             self.setTranslations(trans_list)       
280
281     def removeFromTranslations(self, UID):
282         """ remove """
283         trans_list = self.getRawTranslations()
284         if UID in trans_list:
285             trans_list.remove(UID)
286             self.setTranslations(trans_list)       
287
288     security.declareProtected(MANAGE_PORTAL, 'manage_form_setTranslationOf')
289     def manage_form_setTranslationOf(self, REQUEST):
290         """ Allows managers to reorganize which is translated by whom """
291         lt = getToolByName(self, "lemill_tool")
292         mothers_id = REQUEST.get('mother_field')
293         folder = self.getSectionFolder()
294         old_mother=self.getTranslation_of()
295         if not mothers_id and old_mother: # Removing mother
296             self.setTranslation_of('')
297             old_mother.removeFromTranslations(self.UID())
298         elif ((not old_mother) or mothers_id!=old_mother.getId()) and hasattr(folder, mothers_id): # Changing mother
299             new_mother = getattr(folder, mothers_id)
300             if new_mother.portal_type == self.portal_type:
301                 self.setTranslation_of(new_mother)
302                 new_mother.addToTranslations(self.UID())
303                 if old_mother:
304                     old_mother.removeFromTranslations(self.UID())
305             else:
306                 lt.addPortalMessage("Type mismatch, cannot be translation of that kind of object.")
307         else:
308             lt.addPortalMessage("Object not found.")       
309         return REQUEST.RESPONSE.redirect('%s/manage_translations' % self.absolute_url())
310
311     security.declareProtected(MANAGE_PORTAL, 'manage_form_removeTranslations')
312     def manage_form_removeTranslations(self, REQUEST):
313         """ Allows managers to reorganize which is translated by whom """
314         lt = getToolByName(self, "lemill_tool")
315         oldlist=self.getTranslations()       
316         for n, obj in enumerate(oldlist):
317             checked = REQUEST.get('obj_translation%s' % n)
318             if checked:
319                 self.removeFromTranslations(obj.UID())
320                 obj.setTranslation_of('')
321         return REQUEST.RESPONSE.redirect('%s/manage_translations' % self.absolute_url())
322
323     security.declareProtected(MANAGE_PORTAL, 'manage_form_addTranslation')
324     def manage_form_addTranslation(self, REQUEST):
325         """ Allows managers to reorganize which is translated by whom """
326         lt = getToolByName(self, "lemill_tool")
327         new_addition = REQUEST.get('new_translation')
328         if not new_addition:
329             return REQUEST.RESPONSE.redirect('%s/manage_translations' % self.absolute_url())
330         folder = self.getSectionFolder()
331         new = getattr(folder, new_addition, None)
332         if new:
333             if new.portal_type == self.portal_type:
334                 self.addToTranslations(new.UID())
335                 new.setTranslation_of(self)
336             else
337                 lt.addPortalMessage("text_message_type_mismatch_cant_translate", default="Type mismatch, cannot be translation of that kind of object.")
338         else:
339             lt.addPortalMessage("Object not found.")       
340         return REQUEST.RESPONSE.redirect('%s/manage_translations' % self.absolute_url())
341
342
343     ############ Scoring         #####################################
344
345     def recalculateScore(self):
346         """ Recalculates score for LearningResources according to specifications """
347         # every resource get's score at least one for creating it
348         score = 1
349         # +1 if resource is also published
350         if self.getState() == 'public' and self.getHasCoverImage():
351             score += 1
352         # +5 every additional author
353         authors = self.listCreators()
354         creator = self.Creator()
355         for author in authors:
356             if author != creator:
357                 score += 5
358         # +1 every additional change
359         history=self.getHistory()
360         if len(history) > 0:
361             score += len(history) - 1
362         # +1 if resource is in owner's collection
363         # +10 if resource is someone else's collection
364         collections = self.getCollections()
365         for collection in collections:
366             if collection.Creator() == creator:
367                 score += 1
368             else:
369                 score += 10
370         # +1 if assigned to group
371         group_editing = hasattr(self, 'getGroupEditing')
372         if group_editing:
373             if self.getGroupEditing():
374                 score += 1
375         # if resource is webpage or exercise
376         # add +1 for every used media piece and embed block
377         if self.portal_type in ['MultimediaMaterial', 'ExerciseMaterial']:
378             bodytext = self.getBodyText()
379             for chapter in bodytext:
380                 if chapter['type'] in ['media_piece', 'embed_block']:
381                     score += 1
382         # Set the value for field score
383         self.setScore(score)
384
385
386
387
388     ############ PDF view ########################################
389
390
391     def prepareForPDF(self):
392         """ Removes license and visualNoPrint"""       
393         text=Resource.prepareForPDF(self)
394         text = re.sub(visualClear, '', text)
395         text = re.sub(embedContent, '', text)
396         text = re.sub(visualNoPrint, '', text)
397         text = re.sub(t_license_pattern, '', text)
398         text = re.sub(license_pattern, '', text)
399         return text
400
401
402 InitializeClass(LearningResource)
403
404
Note: See TracBrowser for help on using the browser.