source: trunk/FieldsWidgets.py @ 3073

Revision 3073, 46.4 KB checked in by jukka, 9 years ago (diff)

Still working with exercises

  • 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 AccessControl import ClassSecurityInfo
20from Products.Archetypes.config import REFERENCE_CATALOG
21from Products.Archetypes.Registry import registerField, registerWidget
22from Products.Archetypes.Field import StringField, LinesField, ReferenceField, ObjectField, FileField
23from Products.Archetypes.Widget import TextAreaWidget, StringWidget, SelectionWidget, TypesWidget, MultiSelectionWidget, VisualWidget, FileWidget
24from Products.Archetypes.ReferenceEngine import Reference
25from config import to_unicode
26from Products.Archetypes import config
27from string import letters, punctuation
28from Products.CMFCore.utils import getToolByName
29from ZODB.PersistentMapping import PersistentMapping
30from Products.Archetypes.utils import shasattr
31from Acquisition import aq_base
32from mp3tool import get_length
33from types import ListType, TupleType, StringType, UnicodeType, InstanceType
34from messagefactory_ import i18nme as _
35from random import shuffle
36from copy import copy
37from xml.dom.minidom import parseString as parse_xml
38from xml.sax.saxutils import unescape
39import re, os, shutil
40
41#StringWidget and TextAreaWidget need to update their macro, otherwise they try to use archetypes/skins/widgets -path only
42#Unfortunately this can't be done but by making new Widgets: LeStringWidget and LeTextAreaWidget
43
44class LeVisualWidget(VisualWidget):
45    _properties = VisualWidget._properties.copy()
46    _properties.update({
47        'macro' : 'widget_visual',
48        'rows'  : 18,      #rows of TextArea if VE is not available
49        'cols'  : 80,      #same for cols
50        'width' : '507px', #width of VE frame (if VE is avalilable)
51        'height': '260px', #same for height
52    })
53    # material types use versions of chapter_widgets and are not affected by this
54
55    def process_form(self, instance, field, form, empty_marker=None, emptyReturnsMarker=False):
56        """Basic impl for form processing in a widget"""
57        value = form.get(field.getName(), empty_marker)
58        if value is empty_marker:
59            return empty_marker
60        if emptyReturnsMarker and value == '':
61            return empty_marker
62        return value, {}
63
64registerWidget(LeVisualWidget,
65    title='Visual Widget',
66    description='Visual Widget'
67)   
68
69class LeStringWidget(StringWidget):
70    _properties = StringWidget._properties.copy()
71    _properties.update({
72        'macro' : 'widget_string'
73    })
74
75registerWidget(LeStringWidget,
76    title='String Widget',
77    description='String Widget'
78)
79
80
81class LeTextAreaWidget(TextAreaWidget):
82    _properties = TextAreaWidget._properties.copy()
83    _properties.update({
84        'macro' : 'widget_textarea'
85    })
86
87registerWidget(LeTextAreaWidget,
88    title='Textarea Widget',
89    description='Textarea Widget'
90)
91
92class TagsWidget(StringWidget):
93    _properties = StringWidget._properties.copy()
94    _properties.update({
95        'macro' : 'widget_tags',
96    })
97   
98registerWidget(TagsWidget,
99    title='Tags Widget',
100    description='Tags Widget',
101    used_for=('Products.LeMill.TagsField.TagsField',)
102)
103
104class HTMLLinkWidget(StringWidget):
105    _properties = StringWidget._properties.copy()
106    _properties.update({
107        'macro' : 'widget_htmllink',
108    })
109
110    def getShortLinkName(self, link):
111        """Returns the shortened version of the link pointing to the reference's location. This shortened version goes into the content of the <a> tag."""     
112        lt = getToolByName(self, 'lemill_tool')
113        return lt.parse_text(link, start_with_p=False)
114
115    def process_form(self, instance, field, form, empty_marker=None, emptyReturnsMarker=False):
116        """Basic impl for form processing in a widget"""
117        value = form.get(field.getName(), empty_marker)
118        if value is empty_marker:
119            return empty_marker
120        if emptyReturnsMarker and value == '':
121            return empty_marker
122        if field.getName() == 'home_page':
123            if not (value.lower().startswith('http://') or value.lower().startswith('https://')) and value != '':
124                value = 'http://' + value
125        return value, {}
126   
127registerWidget(HTMLLinkWidget,
128    title='HTML Link Widget',
129    description='Generated <a> tag Widget',
130    used_for=('Products.LeMill.FieldsWidgets.HTMLLinkWidget',)
131)
132
133class MessengerWidget(TypesWidget):
134    _properties = TypesWidget._properties.copy()
135    _properties.update({
136        'macro' : 'widget_messenger',
137    },)
138
139    security = ClassSecurityInfo()
140
141    security.declarePublic('process_form')
142    def process_form(self, instance, field, form, empty_marker=None,
143                     emptyReturnsMarker=False):
144        """Concatenates address + messenger type, but if address already contains something like prefix (word:) then removes it.
145        """
146        name = field.getName()
147        otherName = "%s_other" % name
148        value = form.get(name, empty_marker)
149        othervalue = form.get(otherName, empty_marker)
150        if not value:
151            return '', {}
152        if not othervalue == empty_marker:
153            value = "%s:%s" % (othervalue,value)
154        return value, {}
155
156
157registerWidget(MessengerWidget,
158    title='Messenger Widget',
159    description='Messenger Widget',
160    used_for=('Products.Archetypes.Field.StringField',)
161)
162
163
164class MobileWidget(StringWidget):
165    _properties = StringWidget._properties.copy()
166    _properties.update({
167        'macro' : 'widget_mobile',
168    })
169
170    security = ClassSecurityInfo()
171
172    security.declarePublic('process_form')
173    def process_form(self, instance, field, form, empty_marker=None,
174                     emptyReturnsMarker=False):
175        name = field.getName()
176        otherName = "%s_checkbox" % name
177        value = form.get(name, empty_marker)
178        othervalue = form.get(otherName, empty_marker)
179        if value == empty_marker and emptyReturnsMarker:
180            return empty_marker
181        if othervalue=="1":
182            value = "*SMS*%s" % value
183        return value, {}
184
185   
186registerWidget(MobileWidget,
187    title='Mobile Widget',
188    description='Mobile Widget',
189    used_for=('Products.Archetypes.Field.StringField',)
190)
191
192
193class CopyrightWidget(SelectionWidget):
194    _properties = SelectionWidget._properties.copy()
195    _properties.update({
196        'macro' : 'widget_copyright',
197    })
198   
199registerWidget(CopyrightWidget,
200    title='Copyright Widget',
201    description='Copyright selection Widget',
202    used_for=('Products.LeMill.StringField.StringField',)
203)
204
205class TwoColumnMultiSelectionWidget(MultiSelectionWidget):
206    _properties = MultiSelectionWidget._properties.copy()
207    _properties.update({
208        'macro' : 'widget_twocolumn',
209    })
210   
211registerWidget(TwoColumnMultiSelectionWidget,
212    title='Two column widget',
213    description='Two column multiselection widget',
214    used_for=('Products.Archetypes.Field.MultiSelectionField',)
215)
216
217
218class GroupWidget(SelectionWidget):
219    _properties = SelectionWidget._properties.copy()
220    _properties.update({
221        'macro' : 'widget_group',
222        'format' : 'checkbox',
223    })
224    def getGroupVocabulary(self, instance):
225        """ this builds a vocabulary for groups field """
226        lutool = getToolByName(instance, 'lemill_usertool')
227        mf = lutool.getMemberFolder()
228        user_groups=[]
229        if mf:
230            user_groups=mf.getGroups()
231        resource_groups=instance.getGroupInfo()       
232        resource_uids=[g.UID for g in resource_groups]
233        user_uids=[g.UID for g in user_groups]
234        disabled=[(g.UID, g.Title, True, True) for g in resource_groups if g.UID not in user_uids]
235        other=[(g.UID, g.Title, g.UID in resource_uids, False) for g in user_groups]
236        return disabled+other+[('__new_group','...or create a new group:',False,False)]
237       
238    def process_form(self, instance, field, form, empty_marker=[],
239                     emptyReturnsMarker=False, validating=True):
240        """Basic impl for form processing in a widget"""
241        value = form.get(field.getName(), empty_marker)
242        if value is empty_marker:
243            return empty_marker
244        if emptyReturnsMarker and value == '':
245            return empty_marker
246        if '__new_group' in value:
247            new_group_name = form.get('new_group_name', '')
248            if new_group_name:
249                # create a new group here
250                blog=instance.community.groups.addGroup(new_group_name)
251                value.remove('__new_group')
252                value.append(blog.UID())
253        return value, {}
254
255
256class ChapterWidget(SelectionWidget):
257    _properties = SelectionWidget._properties.copy()
258    _properties.update({
259        'macro' : 'widget_chapter',
260        'collections_only' : False,
261        'helper_js' : ('AC_RunActiveContent.js',),
262        'show_added' : True,
263        'no_label' : True
264    })
265    security = ClassSecurityInfo()
266
267    security.declarePublic('process_form')
268    def process_form(self, instance, field, form, empty_marker=None,
269            emptyReturnsMarker=False):
270        """ goes through the form, reorganizes chapters if necessary and delivers results to field writer (as dict). """
271        chapter_type=form.get('chapter_type')
272        if not chapter_type:
273            return {},{}
274        changes = {}
275        changes['edited']=int(form.get('chapter_last_edited', 0))
276        count=int(form.get('chapter_count',0))
277        changes['count']=count
278        print 'processing widget ChapterWidget with form: ',form
279        new_order=[]
280        reorder=False
281        deleted=[]
282        # Orderings and deletions
283        for index in range(0,count):
284            order=int(form.get('chapter_order_%s' % index, 0))
285            new_order.append(order)
286            if order!=index:
287                reorder=True
288            this_deleted = int(form.get('chapter_deleted_%s' % index, 0))
289            if this_deleted:
290                deleted.append(index)
291        if reorder:
292            changes['new_order']=new_order
293        if deleted:
294            changes['deleted']=deleted
295        # Changes
296        changes['chapter_type']=chapter_type
297        if chapter_type=='media_piece':
298            file=form.get('file', None)
299            if file and hasattr(file, 'filename'):
300                changes['file']=file
301            else:
302                changes['uid']=form.get('piece_uid')
303        elif chapter_type=='text_block':
304            changes['text']=form.get('chapter_textarea')
305        elif chapter_type=='embed_block':
306            changes['text']=''
307            changes['embed']=form.get('embed_textarea')
308        # Save & edit buttons
309        if form.get('saveChapter', None):
310            form.update({'chapter_edited':-1, 'chapter_anchor':changes['edited'], 'stay':1})       
311        elif form.get('editChapter', None):
312            form.update({'chapter_anchor':form['chapter_edited'], 'stay':1})       
313        # Add-buttons
314        elif form.get('add_text_block', None):
315            changes['new_chapters']=[{'text':'','type':'text_block'}]
316            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1})
317        elif form.get('add_media_piece', None):
318            changes['new_chapters']=[{'text':'','type':'media_piece','uid':''}]
319            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1})
320        elif form.get('add_embed_block', None):
321            changes['new_chapters']=[{'text':'','type':'embed_block', 'embed':''}]
322            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1})
323        return changes, {}
324
325registerWidget(ChapterWidget,
326    title='Chapter Widget',
327    description='Chapter editor Widget',
328    used_for=('Products.LeMill.ChapterField',)
329)
330
331class SlideWidget(ChapterWidget):
332    _properties = ChapterWidget._properties.copy()
333    _properties.update({
334        'macro' : 'widget_slides',
335        'collections_only' : False,
336        'show_added' : True,
337        'chapter_count' : 5,
338    })
339
340registerWidget(SlideWidget,
341    title='Slide Widget',
342    description='Slide and caption viewer Widget',
343    used_for=('Products.LeMill.ChapterField',)
344)
345
346class PilotWidget(ChapterWidget):
347    _properties = ChapterWidget._properties.copy()
348    _properties.update({
349        'macro' : 'widget_pilot',
350        'collections_only' : False,
351        'helper_js' : ('AC_RunActiveContent.js',),
352        'show_added' : True,
353        'chapter_count' : 3,
354    })
355    security = ClassSecurityInfo()
356
357    security.declarePublic('process_form')
358    def process_form(self, instance, field, form, empty_marker=None,
359            emptyReturnsMarker=False):
360        """ goes through the form and sends changes forward to field writer """
361        changes, empty= ChapterWidget.process_form(self, instance, field, form)
362        if not changes:
363            return changes,empty
364        chapter_type=changes['chapter_type']
365        if chapter_type=='pilot_section':
366            audio_file=form.get('audio_file', None)
367            if audio_file and hasattr(audio_file, 'filename'):
368                changes['audio_file']=audio_file
369            else:
370                changes['audio_uid']=form.get('audio_uid')
371            image_file=form.get('image_file', None)
372            if image_file and hasattr(image_file, 'filename'):
373                changes['image_file']=image_file
374            else:
375                changes['image_uid']=form.get('image_uid')
376            kws=[]
377            for i in range(0, 3):
378                kw=form.get('keywords_%s' % i, '')
379                if kw:
380                    kws.append(kw)
381            if kws:
382                changes['keywords']=kws
383            qs=[]
384            for i in range(0, 7):
385                q=form.get('question_%s' % i, '')
386                if q:
387                    qs.append(q)
388            if qs:
389                changes['questions']=qs
390        # Buttons
391        if form.get('add_pilot_section', None):
392            changes['new_chapters']={'text':'','type':'pilot_section','audio_uid':'', 'image_uid':'', 'keywords':[], 'questions':[]},
393            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1})
394        return changes, {}
395
396registerWidget(PilotWidget,
397    title='Pilot Widget',
398    description='Scene editor Widget',
399    used_for=('Products.LeMill.ChapterField',)
400)
401
402class ExerciseWidget(ChapterWidget):
403    _properties = ChapterWidget._properties.copy()
404    _properties.update({
405        'macro' : 'widget_exercise',
406        'collections_only' : False,
407        'helper_js' : ('AC_RunActiveContent.js',),
408        'show_added' : True,
409        'chapter_count' : 1,
410    })
411    security = ClassSecurityInfo()
412
413    security.declarePublic('process_form')
414    def process_form(self, instance, field, form, empty_marker=None,
415            emptyReturnsMarker=False):
416        """ goes through the form and tries to merge all text inputs to lists, find references to pieces and if file objects are uploaded, just send them forward to field. """
417        changes, empty= ChapterWidget.process_form(self, instance, field, form)
418        if not changes:
419            return changes,empty
420        chapter_type=changes['chapter_type']               
421        if chapter_type=='guidelines':
422            changes['text']=form.get('chapter')
423        elif chapter_type in ['choice','multiple_choices','poll']:
424            question=form.get('choice_question')
425            answers=[]
426            answer_count=int(form.get('choice_answer_count'))
427            for i in range(0, answer_count):
428                answer={}
429                answer['text']=form.get('choice_answer_%s' % i, '')
430                answer['is_correct']= chapter_type=='poll' or int(form.get('answer_%s_correct' % i, ''))
431                answer['order']=int(form.get('choice_order_%s' % i, 0))               
432                answers.append(answer)
433                # put sorting here!!!
434            changes['question']=question
435            changes['answers']=answers
436        elif chapter_type=='fill_in_the_blanks':
437            changes['text']=form.get('fill_blanks','')
438        elif chapter_type=='open_ended':
439            changes['text']=form.get('open_ended','')
440        # Buttons
441        if form.get('add_exercise', None):
442            changes['new_chapters']=[{'text':'','type':'exercise'}]
443            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1})
444        elif form.get('import_potatoes', None):
445            file=form.get('import_file', None)
446            if file and hasattr(file, 'filename'):
447                if file.filename.endswith('jcl'):
448                    text=self.importJClozeQuestion(file)
449                    changes['chapter_type']='fill_in_the_blanks'
450                    changes['text']=text
451                    form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1})
452                elif file.filename.endswith('jcz'):
453                    new_chapters=self.importJQuizQuestions(file)
454                    if new_chapters:
455                        this_chapter= new_chapters.pop(0)
456                        changes['chapter_type']=this_chapter['type']
457                        changes['answers']=this_chapter['answers']
458                        changes['question']=this_chapter['text']
459                        if new_chapters:
460                            changes['new_chapters']=new_chapters
461                            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1})
462        print changes
463        return changes, {}
464   
465    def importJQuizQuestions(self, file):
466        """ import hot potatoes questions made with jquiz """       
467        dom = parse_xml(file.read())
468        questions = dom.getElementsByTagName("question-record")
469        result=[]
470        for ques in questions:
471            questype = int(ques.getElementsByTagName("question-type")[0].childNodes[0].data)
472            if not (questype==1 or questype==4):
473                return           
474            question = ques.getElementsByTagName("question")[0].childNodes[0].data.encode('utf-8')
475            # if jquiz question type is multiple choice(1) or multiple correct(4)
476            answers = ques.getElementsByTagName("answer")
477            # calculate how many correct answers (is it choice or choice_multiple type)
478            corrects=0
479            made_answers=[]
480            for answer in answers:
481                correct = int(answer.getElementsByTagName("correct")[0].childNodes[0].data) # 1-correct 0-false
482                if correct:
483                    corrects+=1
484                answertext = answer.getElementsByTagName("text")[0].childNodes[0].data.encode('utf-8')
485                if answertext:
486                    made_answers.append({'text':answertext, 'is_correct':correct})
487            if corrects==len(result):
488                qtype='poll'
489            elif corrects==1:
490                qtype='choice'
491            else:
492                qtype='multiple_choices'
493            result.append({'type':qtype,'text':question,'answers':made_answers})       
494        dom.unlink()
495        return result
496   
497    def importJClozeQuestion(self, file):
498        """ import hot potatoes question made with JCloze """       
499        dom = parse_xml(file.read())
500        gap_fill = dom.getElementsByTagName("gap-fill")[0]
501        result=[]
502        for node in gap_fill.childNodes:
503            if node.nodeType==node.TEXT_NODE:
504                result.append(node.data)
505            else:
506                for answer in node.getElementsByTagName("answer"):
507                    text_tag=answer.getElementsByTagName("text")[0]
508                    text=text_tag.childNodes[0].data
509                    result.append('{%s}' % text)
510        result=u''.join(result)
511        #result=unescape(u''.join(result), {'&#x003C;':'<','&#x003E;':'>'})
512        dom.unlink()
513        return result
514
515registerWidget(ExerciseWidget,
516    title='Exercise Widget',
517    description='Exercise editor Widget',
518    used_for=('Products.LeMill.ChapterField',)
519)
520
521class AudioWidget(FileWidget):
522    _properties = FileWidget._properties.copy()
523    _properties.update({
524    'macro' : 'widget_audio',
525    'helper_js' : ('AC_RunActiveContent.js',),
526    })
527
528registerWidget(AudioWidget,
529    title='Audio Widget',
530    description='Widget that uses flashplayer for mp3:s',
531    used_for=('Products.LeMill.ChapterField',)
532)
533
534class LeMillLinksWidget(LeVisualWidget):
535    _properties = LeVisualWidget._properties.copy()
536    _properties.update({
537            'macro' : 'widget_lemilllinks',
538    })
539       
540    def process_form(self, instance, field, form, empty_marker=None,
541                     emptyReturnsMarker=False):
542        """ Replaces internal links with {uid:UID} and external links with {url} """
543        fieldname = field.getName()
544        value = form.get(fieldname, empty_marker)
545       
546        if value.strip() == '':
547            return None, {}
548           
549        portal_url = getToolByName(instance, 'portal_url')
550        portal_url = portal_url()
551        pc = getToolByName(instance, 'portal_catalog')
552        from LargeSectionFolder import DEFAULT_LOCATIONS
553        from config import CONTENT_TYPES, ACTIVITY_TYPES, TOOLS_TYPES
554        suitable_types = CONTENT_TYPES + ACTIVITY_TYPES + TOOLS_TYPES
555        locations = {}
556        # Only some locations are needed
557        for d in DEFAULT_LOCATIONS.keys():
558            if d in suitable_types:
559                locations[d] = DEFAULT_LOCATIONS[d]
560        # array of inserted data
561        rows = value.split('\n') 
562
563        data = ""
564        for row in rows:
565            r = row
566            row = row.strip()
567            words = []
568            if row != '':
569                words = row.split(' ')
570            for word in words:
571                index = words.index(word)
572                # control is it link to LeMill resource
573                if (word.lower().startswith(portal_url)):
574                    isMaterial = False
575                    for location, url in locations.items():
576                        if url in word:
577                            # Now the type is known, getting the id
578                            temp = word.split(url)
579                            temp = temp[1]
580                            temp = temp.split('/')
581                            id = temp[1]
582                            # Now to make a catalog search
583                            query = {'getId':id,'meta_type':location}
584                            results = pc(query)
585                            if len(results)>0:
586                                words[index] = '{uid:'+results[0].UID+'}'
587                                #value = value.replace(word,'{uid:'+results[0].UID+'}')
588                                isMaterial = True
589                    # if we dont find LeMill resource, then treat this url as any other link           
590                    if not isMaterial:
591                        words[index] = '{'+word+'}'
592                # if it is external link
593                elif (word.lower().startswith("http://") or word.lower().startswith("https://")):
594                    words[index] = '{'+word+'}'
595            if words != []:
596                data += ' '.join(words) + '\n'
597        return data, {}
598
599registerWidget(LeMillLinksWidget,
600    title='LeMill Links Widget',
601    description='LeMill Links Widget',
602    used_for=('Products.LeMill.FieldsWidgets.LeMillLinksField',)
603)
604
605
606class EmbedWidget(StringWidget):
607    _properties = StringWidget._properties.copy()
608    _properties.update({
609        'macro' : 'widget_embed'
610    })
611
612registerWidget(EmbedWidget,
613    title='Embed Widget',
614    description='Embed Widget, not modifiable by common means, only displays embed code'
615)
616
617
618####                            ####
619####            FIELDS          ####
620####                            ####
621
622
623class TagsField(LinesField):
624    """ A field that stores tags """
625    __implements__ = LinesField.__implements__
626    _properties = LinesField._properties.copy()
627    _properties.update({
628        'widget' : TagsWidget,
629    })
630
631    security = ClassSecurityInfo()
632
633    def set(self, instance, value, **kwargs):
634        if not value:
635            value=''
636        if isinstance(value,str):
637            value = value.lower()
638            value = value.split(',')
639        if isinstance(value, tuple) or isinstance(value, list):
640            value=[t.strip().lower() for t in value] # strip and clean individual tags
641            value=list(set(value)) # remove duplicates
642            value= '\n'.join(value) # merge back to string
643        LinesField.set(self, instance, value, **kwargs)
644                       
645    def getRaw(self, instance, **kwargs):
646        version = None
647        try:
648            version = instance.REQUEST.get('version',None)
649        except AttributeError:
650            pass
651        if version:
652            value = instance.getFieldHistory(self.getName(), version)
653            if value and callable(value):
654                value=value()
655            value = value.split(',')
656        else:
657            value = LinesField.get(self, instance, **kwargs)
658        return ', '.join(value)
659
660    def get(self, instance, **kwargs):
661        version = None
662        try:
663            version = instance.REQUEST.get('version', None)
664        except AttributeError:
665            pass
666        if version:
667            value = instance.getFieldHistory(self.getName(), version) or ''
668            if value and callable(value):
669                value=value()
670            if value:
671                value = value.split(',')             
672        else:
673            value = LinesField.get(self, instance, **kwargs)
674        return value
675
676registerField(TagsField,
677               title='Tags Field',
678               description=('Tags Field'),
679               )
680
681class WYSIWYMField(StringField):
682    """A field that stores WYSIWYM strings"""
683    _properties = StringField._properties.copy()
684    _properties.update({
685        'widget' : LeTextAreaWidget,
686        'edit_accessor' :  "getRaw",
687        'default_output_type' : 'text/x-html-captioned',
688        'default_content_type' : 'text/html',
689    })   
690   
691    security = ClassSecurityInfo()
692   
693   
694    def getRaw(self, instance, **kwargs):
695        """ Gets raw version for editing """
696        version = None
697        try:
698            version = instance.REQUEST.get('version',None)
699        except AttributeError:
700            pass
701        if version:
702            value = instance.getFieldHistory(self.getName(), version)
703        else:
704            value = StringField.get(self, instance,**kwargs)
705        return value
706   
707    def get(self, instance, **kwargs):
708        """ Gets cleaned version for display """
709        version = None
710        try:
711            version = instance.REQUEST.get('version',None)
712        except AttributeError:
713            pass
714        value = self.getRaw(instance, **kwargs)
715        cleaned_text = getattr(instance, 'cleaned_%s' % self.getName(), None)
716        if cleaned_text and not version:
717            return cleaned_text       
718        ltool = getToolByName(instance, 'lemill_tool')
719        cleaned_text = ltool.parse_text(value)
720        if not version:
721            setattr(instance, 'cleaned_%s' % self.getName(), cleaned_text)
722        return cleaned_text
723
724    def set(self, instance, value, **kwargs):
725        """ update the cleaned_text and then call original set """
726
727        # update cleaned text
728        ltool = getToolByName(instance, 'lemill_tool')
729        cleaned_text = ltool.parse_text(value)
730        setattr(instance, 'cleaned_%s' % self.getName(), cleaned_text)
731
732        # set value
733        StringField.set(self, instance, value, **kwargs)
734       
735
736
737    # render -method has moved to LeMillTools, because it is needed also outside the wysiwym field.
738
739registerField(WYSIWYMField,
740               title='WYSIWYM Field',
741               description=('WYSIWYM Field'),
742               )
743
744
745class ChapterField(ObjectField):
746    """ chapters are stored as lists, where chapter can either be a string or UID. If UID, view widget should fetch the object. """
747    _properties = ObjectField._properties.copy()
748    _properties.update({
749        'widget' : ChapterWidget,
750        'relationship' : 'uses', # required
751        'referenceClass' : Reference,
752        'referenceReferences' : False,
753        'index_method':'indexValue'})
754   
755    security = ClassSecurityInfo()
756    chapter_names={'text_block':_('text chapter'), 'media_piece':_('media piece'), 'embed_block':_('embedded content'), 'multiple_choices':_('multiple choices'), 'choices':_('choices'), 'fill_in_the_blanks':_('fill in the blanks'), 'open_ended':_('open ended question')}
757
758    def getChapterNames(self):
759        """ Return readable versions of different chapter types """       
760        return ChapterField.chapter_names
761
762
763    def getRaw(self, instance, version=None, **kwargs):
764        """ Gets raw version for editing, as list """
765        if version:
766            value = instance.getFieldHistory(self.getName(), version) or []
767        elif '_initializing_' in kwargs:
768            value = self.getDefault(instance) or []   
769        else:
770            value = ObjectField.get(self, instance, **kwargs) or []
771        if not isinstance(value, list):
772            value = []
773        for chapter in value:
774            if isinstance(chapter, tuple):
775                value = self.updateChapterList(value)
776                break
777        return value
778
779    def get(self, instance, **kwargs):
780        """ Get a cleaned version for display """
781        print 'ChapterField.get called with args', kwargs
782        REQUEST=getattr(instance,'REQUEST',None)
783        if REQUEST:
784            version=REQUEST.get('version',None)
785            translation=REQUEST.get('translation',None)
786        else:
787            version=None
788            translation=None
789        convert=False
790
791        if version or translation: # don't cache cleaned version
792            value = self.getRaw(instance, version=version, **kwargs)
793            if convert:
794                value = self.updateChapterList(value)
795            value=self.cleanChapters(instance, value)
796            if translation:
797                value=self.mergeTranslationSource(instance,value)
798            return value
799        cleaned_value = getattr(instance, 'cleaned_%s' % self.getName(), None)
800        if cleaned_value:
801            for chapter in cleaned_value:
802                if isinstance(chapter, tuple):
803                    convert=True
804                    break
805            if convert:
806                cleaned_value=self.updateChapterList(cleaned_value)
807                cleaned_value=self.updateCleanedChapters(instance, cleaned_value)
808                print 'converted and updated cleaned chapter list'
809                return cleaned_value
810            else:
811                print 'no need to convert, giving cleaned as it is'
812                return cleaned_value
813        print 'ChapterField is calling getRaw...'
814        value=self.getRaw(instance, **kwargs)
815        print '...ChapterField has called getRaw.'
816        cleaned_value=self.updateCleanedChapters(instance, value)
817        print 'updated cleaned chapter'
818        return cleaned_value
819
820    def textOnly(self, instance, **kwargs):
821        print '***** calling indexer for ChapterField'
822        listofdicts= getattr(instance, 'cleaned_%s' % self.getName(), None)
823        return '\n'.join([x['text'] for x in listofdicts])
824
825    def mergeTranslationSource(self, instance, chapter_list):
826        """ Adds original source texts to translated chapters, under key 'original' """
827        return chapter_list
828        #trans_source=instance.get
829        #for chapter in chapter_list:
830       
831
832    def cleanChapters(self, instance, chapters):
833        ltool = getToolByName(instance, 'lemill_tool')
834        cleaned_chapters=[]
835        parsed_fields=['text','keywords','questions','answers']
836        for chapter in chapters:
837            new_chapter={}
838            new_chapter=copy(chapter)
839            for key, value in chapter.items():
840                if key in parsed_fields:
841                    new_chapter[key]=ltool.parse_text(value, start_with_p=False)
842            cleaned_chapters.append(new_chapter)
843        return cleaned_chapters
844
845
846    def updateCleanedChapters(self, instance, chapters):
847        cleaned_chapters=self.cleanChapters(instance, chapters)
848        setattr(instance, 'cleaned_%s' % self.getName(), cleaned_chapters)
849        return cleaned_chapters
850       
851
852    def _updatePilot(self, old_values):
853        """ in new dictionary based chapters it makes sense to use one chapter per slide, instead of three chapters
854        this method converts old chapter lists into new format """       
855        new_scene=True
856        updated_values=[]
857        for cvalue, ctype in old_values:
858            if new_scene:
859                scene={'text':'', 'image_uid':'', 'audio_uid':'', 'keywords':[], 'questions':[]}
860                new_scene=False
861            if ctype=='audio_piece':
862                scene['audio_uid']=cvalue
863            elif ctype=='image_piece':
864                scene['image_uid']=cvalue
865            elif ctype=='pilot_keywords':
866                scene['keywords']=cvalue
867                updated_values.append(scene)
868                new_scene=True
869            elif ctype=='pilot_questions':
870                scene['questions']=cvalue
871                updated_values.append(scene)
872                new_scene=True
873        return updated_values           
874
875
876    def updateChapterList(self, old_values):
877        """ Backward compatibility:
878            Converts a chapterlist to use dicts instead of tuples. Returns updated list """
879        updated_values=[]
880        if 'pilot_keywords' in [ct for cv, ct in old_values]: # Pilots need little more work
881            return self._updatePilot(old_values)
882        for cvalue, ctype in old_values:                   
883            if ctype in ['multiple_choices','poll','choices']:
884                q,correct,wrong=cvalue
885                if ctype=='multiple_choices':
886                    choices=[(c, 1) for c in correct]
887                    choices+=[(w, 0) for w in wrong]
888                    shuffle(choices)
889                elif ctype=='choices':
890                    choices=[(correct,1)]
891                    choices+=[(w, 0) for w in wrong]
892                    shuffle(choices)
893                elif ctype=='poll':
894                    choices=[(c,1) for c in correct+wrong]
895                new_chapter={'text':q, 'type':ctype, 'choices':choices}
896            elif ctype in ['image_piece','audio_piece','media_piece']:
897                new_chapter={'text':'', 'type':ctype, 'uid':cvalue}
898            elif ctype=='embed_block':
899                new_chapter={'text':'', 'type':'embed_block', 'embed':cvalue}
900            elif ctype=='guidelines':
901                new_chapter={'text':cvalue, 'type':'text_block'}
902            else:
903                new_chapter={'text':cvalue, 'type':ctype}
904            changed=True
905            updated_values.append(new_chapter)
906        return updated_values
907
908    def set(self, instance, value, **kwargs):
909        print 'ChapterField.set called with value: ', value, kwargs
910        if not value:
911            return
912        lt = getToolByName(instance, 'lemill_tool')
913        changed=False
914        if kwargs.get('_initializing_', False):
915            old_values=self.getDefault(instance)
916            changed=True
917        else:
918            old_values=self.getRaw(instance, **kwargs)
919        print 'old values returned:', old_values
920        references_to_add=[]
921        references_to_remove=[]       
922        if isinstance(old_values, list) and old_values:
923            # check if this needs conversion
924            if isinstance(old_values[0], tuple):
925                print 'updating chapter list'
926                old_values=self.updateChapterList(old_values)
927                changed=True
928                # conversion done.       
929        ### The normal case: widget gives a dictionary ###   
930        if isinstance(value, dict): 
931            edited=value['edited']
932            new_values=copy(old_values)
933
934            if edited!=-1: # one chapter has been edited
935                ctype=value['chapter_type']
936                old_chapter=old_values[edited]           
937                # build _one_ chapter based on input
938                new_chapter={'type':ctype}
939                if ctype in ['multiple_choices','choices','poll']:
940                    new_chapter['text']=value['question']
941                    choices=[]
942                    for answer in value['answers']:
943                        choices.append((answer['order'], answer['text'], answer['is_correct']))
944                    choices.sort()
945                    choices=[(y,z) for (x,y,z) in choices]
946                    new_chapter['choices']=choices
947                elif ctype=='pilot_scene':
948                    new_chapter['text']=u'\n'.join(value['keywords'] or value['questions'])
949                    if value['audio_file']:
950                        new_piece = lt.createPieceFromFile(value['audio_file'], instance)
951                        uid=new_piece.UID()
952                    else:
953                        uid=value['audio_uid']
954                    old_uid=old_chapter.get('audio_uid','')
955                    if old_uid:
956                        if old_uid!=uid:
957                            references_to_remove.append(old_uid)
958                            references_to_add.append(uid)
959                    else:
960                        references_to_add.append(uid)
961                    new_chapter['audio_uid']=uid               
962                    if value['image_file']:
963                        new_piece = lt.createPieceFromFile(value['image_file'], instance)
964                        uid=new_piece.UID()
965                    else:
966                        uid=value['image_uid']
967                    old_uid=old_chapter.get('image_uid','')
968                    if old_uid:
969                        if old_uid!=uid:
970                            references_to_remove.append(old_uid)
971                            references_to_add.append(uid)
972                    else:
973                        references_to_add.append(uid)
974                    new_chapter['image_uid']=uid
975                    new_chapter['questions']=value['questions']
976                    new_chapter['keywords']=value['keywords']
977                elif ctype in ['image_piece','audio_piece','media_piece']:
978                    file=value.get('file','')
979                    new_chapter['text']=''
980                    if file:
981                        new_piece = lt.createPieceFromFile(file, instance)
982                        uid=new_piece.UID()
983                    else:
984                        uid=value['uid']
985                    old_uid=old_chapter.get('uid','')
986                    if old_uid:
987                        if old_uid!=uid:
988                            references_to_remove.append(old_uid)
989                            if uid:
990                                references_to_add.append(uid)
991                    elif uid:
992                        references_to_add.append(uid)
993                    new_chapter['uid']=uid
994                elif ctype == 'embed_block':
995                    new_chapter['text']=''
996                    new_chapter['embed']=value['embed']
997                else:           
998                    new_chapter['text']=value['text']
999                if new_chapter!=old_chapter:
1000                    changed=True
1001                new_values[edited]=new_chapter
1002            ## Deleting ##
1003            deleted=value.get('deleted',[])
1004            n_o=value.get('new_order',[])
1005            if deleted:
1006                vals=[]
1007                for i, val in enumerate(new_values):
1008                    if i in deleted:
1009                        uid=val.get('uid','')
1010                        if uid:
1011                            references_to_remove.append(uid)
1012                    else:
1013                        vals.append(val)
1014                new_values=vals
1015                changed=True
1016                if n_o:
1017                    n_o=[val for i, val in enumerate(n_o) if i not in deleted]           
1018            ## Sorting ##
1019            if n_o:
1020                sortable=zip(n_o, new_values)
1021                sortable.sort()
1022                new_values=[val for o, val in sortable]
1023                changed=True                       
1024            ## Adding new chapters ##
1025            new_chapters=value.get('new_chapters',[])
1026            if new_chapters:
1027                new_values+=new_chapters
1028                changed=True
1029        ### The case where field's get-method feeds field's set-method (archetype update, copying)
1030        elif isinstance(value, list):
1031            print 'setting from a list'
1032            references_new=[]
1033            for chapter in value:
1034                uid=chapter.get('uid','')
1035                if uid:
1036                    references_new.append(uid)
1037            references_old=[]
1038            for chapter in old_values:
1039                uid=chapter.get('uid','')
1040                if uid:
1041                    references_old.append(uid)
1042            references_to_add=[uid for uid in references_new if uid not in references_old]
1043            references_to_remove=[uid for uid in references_old if uid not in references_new]
1044            if value!=old_values:
1045                changed=True
1046            new_values=value
1047        ### update references if necessary
1048        if references_to_add or references_to_remove:
1049            tool = instance.reference_catalog
1050            for uid in references_to_add:
1051                tool.addReference(instance, uid, self.relationship)
1052            for uid in references_to_remove:
1053                tool.deleteReference(instance, uid, self.relationship)
1054        if changed:
1055            # update cleaned text
1056            print 'trying to update cleaned chapters'
1057            self.updateCleanedChapters(instance, new_values)       
1058            # set new value
1059            print 'new_values for ObjectField.set:', new_values
1060            ObjectField.set(self, instance, new_values, **kwargs)
1061
1062    def getObjectByUID(self, instance, uid, catalog_only=False):
1063        """ Helper method for fetching mediapieces from references """
1064        uid_catalog = getToolByName(instance, 'uid_catalog')
1065        if not uid:
1066            return None # this is just a newly created placeholder
1067        results=uid_catalog(UID=uid)
1068        try:
1069            return results[0].getObject()
1070        except IndexError:
1071            return None
1072
1073    def getLength(self, piece):
1074        as_time=piece.getLength()
1075        return {'hour':as_time/3600,'minute':(as_time % 3600)/60,'second':as_time % 60}           
1076
1077registerField(ChapterField,
1078    title='Chapter field',
1079    description=('Chapter field'),
1080)
1081
1082class AudioField(FileField):
1083    """ field for mp3:s """
1084    _properties = FileField._properties.copy()
1085    _properties.update({
1086        'widget' : AudioWidget,
1087    })
1088
1089    def getLength(self, instance):
1090        i = aq_base(instance)
1091        file = AudioField.get(self,instance)
1092        if not shasattr(i, '_mp3length'):
1093            instance._mp3length=get_length(str(file))
1094        print instance._mp3length
1095        if instance._mp3length==0:
1096            instance._mp3length=get_length(str(file))
1097        return {'hour':instance._mp3length/3600,'minute':(instance._mp3length % 3600)/60,'second':instance._mp3length % 60}
1098
1099    def set(self, instance, value, **kwargs):
1100        """ allow only audio here """
1101        FileField.set(self, instance, value, **kwargs)
1102        file=self.get(instance)
1103        if self.getContentType(instance).startswith('audio/'):
1104            instance._mp3length=get_length(str(file))
1105        elif str(file)!='':
1106            FileField.set(self, instance, "DELETE_FILE", **kwargs)
1107
1108registerField(AudioField,
1109    title='Audio field',
1110    description=('Field that accepts only mp3:s'),
1111)
1112
1113class LeMillLinksField(ObjectField):
1114    """ A field that stores LeMill internal links """
1115    __implements__ = ObjectField.__implements__
1116    _properties = ObjectField._properties.copy()
1117    _properties.update({
1118        'widget' : LeMillLinksWidget,
1119    })
1120
1121    security = ClassSecurityInfo()
1122
1123    def set(self, instance, value, **kwargs):
1124        ObjectField.set(self, instance, value, **kwargs)
1125
1126    def getRaw(self, instance, **kwargs):
1127        version = None
1128        try:
1129            version = instance.REQUEST.get('version', None)
1130        except AttributeError:
1131            pass
1132        if version:
1133            value = instance.getFieldHistory(self.getName(), version)
1134        else:
1135            value = ObjectField.get(self, instance, **kwargs)
1136        return value
1137
1138    def getRawEdit(self, instance, **kwargs):
1139        """ Returns a text for editing """
1140        pc = getToolByName(instance, 'portal_catalog')
1141        field_data = ObjectField.get(self, instance, **kwargs)#XXX See if any version control is needed here
1142        if field_data == None:
1143            return ""
1144        # find uids and links - replace them with urls
1145        links = re.findall('\{.*?\}', field_data)
1146        for link in links:
1147            linktext = link[1:-1]
1148            if linktext.lower().startswith('http://') or linktext.lower().startswith('https://'):
1149                field_data = field_data.replace(link, linktext)
1150            elif linktext.lower().startswith('uid:'):
1151                uid = linktext[4:]
1152                results = pc({'UID':uid})
1153                if len(results)>0:
1154                    field_data = field_data.replace(link, results[0].getURL())
1155                else:
1156                    field_data = field_data.replace(link, '')
1157        return field_data
1158
1159    def get(self, instance, **kwargs):
1160        pc = getToolByName(instance, 'portal_catalog')
1161        letool = getToolByName(instance,'lemill_tool')
1162        version = None
1163        try:
1164            version = instance.REQUEST.get('version',None)
1165        except AttributeError:
1166            pass
1167        if version:
1168            field_data = instance.getFieldHistory(self.getName(), version)
1169        else:
1170            field_data = ObjectField.get(self, instance, **kwargs)
1171        if not field_data:
1172            return ""
1173        links = re.findall('\{.*?\}', field_data)
1174        for link in links:
1175            linktext = link[1:-1]
1176            if linktext.lower().startswith('http://') or linktext.lower().startswith('https://'):
1177                repl='<a href="%s">%s</a>' % (linktext, letool.shorten_url(linktext))
1178                field_data = field_data.replace(link, repl)
1179            elif linktext.lower().startswith('uid:'):
1180                uid = linktext[4:]
1181                results = pc({'UID':uid})
1182                repl=''
1183                if results:
1184                    repl='<a href="%s">%s</a>' % (results[0].getURL(), results[0].Title)
1185                field_data = field_data.replace(link, repl)
1186        field_data = field_data.strip()
1187        value = field_data.split('\n')
1188        return value
1189
1190registerField(LeMillLinksField,
1191    title='LeMill Links Field',
1192    description=('LeMill Links Field'),
1193)
Note: See TracBrowser for help on using the repository browser.