source: trunk/Piece.py @ 3029

Revision 3029, 15.0 KB checked in by jukka, 10 years ago (diff)

Fixed #1962. Refactored messages that are based on properties of resources (deleted, draft, private, missing language) to come from a single source, getMessages-method and message_macros.pt. It should be easy to expand to add new suggestions.

  • Property svn:eol-style set to native
Line 
1# Copyright 2006 by the LeMill Team (see AUTHORS)
2#
3# This file is part of LeMill.
4#
5# LeMill is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# LeMill is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with LeMill; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19from Products.Archetypes.public import *
20from Products.Archetypes.utils import mapply
21from FieldsWidgets import LeStringWidget, CopyrightWidget
22from Globals import InitializeClass
23from Products.CMFCore.utils import getToolByName
24from AccessControl import ClassSecurityInfo, Unauthorized
25from config import PROJECTNAME, VIEW, ALL_LICENSES, FS_STORAGE
26from permissions import MODIFY_CONTENT, ModerateContent
27from Schemata import resource_schema, description, author_schema,  coverImage
28from Resource import Resource
29from mp3tool import get_length, is_mp3
30if FS_STORAGE:
31    from Products.FileSystemStorage.FileSystemStorage import FileSystemStorage
32else:
33    from Products.Archetypes.Storage import AttributeStorage as FileSystemStorage
34
35import cStringIO, re
36try:
37    from PIL import Image
38except ImportError:
39    pass
40
41piece_schema= Schema((
42    ImageField('image',
43               required=False,
44               accessor='getImage',
45               mutator='setImage',
46               storage = FileSystemStorage(),
47               sizes={'small':(120,120),'large':(500,500), 'slide':(700,525)},
48               widget = ImageWidget(visible={'view':'invisible','edit':'invisible'}),
49               ),
50    IntegerField('length',
51               required=False,
52               default=0,
53               widget = LabelWidget(
54                    label = "Playing time",
55                    label_msgid = "label_playing_time",
56                    i18n_domain = "lemill",
57                    visible={'view':'visible', 'edit':'invisible'}),
58               ),
59    StringField('piece_type',
60                index = 'FieldIndex:schema',
61                widget=ComputedWidget(
62                    visible={'edit':'invisible', 'view':'invisible'},),
63                ),
64    StringField('originalAuthor',
65              schemata="metadata",
66              seachable=True,
67              copied_in_translation=True,
68              widget = LeStringWidget(
69                  visible = {'view':'invisible', 'edit':'visible'},
70                  label='Original author',
71                  label_msgid='label_original_author',
72                  i18n_domain="lemill",
73                  ),
74              ),
75    FileField('file',
76              accessor='getFile',
77              mutator='setFile',
78              copied_in_translation=True,
79              primary=True,
80              storage = FileSystemStorage(),
81              widget=FileWidget(label="Contents",
82                                label_msgid="label_piece_file",
83                                i18n_domain="lemill",
84                                show_content_type = True,
85                                #visible = {'edit' : 'visible', 'view' : 'invisible' }
86                                ),
87              ),
88    FileField('source',
89              accessor='getSourceFile',
90              mutator='setSourceFile',
91              storage = FileSystemStorage(),
92              widget=FileWidget(label="Source file",
93                                label_msgid="label_piece_source_file",
94                                description="Upload the source file which has the contents in an easily editable format.",
95                                description_msgid="help_piece_source_file",
96                                i18n_domain="lemill",
97                                show_content_type = True,
98                                visible = {'edit' : 'invisible', 'view' : 'invisible' }
99                                ),
100              ),
101   StringField('rights',
102       schemata="metadata",
103       default = "CreativeCommons",
104       copied_in_translation=True,
105       vocabulary=DisplayList((ALL_LICENSES)),
106       widget=CopyrightWidget(
107                visible={'view':'visible','edit':'visible'},
108                label='License',
109                label_msgid='label_license',
110                i18n_domain="lemill"
111           ),
112       ),
113))
114
115schema = resource_schema + description + author_schema + coverImage + piece_schema
116
117
118schema = schema.copy()
119schema.__delitem__('language')
120schema.moveField('rights', pos='bottom')
121
122class Piece(Resource):
123    """Piece"""
124    schema = schema
125
126    actions= (
127    {
128    'id':'edit',
129    'name':'Edit',
130    'action':'string:${object_url}/piece_edit',
131    'permission':('View',),
132    },
133    )
134   
135    portlet = 'here/portlet_piece_actions/macros/portlet'
136    meta_type = "Piece"
137    archetype_name = "Piece"
138    default_location = 'content/pieces'
139    security = ClassSecurityInfo()
140    security.declareObjectPublic()
141
142    aliases = {
143        '(Default)' : 'base_view',
144        'view'      : 'base_view',
145        'edit'      : 'piece_edit'
146    }
147
148
149    def getMessages(self):
150        """ Instead of inheriting from Resource, this should inherit from CommonMixIn to not worry about language """
151        messages=CommonMixIn.getMessages(self)
152        return messages
153
154
155    # at_post_edit_script from Resources without recalculateAuthor
156    security.declarePrivate('at_post_edit_script')
157    def at_post_edit_script(self):
158        self.post_edit_rename()
159        self.post_edit_update_history()
160        self.post_edit_credit_author()
161        self.recalculateScore()
162        self.reindexObject()
163       
164    # Use the file as cover image, if it's an image
165    def setFile(self,value,**kwargs):
166        file=self.getField('file')
167        # Call normal mutator for this field
168        file.set(self,value,**kwargs)
169        self.resetPieceType()
170        #print '%s : setting file for piece' % self.id
171
172        if self.getPiece_type()=='image':
173            # Call mutator for the coverImage field as well
174            self.setCoverImage(value)
175            # set 'hasCoverImage'-flag
176            self.setHasCoverImage(True)
177            # Populate the image field
178            self.setImage(value)
179        elif self.getPiece_type()=='audio':
180            try:
181                self.setLength(self.getMp3Length())
182            except:
183                print 'something went wrong with setting mp3 length'
184
185    def getLength(self):
186        """get stored length of audio piece """
187        return self.getField('length').get(self) or 30
188
189    def getPiece_type(self):
190        """ getter, if value doesn't exist, recalculate it """
191        pt_field=self.getField('piece_type')
192        pt=pt_field.get(self)
193        if not pt:
194            pt=self.evaluatePieceType()
195            pt_field.set(self, pt)
196        return pt
197       
198
199    def hasEditableCoverImage(self):
200        """ Cover Image is not created automatically """
201        return not self.isImage()
202
203    def _getImage(self, label):
204        field=self.getField('image')
205        image=field.getStorage(self).get(label, self)
206        if hasattr(image, '__of__'):
207            return image.__of__(self)
208        else:
209            return image       
210
211    def image_small(self):
212        """ get 120 x 120 thumbnail of the image """
213        return self._getImage('image_small')
214
215    def image_large(self):
216        """ get 500 x 500 version of the image """
217        return self._getImage('image_large')
218
219    def image_slide(self):
220        """ get 700 x 525 version of the image """
221        return self._getImage('image_slide')
222
223    def resetPieceType(self):
224        """ sets PieceType according to current file """
225        self.setPiece_type(self.evaluatePieceType())
226           
227    def evaluatePieceType(self):
228        """ returns piece type, usually you should get it from the field instead """                   
229        ltool = getToolByName(self, 'lemill_tool')
230        file=self.getField('file')
231        if not file:
232            return ''
233        content_type= file.getContentType(self)
234        if content_type.startswith('image/'):
235            return 'image'
236        elif content_type.startswith('audio/'):
237            return 'audio'
238        elif content_type=='application/x-shockwave-flash':
239            return 'flash'
240        elif content_type.startswith('video/'):
241            return 'video'
242        elif content_type.startswith('application/'):
243            raw_file=file.get(self, raw=True, unwrapped=True)
244            raw_file = getattr(raw_file, 'aq_self', raw_file)
245            if str(raw_file).startswith('FLV'):
246                return 'flv-video'
247            raw_file = cStringIO.StringIO(str(file))
248            if is_mp3(raw_file):
249                return 'audio'
250        # Need to use 'text/xml' instead of 'text/plain' it was during upload
251        elif content_type=='text/xml':
252            raw_file = file.get(self, raw=True, unwrapped=True)
253            raw_file = getattr(raw_file, 'aq_self', raw_file)
254            if ltool.checkIsKML(str(raw_file)):
255                return 'kml'
256        else:
257            return content_type
258
259    def isPiece(self):
260        """ Quick way to check what we are """
261        return True
262
263
264    def isImage(self):
265        """Returns whether the piece is an image."""
266        return self.getPiece_type()=='image'
267
268    def isAudio(self):
269        """Returns whether the piece is an audio file. """
270        return self.getPiece_type()=='audio'
271
272    def isSwf(self):
273        """Returns whether the piece is an swf Flash animation file."""
274        return self.getPiece_type()=='flash'
275
276    def isMovie(self):
277        """Returns whether the piece is an movie file. (not tested) """
278        return self.getPiece_type()=='video'
279   
280    def isFLVVideo(self):
281        """Returns whether the piece is an FLV video."""
282        return self.getPiece_type()=='flv-video'
283
284    def isKml(self):
285        """ Returns whether the piece is a kml. """
286        return self.getPiece_type() == 'kml'
287       
288    def getMp3Length(self):
289        """Calling out for our custom mp3tool"""
290        print 'calculating length for mp3 %s' % self.getId()
291        file=self.getField('file')
292        file=file.get(self, raw=True, unwrapped=True)
293        file = str(getattr(file, 'aq_self', file))
294        try:
295            length=get_length(file)
296        except:
297            length=0
298            print 'calculating mp3 length failed for %s' % self.id
299        return length
300
301
302    def getDefaultIcon(self, meta_type='', obj=None):
303        """ Names for icons are not anymore algorithmically derived """
304        if self.isAudio():
305            return 'images/default_soundclip.png'
306        elif self.isMovie():
307            return 'images/default_movieclip.png'
308        elif self.isKml():
309            return 'images/default_kml.png'
310        elif self.isImage():
311            return 'images/default_learningresource.png' # img for deleted piece missing
312        else:
313            # images should always have coverimages and this method shouldn't even get called
314            return 'images/default_learningresource.png'
315
316    def getResourcesUsingPiece(self, return_objects=False):
317        """ Find resources that use this piece, returns full objects """
318        uid=self.UID()
319        rc=getToolByName(self, 'reference_catalog')
320        uc=getToolByName(self, 'uid_catalog')
321        lutool=getToolByName(self, 'lemill_usertool')
322        results=rc({'targetUID':uid})
323        if return_objects:
324            resultobjects=[]
325            for refobj in results:
326                res=uc({'UID':refobj.sourceUID})
327                if res:
328                    # getObject verified
329                    obj=res[0].getObject()
330                    if obj:
331                        states = ['draft', 'public']
332                        if lutool.getAuthenticatedId() == obj.Creator() or self.amIManager():
333                            states.append('private')
334                        if obj.state in states:
335                            resultobjects.append(obj)
336            return resultobjects
337        else:
338            return results   
339
340    security.declareProtected(MODIFY_CONTENT,'processForm')
341    def processForm(self, data=1, metadata=None, REQUEST=None, values=None):
342        """ Simplified processForm for pieces, just to make sure that expensive
343         setFile and rename doesn't happen several times """
344        request = REQUEST or self.REQUEST
345        fieldset=['title','description','tags','rights','originalAuthor']
346        fields= [self.getField(x) for x in fieldset]
347        if values:
348            form = values
349        else:
350            form = request.form
351
352        form_keys = form.keys()
353
354        for field in fields:
355
356            widget = field.widget
357            result = widget.process_form(self, field, form,
358                                         empty_marker=[])
359            if result is [] or result is None: continue
360
361            # Set things by calling the mutator
362            mutator = field.getMutator(self)
363            __traceback_info__ = (self, field, mutator)
364            result[1]['field'] = field.__name__
365            mapply(mutator, result[0], **result[1])
366
367        self.unmarkCreationFlag()
368        self.at_post_edit_script()
369
370
371    def hasDefaultTitle(self):
372        """ some heuristic to recognize default titles """
373        x=self.Title()
374        return re.match(r'.*\.(...)$', x) or re.match(r'.*\.(....)$', x)
375
376    def recalculateScore(self):
377        """ Calculates the score for Pieces according to specifications """
378        score = 1+len(self.getResourcesUsingPiece()) # 1 point for each time piece used in a resource       
379       
380        if not self.hasDefaultTitle():       
381            score = score + 1 #1 point if piece has a non-default title
382        if len(self.getTags()) > 0:
383            score = score + 1 #1 point if piece has tags
384        if len(self.Description()) > 0:
385            score = score + 1 #1 point if piece has a description   
386        self.setScore(score)
387       
388    def createAlphaNumericId(self):
389        """ Returns id without the '.' and '-' characters. """
390        return self.id.replace('.', '').replace('-', '')
391
392    def getDiscussion(self, do_create=True):
393        """ Pieces don't have discussions """
394        return None
395       
396    def canIEditSource(self, member=None):
397        return self.amIOwner() or self.canIModerate()
398       
399    security.declareProtected(MODIFY_CONTENT, 'add_source')
400    def add_source(self):
401        REQUEST = self.REQUEST
402        file = REQUEST.get('file')
403        if file:
404            self.setSourceFile(file)
405            return REQUEST.RESPONSE.redirect('%s/view' % self.absolute_url())
406        return REQUEST.RESPONSE.redirect('%s/source_file_add' % self.absolute_url())
407       
408    security.declareProtected(ModerateContent, 'delete_source')   
409    def delete_source(self):
410        """ """
411        self.setSourceFile('')
412        REQUEST = self.REQUEST
413        return REQUEST.RESPONSE.redirect('%s/view' % self.absolute_url())
414       
415registerType(Piece, PROJECTNAME)
Note: See TracBrowser for help on using the repository browser.