Changeset 3045 for trunk


Ignore:
Timestamp:
08/10/10 17:40:55 (10 years ago)
Author:
jukka
Message:

There are so many changes coming that I better start commiting even these unfinished versions. ExerciseTemplate? is the most broken thing now, other places that try to use bodytext from webpages/exercises may have issues too.

Location:
trunk
Files:
2 added
40 edited

Legend:

Unmodified
Added
Removed
  • trunk/CommonMixIn.py

    r3029 r3045  
    7474        no_title= not title.startswith(o_type) 
    7575        object_age=DateTime()-DateTime(self.CreationDate()) 
    76         return object_age<0.1 and default_id and no_title 
     76        return (object_age<0.1 and default_id and no_title) or not title 
    7777 
    7878 
     
    353353            return new_id 
    354354        return False 
     355 
     356 
     357    security.declarePublic('getRequiredFieldNames') 
     358    def getRequiredFieldNames(self): 
     359        """ Returns field names, not actual field objects """ 
     360        a=[field.getName() for field in self.schema.fields() if field.required] 
     361        print 'getRequiredFieldNames: ', a 
     362        return a 
     363             
     364 
    355365 
    356366    ####### Schema update ############### 
     
    407417    security.declareProtected(MODIFY_CONTENT, 'indexObject') 
    408418    def indexObject(self): 
     419        print 'CommonMixIn.indexObject called.' 
     420        t=time.time() 
    409421        pc = getToolByName(self, 'portal_catalog') 
    410422        url = '/'.join( self.getPhysicalPath() ) 
     
    413425            url=str(url) # This gets rid of CatalogError: The object unique id must be a string. 
    414426        ZCatalog.catalog_object(pc, self, url, [], update_metadata=1, pghandler=None) 
     427        print '...CommonMixIn.indexObject finished:', time.time()-t 
     428 
    415429         
    416430    security.declareProtected(MODIFY_CONTENT, 'unindexObject') 
     
    432446        This uses ZCatalog's catalog_object instead of Plone's CatalogTool and passes workflow while at it 
    433447        this also assumes that all LeMill objects only use portal_catalog and doesn't do multiple catalogs.""" 
     448        print 'CommonMixIn.reindexObject called.', idxs 
     449        t=time.time() 
    434450        # Never index trashed objects 
    435451        if self.aq_parent.getId()=='trash': 
     
    451467        # This is done with: 
    452468        # self._catalogUID(self) 
     469        print '...CommonMixIn.reindexObject finished:', time.time()-t 
    453470 
    454471    security.declareProtected(MANAGE_PORTAL, 'showCatalogObject') 
     
    769786        """Redirect to current location.""" 
    770787        rc = getToolByName(self, 'reference_catalog') 
    771         return rc.lookupObject(self.redirect_to, REQUEST) 
     788        obj = rc._objectByUUID(self.redirect_to) 
     789        if REQUEST and obj: 
     790            return REQUEST.RESPONSE.redirect(obj.absolute_url()) 
     791        #return rc.lookupObject(self.redirect_to, REQUEST) 
     792        # rc.lookupObject returns an error if none found, so we won't use it 
     793 
    772794 
    773795registerType(Redirector) 
  • trunk/ExerciseMaterial.py

    r3002 r3045  
    2020from Products.Archetypes.public import * 
    2121from Products.CMFCore.utils import getToolByName 
    22  
    2322from config import PROJECTNAME, MODIFY_CONTENT, VIEW, to_unicode 
    2423from FieldsWidgets import ChapterField, ExerciseWidget 
     
    2827from random import shuffle 
    2928from Products.LeMill import LeMillMessageFactory as _ 
    30  
    3129import re 
    3230 
     
    4543        default_output_type = 'text/x-html-captioned', 
    4644        default_content_type = 'text/html', 
    47         default=[('','guidelines'),], 
     45        default=[{'type':'guidelines', 'text':''}], 
    4846        widget=ExerciseWidget(label = "Body text", 
    4947            label_msgid = "label_bodytext", 
     
    5452 
    5553schema = material_schema + community_editing_schema + no_description + draft_schema + exercise_schema 
    56  
    5754schema = schema.copy() 
    5855schema.moveField('rights', pos='bottom') 
     
    6259schema.moveField('editingMode', before='hideDrafts') 
    6360 
    64 fill_in_the_blanks=re.compile(r"""(?P<filler>({.*?})+)""",  re.IGNORECASE) 
     61blanks= re.compile("(\{.*?\})") 
    6562 
    6663class ExerciseMaterial(Material): 
    6764    """Exercise page""" 
    68  
    6965    schema = schema 
    70  
    7166    meta_type = "ExerciseMaterial" 
    7267    archetype_name = "ExerciseMaterial" 
    7368    default_location = 'content/exercises' 
    74  
    75  
    7669    security = ClassSecurityInfo() 
    7770    security.declareObjectPublic() 
    78  
    7971    aliases = { 
    8072        '(Default)' : 'fullscreen_view', 
     
    8476    } 
    8577 
    86     security.declarePrivate('manage_afterAdd') 
    87     def manage_afterAdd(self, item, container): 
    88         Material.manage_afterAdd(self, item, container) 
    89  
    90     def getOnlyText(self): 
    91         field=self.getField('bodyText') 
    92         values = field.get(self) 
    93         dump= '\n'.join([x[0] for x in values if x[1] not in ['media_piece', 'multiple_choices', 'choice']]) 
    94         return dump 
    95  
    96     def getOnlyRawText(self): 
    97         field=self.getField('bodyText') 
    98         values = field.getRaw(self) 
    99         dump= '\n'.join([x[0] for x in values if x[1] not in ['media_piece', 'multiple_choices', 'choice']]) 
    100         return dump 
    101  
    102  
    103     def isCorrectAnswer(self, chapter, answer_index): 
    104         """ given a chapter having a multiple choice question, is this answer in correct answers? """  
    105         return answer_index < len(chapter[1]) 
    106          
    107  
    108     security.declareProtected(MODIFY_CONTENT,'delChapter') 
    109     def delChapter(self, REQUEST): 
    110         """ delete chapter """ 
    111         field = self.getField('bodyText') 
    112         field.delChapter(self, int(REQUEST.get('delete'))) 
    113         return REQUEST.RESPONSE.redirect(self.absolute_url()+'/edit') 
    114  
    115     def getAllAnswers(self, chapter): 
    116         """ Will return the list of the shuffled combined correct and incorrect answers """ 
    117         # Chapter should have the structure: [question,correct_list,incorrect_list] 
    118         if isinstance (chapter[1], str): 
    119             if chapter[1] == '': 
    120                 chapter[1] = [] 
    121             else: 
    122                 chapter[1] = [chapter[1]] 
    123         all_answers = chapter[1] + chapter[2] 
    124         extended_answers = [] 
    125         for numerated_answer in enumerate(all_answers): 
    126             extended_answers.append(numerated_answer) 
    127         shuffle(extended_answers) 
    128         return extended_answers 
    129  
    130     def get_values_from_fitbs(self, chapter_index): 
    131         """ find words in {}:s and return them as dict """ 
    132          
    133         text=self.getBodyText() 
    134         text=text[chapter_index][0] 
    135         matches=fill_in_the_blanks.findall(text) 
    136         results={} 
    137         index=0 
    138         for m in matches: 
    139             answer=m[0] 
    140             answer=answer.strip(' {}') 
    141             answer=answer.split('}{') 
    142             results['exercise_%s_answer_%s' % (chapter_index, index)]=answer 
    143             index=index+1 
    144         return results 
    145  
    146     def replace_blanks_with_input_tag(self, chapter_index, answers=True, readonly=True): 
    147         """ find words in { } and replace them with input boxes """ 
    148         replacement="""<input type="text" value="%s"%s name="exercise_%s_answer_%s" id="exercise_%s_answer_%s" />""" 
    149         self.iterator=0 
    150          
    151         def rep(match): 
    152             answer_index=self.iterator 
    153             if answers: 
    154                 value=match.group('filler').strip(' {}') 
    155                 value=value.replace('}{','/') 
    156             else: 
    157                 value="" 
    158             self.iterator+=1 
    159             return replacement % (value, readonly, chapter_index, answer_index, chapter_index, answer_index) 
    160  
     78    def separateBlanksFromText(self, text): 
     79        pieces=[] 
     80        answers=[] 
     81        previous_was_blank=False 
     82        for piece in re.split(blanks, text): 
     83            if piece.startswith('{'): 
     84                answer=piece.strip('{} ') 
     85                if previous_was_blank: 
     86                    answers[-1].append(answer) 
     87                else: 
     88                    answers.append([answer]) 
     89            else: 
     90                pieces.append(piece) 
     91        return pieces, answers 
     92             
     93    def replaceBlanksWithInputTag(self, text, index, readonly=False, give_answers=False): 
     94        pieces, answers=self.separateBlanksFromText(text) 
     95        inputs=[] 
     96        results=[] 
    16197        if readonly: 
    16298            readonly=' readonly="1"' 
    16399        else: 
    164             readonly=''             
    165         text=self.getBodyText() 
    166         text=text[chapter_index][0] 
    167         text=fill_in_the_blanks.sub(rep, text) 
    168         del self.iterator 
    169         return text 
    170  
    171     def replace_blanks_with_your_answers(self, text, your_answerdict, i): 
    172         """ find braces {text} and add own answers before them in brackets [mytext]{text} """ 
    173         self.iterator=0 
     100            readonly='' 
     101        for i, answer in enumerate(answers): 
     102            if give_answers: 
     103                answer=''.join('{%s}' % a for a in answer) 
     104            else: 
     105                answer="" 
     106            a="""<input type="text" value="%s"%s name="exercise_%s_answer_%s" id="exercise_%s_answer_%s" />""" % (answer, readonly, index, i, index, i) 
     107            inputs.append(a) 
     108        for piece in pieces: 
     109            results.append(piece) 
     110            if inputs: 
     111                results.append(inputs.pop(0)) 
     112        if inputs: # leftovers 
     113            results+=inputs 
     114        return u''.join(results) 
     115 
     116    def checkFillInTheBlankAnswersFromForm(self, form, text, index): 
     117        """ Checks fill-in-the-blanks exercise from web form, 
     118            returns completed exercise text and lists of correct and wrong answers """ 
     119        pieces, answers=self.separateBlanksFromText(text) 
     120        corrects=wrongs=0 
     121        my_answers=[] 
     122        for i, choices in enumerate(answers): 
     123            a=to_unicode(form.get('exercise_%s_answer_%s' % (index, i), '')) 
     124            if a in choices: 
     125                corrects+=1 
     126                my_answers.append((a, '% ')) 
     127            else: 
     128                wrongs+=1 
     129                my_answers.append((a, '* ')) 
     130        results=[] 
     131        for piece in pieces: 
     132            results.append(piece) 
     133            if answers and my_answers: 
     134                ans, mark=my_answers.pop(0)                 
     135                results.append('/'.join(answers.pop(0))) 
     136                results.append('[%s%s]' % (mark, ans)) 
     137        results.append('\n') 
     138        return u''.join(results), corrects, wrongs 
     139 
     140    def checkMultipleChoiceFromForm(self, form, question, choices, index): 
     141        """ Checks form for multiple choice test, returns exercise with correct answers marked and number of right and wrong answers """ 
     142        corrects=wrongs=0 
     143        results=[question,'\n\n'] 
     144        for i, choice in enumerate(choices): 
     145            a=form.get('exercise_%s_choice_%s' % (index, i), '') 
     146            if a: 
     147                results.append('[x]') 
     148                if not choice[1]: 
     149                    results.append(' <- [ ]\t') 
     150                    wrongs+=1 
     151                else: 
     152                    results.append('\t\t') 
     153                    corrects+=1 
     154            else: 
     155                results.append('[ ]') 
     156                if choice[1]: 
     157                    results.append(' <- [x]\t') 
     158                    wrongs+=1 
     159                else: 
     160                    results.append('\t\t') 
     161                    corrects+=1                 
     162            results.append(choice[0]) 
     163            results.append('\n') 
     164        results.append('\n') 
     165        return u''.join(results), corrects, wrongs                 
     166 
     167    def checkChoiceFromForm(self, form, question, choices, index): 
     168        """ Checks form for choice test, returns exercise with correct answer marked, correctness as boolean """ 
     169        results=[question,'\n\n'] 
     170        a=int(form.get('exercise_%s_choice' % index, -1)) 
     171        correct=False 
     172        for i, choice in enumerate(choices): 
     173            if a==i: 
     174                results.append('[x]') 
     175                if choice['is_correct']: 
     176                    correct=True 
     177                    results.append('\t\t') 
     178                else: 
     179                    results.append(' <- [ ]\t') 
     180            else: 
     181                results.append('[ ]') 
     182                if choice['is_correct']: 
     183                    results.append(' <- [x]\t') 
     184                else:                     
     185                    results.append('\t\t') 
     186            results.append(choice['text']) 
     187            results.append('\n') 
     188        results.append('\n') 
     189        return u''.join(results), correct                 
     190 
     191    def checkPollFromForm(self, form, question, choices, index): 
     192        """ Reports back the selected poll option """ 
     193        results=[question,'\n\n'] 
     194        a=int(form.get('exercise_%s_choice' % index, -1)) 
     195        answer=choices[a]['text'] 
     196        results.append('[x]\t\t') 
     197        results.append(answer) 
     198        results.append('\n') 
     199        return u''.join(results)                 
    174200         
    175         def rep(match): 
    176             value=match.group('filler')             
    177             your_answer=your_answerdict.get('exercise_%s_answer_%s' % (i, self.iterator), '') 
    178             self.iterator+=1  
    179             return '[%s]%s' % (your_answer, value)  
    180         text=fill_in_the_blanks.sub(rep, text) 
    181         del self.iterator 
    182         return text 
    183  
    184     def replace_blanks_with_feedback(self, text, your_answerdict, i): 
    185         """ prepare the exercise for web-based feedback, make a html version out of it """ 
    186         fitb_correct_repl = u"""<span class="correct_fitb">%s</span>""" 
    187         fitb_incorrect_repl = u"""<span class="incorrect_fitb">%s</span> <span class="corrected_fitb">%s</span>""" 
    188         self.iterator = 0 
    189  
    190         def rep(match): 
    191             value = match.group('filler') 
    192             your_answer_all = your_answerdict.get('exercise_%s_answer_%s' % (i, self.iterator), '') 
    193             your_answer = your_answer_all[0] 
    194             self.iterator+=1 
    195  
    196             def get_one_value_from_fitbs(values): 
    197                 """ get rid of brackets and return the first value, if it exists """ 
    198                 answer = values.strip(' {}') 
    199                 answer = answer.split('}{') 
    200                 if len(answer)>0: 
    201                     return answer[0] 
    202                 else: 
    203                     return values 
    204  
    205             if your_answer_all[1] == 'correct': 
    206                 return fitb_correct_repl % (to_unicode(your_answer.strip())) 
    207             elif your_answer_all[1] == 'incorrect': 
    208                 value = get_one_value_from_fitbs(value) 
    209                 return fitb_incorrect_repl % (to_unicode(your_answer.strip()), to_unicode(value)) 
    210         text = fill_in_the_blanks.sub(rep, to_unicode(text)) 
    211         del self.iterator 
    212         return text 
    213  
    214     def checkQuesIntegrity(self, chapter_type, chapter_text): 
    215         """ checks should we display question in exercise_view or is question incomplete """ 
    216         # choice question should have at least one correct and one false variant 
    217         if chapter_type == 'choice': 
    218             if isinstance (chapter_text[1], str): 
    219                 chapter_text[1] = [chapter_text[1]] 
    220             if chapter_text[1] == ['']: 
    221                 chapter_text[1] = [] 
    222             if chapter_text[2] == ['']: 
    223                 chapter_text[2] = [] 
    224             if len(chapter_text[1]) == 0 or len(chapter_text[2]) == 0: 
    225                 return False 
    226         # multiple choices question should have at least one variant 
    227         elif chapter_type == 'multiple_choices': 
    228             if chapter_text[1] == ['']: 
    229                 chapter_text[1] = [] 
    230             if chapter_text[2] == ['']: 
    231                 chapter_text[2] = [] 
    232             answers_total = chapter_text[1] + chapter_text[2] 
    233             if len(answers_total) == 0: 
    234                 return False 
    235         # fill in the blanks question has to contain at least one gap 
    236         elif chapter_type == 'fill_in_the_blanks': 
    237             if not re.findall('{.*?}', chapter_text): 
    238                 return False 
    239         # open ended question has to contain question text 
    240         elif chapter_type == 'open_ended': 
    241             if chapter_text.strip('</p> ') == '': 
    242                 return False 
    243         return True 
    244  
    245     def showWebFeedback(self, REQUEST): 
    246         """ Deals with the web-based feedback: returns ([chapter1 data, chapter2 data, ..], message) """ 
    247         message = (None, 'warn') 
    248         all_chapters = []    # [{'chapter_type': type, 'question_text': 'text', .. }, .. ] 
    249         letool = getToolByName(self,'lemill_tool') 
    250         mailbody = u'' 
    251  
    252         # variables for calculating test summary later 
    253         all_choice_percentages = {}     
    254         all_multiple_percentages = {}   # dictionary for storing all multiple_choices given percentages {100: 3, 50: 1} 
    255         fill_in_the_blanks_quescount = 0 
    256         all_blanks_count = 0 
    257         all_blanks_correct = 0 
    258         showSummary = False 
    259          
    260         exercise_body = self.getBodyText() 
    261         # Go through the exercise body 
    262         i = 0 
    263         for (chapter_text, chapter_type) in exercise_body: 
    264  
    265             if chapter_type in ['text_block','media_piece','embed_block']: 
    266                 chapter_data = {'chapter_type':chapter_type, 'question_text':chapter_text} 
    267                 all_chapters.append(chapter_data) 
    268              
    269             if chapter_type == 'choice' and self.checkQuesIntegrity(chapter_type, chapter_text): 
    270                showSummary = True 
    271                choice_feedback = self.choice_feedback(REQUEST, i, chapter_text) # get [array of variants, percentage, mailbody] 
    272                variants = choice_feedback[0] 
    273                percentage = choice_feedback[1] 
    274                mailbody += choice_feedback[2] 
    275                chapter_data = {'chapter_type':chapter_type, 'question_text':chapter_text[0], 'variants':variants} 
    276                all_chapters.append(chapter_data) 
    277                if (all_choice_percentages.has_key(percentage)): 
    278                    all_choice_percentages[percentage] = all_choice_percentages[percentage] + 1 
    279                else: 
    280                    all_choice_percentages[percentage] = 1 
    281                  
    282             elif chapter_type == 'multiple_choices' and self.checkQuesIntegrity(chapter_type, chapter_text) == True: 
    283                 showSummary = True 
    284                 multiple_choices_feedback = self.multiple_choices_feedback(REQUEST, i, chapter_text) # get [array of variants, percentage, mailbody] 
    285                 variants = multiple_choices_feedback[0] 
    286                 percentage = multiple_choices_feedback[1] 
    287                 mailbody += multiple_choices_feedback[2] 
    288                 chapter_data = {'chapter_type':chapter_type, 'question_text':chapter_text[0], 'variants':variants, 'percentage':percentage} 
    289                 all_chapters.append(chapter_data) 
    290                 if (all_multiple_percentages.has_key(percentage)): 
    291                     all_multiple_percentages[percentage] = all_multiple_percentages[percentage] + 1 
    292                 else: 
    293                     all_multiple_percentages[percentage] = 1 
    294                  
    295             elif chapter_type == 'fill_in_the_blanks' and self.checkQuesIntegrity(chapter_type, chapter_text) == True: 
    296                 showSummary = True 
    297                 correct_answer_dict = self.get_values_from_fitbs(i) 
    298                 my_answer_dict = {} 
    299                 my_answer_dict_for_mail = {} 
    300                 correct_answers_n = 0 
    301                 all_answers_n = len(correct_answer_dict.keys()) 
    302                 for it in range(all_answers_n): 
    303                     my_answer = REQUEST.get('exercise_%s_answer_%s' % (i,it),'') 
    304                     correct_answer = [to_unicode(c.strip()) for c in correct_answer_dict['exercise_%s_answer_%s' % (i,it)]] 
    305                     if to_unicode(my_answer.strip()) in correct_answer: 
    306                         my_answer_dict['exercise_%s_answer_%s' % (i,it)]=(my_answer, 'correct') 
    307                         correct_answers_n+=1 
    308                     else: 
    309                         my_answer_dict['exercise_%s_answer_%s' % (i,it)]=(my_answer, 'incorrect') 
    310                     my_answer_dict_for_mail['exercise_%s_answer_%s' % (i,it)] = my_answer         
    311                 # Using external helper method to replace the blanks and construct the html 
    312                 exercise_text = self.replace_blanks_with_feedback(chapter_text, my_answer_dict, i) 
    313  
    314                 chapter_data = {'chapter_type':chapter_type, 'question_text':exercise_text, 'gaps_count':all_answers_n, 'correct_gaps':correct_answers_n} 
    315                 all_chapters.append(chapter_data) 
    316  
    317                 mail_exercise_text = self.replace_blanks_with_your_answers(chapter_text, my_answer_dict_for_mail, i) 
    318                 mail_exercise_text = mail_exercise_text.replace("<p>","") 
    319                 mail_exercise_text = mail_exercise_text.replace("</p>","") 
    320                 mail_exercise_text = letool.html_to_text(mail_exercise_text) 
    321                 mailbody += self.translate(u"""Answer entered by student is in the square brackets. The correct answer is in the logical brackets. 
    322 In the following fill-in-the-blanks exercise student answers and correct answers were: 
    323 %(exercise_text)s 
    324 Got %(correct_answers_n)s/%(all_answers_n)s correct!\n 
    325 """,domain='lemill') % {'exercise_text':to_unicode(mail_exercise_text), 'correct_answers_n':correct_answers_n, 'all_answers_n':all_answers_n}             
    326                  
    327                 fill_in_the_blanks_quescount += 1 
    328                 all_blanks_count += all_answers_n 
    329                 all_blanks_correct += correct_answers_n 
    330                  
    331             elif chapter_type == 'open_ended' and self.checkQuesIntegrity(chapter_type, chapter_text) == True: 
    332                 student_answer = REQUEST.get('exercise_%s_answer' % i, '') 
    333                 answer = student_answer.replace('\n','<br />') 
    334                 chapter_data = {'chapter_type':chapter_type, 'question_text':chapter_text, 'answer':answer} 
    335                 all_chapters.append(chapter_data) 
    336                 question = chapter_text.replace("<p>", "") 
    337                 question = question.replace("</p>", "") 
    338                 question = question.replace("<br/>", "\n") 
    339                 mailbody += self.translate(u"""Answer to question:  
    340 %(question)s  
    341 was: 
    342 %(answer)s 
    343 Teacher will give feedback to this answer.\n 
    344 """,domain='lemill') % {'question':to_unicode(question), 'answer':to_unicode(student_answer)} 
    345                  
    346             i += 1 # increase the thing 
    347          
    348         summary = "" 
    349         if showSummary == True:         
    350             summary += "<br /><b>"+self.translate(u"Summary of test results", domain='lemill')+"</b><br /><br />" 
    351          
    352         # choice questions summary 
    353         if len(all_choice_percentages) != 0: 
    354             choice_summary = "" 
    355             choice_ques_count = 0 
    356             sum = 0; 
    357             for key in sorted(all_choice_percentages.iterkeys(), reverse=True): 
    358                 percent = key 
    359                 percent_count = all_choice_percentages[key] 
    360                 sum += percent_count*(float(percent)/100) 
    361                 choice_ques_count += percent_count 
    362                 if percent == 100: 
    363                     choice_summary += self.translate(u"Correct answers (100%%): %(correct_answers_count)s", domain='lemill') % {'correct_answers_count': percent_count} 
    364                 elif percent == 0: 
    365                     choice_summary += self.translate(u"Incorrect answers (0%%): %(wrong_answers_count)s", domain='lemill') % {'wrong_answers_count': percent_count} 
    366                 choice_summary += "<br />" 
    367             choice_overall_percentage = (sum/choice_ques_count)*100 
    368             choice_rounded_percentage = self.round_number(choice_overall_percentage) 
    369             choice_summary += self.translate(u"Percentage: %(percentage)s%%",domain='lemill') % {'percentage':choice_rounded_percentage} 
    370             summary += self.translate(u"Multiple choice questions: %(ques_count)s", domain='lemill') % {'ques_count':choice_ques_count} 
    371             summary += "<br />" + choice_summary + "<br /><br />" 
    372          
    373         # multiple_choices questions summary 
    374         if len(all_multiple_percentages) != 0: 
    375             multiple_choice_summary = "" 
    376             choice_ques_count = 0 
    377             sum = 0; 
    378             for key in sorted(all_multiple_percentages.iterkeys(), reverse=True): 
    379                 percent = key 
    380                 percent_count = all_multiple_percentages[key] 
    381                 sum += percent_count*(float(percent)/100) 
    382                 choice_ques_count += percent_count 
    383                 if percent == 100: 
    384                     multiple_choice_summary += self.translate(u"Correct answers (100%%): %(correct_answers_count)s", domain='lemill') % {'correct_answers_count': percent_count} 
    385                 elif percent == 0: 
    386                     multiple_choice_summary += self.translate(u"Incorrect answers (0%%): %(wrong_answers_count)s", domain='lemill') % {'wrong_answers_count': percent_count} 
    387                 else: 
    388                     multiple_choice_summary += self.translate(u"Partly correct answers (%(percentage)s%%): %(answers_count)s", domain='lemill') % {'percentage': percent, 'answers_count': percent_count}  
    389                 multiple_choice_summary += "<br />" 
    390             choice_overall_percentage = (sum/choice_ques_count)*100 
    391             choice_rounded_percentage = self.round_number(choice_overall_percentage) 
    392             multiple_choice_summary += self.translate(u"Percentage: %(percentage)s%%",domain='lemill') % {'percentage':choice_rounded_percentage} 
    393             summary += self.translate(u"Multiple response questions: %(ques_count)s", domain='lemill') % {'ques_count':choice_ques_count} 
    394             summary += "<br />" + multiple_choice_summary + "<br /><br />" 
    395          
    396         # fill_in_the_blanks questions summary             
    397         if fill_in_the_blanks_quescount != 0: 
    398             all_blanks_false = all_blanks_count - all_blanks_correct 
    399             fill_in_the_blanks_percentage = (float(all_blanks_correct)/all_blanks_count)*100 
    400             rounded_percentage = self.round_number(fill_in_the_blanks_percentage) 
    401             blanks_summary = self.translate(u"""Fill-in-the-blanks questions: %(ques_count)s  
    402 Number of blanks: %(blanks_count)s 
    403 Correct answers: %(correct_answers_count)s 
    404 Incorrect answers: %(wrong_answers_count)s 
    405 Percentage: %(percentage)s%% 
    406 """,domain='lemill') % {'ques_count':fill_in_the_blanks_quescount, 'blanks_count':all_blanks_count, 'correct_answers_count':all_blanks_correct,'wrong_answers_count':all_blanks_false,'percentage':rounded_percentage} 
    407             summary += blanks_summary.replace("\n", "<br />") 
    408          
    409         self.test_summary = summary 
    410         mail_summary = letool.html_to_text(summary)      
    411          
    412         # If there is a user e-mail -> we try to get the e-mail sending functionality going 
    413         # All the other checks will be defined there, as it was before this web-based feedback 
    414         if REQUEST.get('students_email', ''): 
    415             message = self.sendAnswers(REQUEST, mailbody, mail_summary) 
    416         return (all_chapters, message) 
    417      
    418     def getTestSummary(self): 
    419         """ """ 
    420         return self.test_summary 
    421      
    422     def choice_feedback(self, REQUEST, exercise_nr, chapter_text): 
    423         """ this method will return [variants for web, percentage, feedback for mail] """ 
    424         choice_array = [] 
    425         question = chapter_text[0]                 
    426         correct_answer = chapter_text[1] 
    427         if isinstance(correct_answer, str): 
    428             correct_answer = [correct_answer] 
    429         answers_total = correct_answer + chapter_text[2] 
    430         my_answer = "" 
    431          
    432         answer_value = REQUEST.get('exercise_%s_checkbox' % (exercise_nr),'') 
    433         if (answer_value): 
    434             answer_value = int(answer_value) 
    435          
    436         # Need a number to get the order right 
    437         answer_number = REQUEST.get('exercise_%s_hiddencheck_%s' % (exercise_nr,0),'') 
    438         # check is correct answer or not 
    439         if answer_value == 0: 
    440             choice_array.append((answer_number, correct_answer[0], 'correct_mc')) 
    441             percentage = 100 
    442             my_answer = answers_total[0] 
     201    def checkOpenEndedFromForm(self, form, question, index): 
     202        """ Reports back the given answer """ 
     203        results=[question,'\n\n'] 
     204        a=form.get('exercise_%s_open_ended' % index, '') 
     205        results.append(a) 
     206        results.append('\n\n') 
     207        return u''.join(results)                            
     208 
     209    def checkExercise(self): 
     210        """ Builds a text dump about evaluating the exercise, used for web feedback and email """ 
     211        lt=getToolByName(self, 'lemill_tool') 
     212        form=self.REQUEST.form 
     213        corrects=0 
     214        wrongs=0 
     215        results=[to_unicode(self.Title()),'\n','-'*len(self.Title()),'\n\n'] 
     216        task=0 
     217        for index, chapter in enumerate(self.getBodyText()): 
     218            text=lt.stripHTML(to_unicode(chapter['text'])) 
     219            cs=ws=0 
     220            s='' 
     221            if chapter['type']=='fill_in_the_blanks': 
     222                s, cs, ws=self.checkFillInTheBlankAnswersFromForm(form, text,index) 
     223            elif chapter['type']=='open_ended': 
     224                s=self.checkOpenEndedFromForm(form, text, index) 
     225            elif chapter['type']=='poll': 
     226                s=self.checkPollFromForm(form, text, chapter['choices'], index) 
     227            elif chapter['type']=='choices': 
     228                s, cs, ws=self.checkChoiceFromForm(form, text, chapter['choices'], index) 
     229            elif chapter['type']=='multiple_choices': 
     230                s, cs, ws=self.checkMultipleChoiceFromForm(form, text, chapter['choices'], index) 
     231            if s: 
     232                task+=1  
     233                corrects+=cs 
     234                wrongs+=ws 
     235                results.append('%s. ' % task) 
     236                results.append(s) 
     237        if corrects or wrongs: 
     238            results.append('-----------------------------------------\n') 
     239            results.append('                %s / %s   = %2d%%\n' % (corrects, corrects+wrongs, ((corrects*1.0)/(corrects+wrongs))*100)) 
     240        print u''.join(results) 
     241        return u''.join(results) 
     242 
     243    def sendAnswers(self, REQUEST): 
     244        """ Send e-mail to a teacher """ 
     245        mhost=self.MailHost 
     246        body=[] 
     247        students_name = to_unicode(REQUEST.get('your_name','')) 
     248        students_email = to_unicode(REQUEST.get('students_email','')) 
     249        teachers_email = to_unicode(REQUEST.get('teachers_email','')) 
     250        captcha_c=REQUEST.get('recaptcha_challenge_field','') 
     251        captcha_r=REQUEST.get('recaptcha_response_field','') 
     252        captcha=self.lemill_tool.validateCaptcha(captcha_c, captcha_r)         
     253        if not students_email or (teachers_email and not students_name): 
     254            msg = self.translate("text_mail_feedback_no_information", "You have not provided enough information to send an e-mail.",domain='lemill') 
     255            return (msg, 'warn') 
     256        if not captcha: 
     257            msg = self.translate("Invalid answer for humanity test.",domain='lemill') 
     258            return (msg, 'warn') 
     259        # Build message body 
     260        if teachers_email: 
     261            body.append(self.translate(u"""Hello, 
     262%(name)s has completed the LeMill exercise '%(exercise_title)s' at %(exercise_url)s. 
     263""",domain='lemill') % {'name':students_name, 'exercise_title':self.Title(), 'exercise_url':self.absolute_url()})  
    443264        else: 
    444             choice_array.append((answer_number, correct_answer[0], 'incorrect_mc')) 
    445             percentage = 0 
    446              
    447         for a in range(1,len(answers_total)): 
    448             # Need a number to get the order right 
    449             answer_number = REQUEST.get('exercise_%s_hiddencheck_%s' % (exercise_nr,a),'') 
    450             if answer_value == a: 
    451                 choice_array.append((answer_number, answers_total[a], 'incorrect_fitb')) 
    452                 my_answer = answers_total[a] 
    453             else: 
    454                 choice_array.append((answer_number, answers_total[a], '')) 
    455                  
    456         # Structuring the array, appending elements to the text placeholder 
    457         choice_array.sort(cmp=lambda t1,t2: cmp(t1[0],t2[0])) 
    458          
    459         question = question.replace("<br/>", "\n") 
    460         mbody = self.translate(u"""Answer to question: 
    461 %(question)s 
    462 was: 
    463 %(my_answers)s 
    464 The correct answer is: 
    465 %(correct_answers)s 
    466 Got %(percentage)s%% correct!\n 
    467 """,domain='lemill') % {'question':to_unicode(question), 'my_answers':to_unicode(my_answer), 'correct_answers':to_unicode(', '.join(correct_answer)), 'percentage':percentage} 
    468          
    469         return [choice_array, percentage, mbody]    
    470      
    471     def multiple_choices_feedback(self, REQUEST, exercise_nr, chapter_text): 
    472         """ this method will return multiple_choices question [percentage, question feedback for web, feedback for mail] """ 
    473         i = exercise_nr 
    474         mchoice_array = [] 
    475         answered_right = 0 
    476         answered_wrong = 0 
    477         question = chapter_text[0] 
    478         correct_answers = chapter_text[1] 
    479         answers_total = correct_answers + chapter_text[2] 
    480         my_answers = [] 
    481         for a in range(len(answers_total)): 
    482             answer_value = REQUEST.get('exercise_%s_checkbox_%s' % (i,a),'') 
    483             # Need a number to get the order right 
    484             answer_number = REQUEST.get('exercise_%s_hiddencheck_%s' % (i,a),'') 
    485             if answer_value: 
    486                 if answers_total[a] in correct_answers: 
    487                     mchoice_array.append((answer_number, answers_total[a], 'checked_correct')) 
    488                     answered_right+=1 
    489                 else: 
    490                     mchoice_array.append((answer_number, answers_total[a], 'checked_incorrect')) 
    491                     answered_wrong+=1 
    492                 my_answers.append(answers_total[a]) 
    493             if not answer_value: 
    494                 if answers_total[a] in correct_answers: 
    495                     mchoice_array.append((answer_number, answers_total[a], 'incorrect')) 
    496                     answered_wrong+=1 
    497                 else: 
    498                     mchoice_array.append((answer_number, answers_total[a], 'correct')) 
    499                     answered_right+=1 
    500         # Structuring the array, appending elements to the text placeholder 
    501         mchoice_array.sort(cmp=lambda t1,t2: cmp(t1[0],t2[0])) 
    502         percentage = int((float(answered_right)/(answered_wrong+answered_right))*100) 
    503          
    504         question = question.replace("<br/>", "\n") 
    505         mbody = self.translate(u"""Answer to question: 
    506 %(question)s 
    507 was: 
    508 %(my_answers)s 
    509 The correct answer is: 
    510 %(correct_answers)s 
    511 Got %(percentage)s%% correct!\n 
    512 """,domain='lemill') % {'question':to_unicode(question), 'my_answers':to_unicode(', '.join(my_answers)), 'correct_answers':to_unicode(', '.join(correct_answers)), 'percentage':percentage} 
    513         return [mchoice_array, percentage, mbody]  
    514  
    515     def round_number(self, nr): 
    516         """ round given number """ 
    517         rounded = "%.2f" %nr 
    518         # for example if number 60.00 return 60 
    519         rounded = rounded.rstrip("0") 
    520         if (rounded[-1] == "."): 
    521             rounded = rounded.replace(".", "") 
    522         return rounded 
    523      
    524     def sendAnswers(self, REQUEST, mailbody, summary): 
    525         """ Send e-mail to a teacher """ 
    526         mhost = self.MailHost 
    527         mbody = u'' 
    528         students_name = REQUEST.get('your_name','') 
    529         students_email = REQUEST.get('students_email','') 
    530         teachers_email = REQUEST.get('teachers_email','') 
    531  
    532         if not students_email or (teachers_email and not students_name): 
    533             msg = self.translate(u"text_mail_feedback_no_information", u"You have not provided enough information to send an e-mail.",domain='lemill') 
    534             # Messages are translated here, every message should also have a type along with it 
     265            body.append(self.translate(u"""Hello %(name)s, 
     266You have completed the LeMill exercise '%(exercise_title)s' at %(exercise_url)s. 
     267""",domain='lemill') % {'name':students_name, 'exercise_title':self.Title(), 'exercise_url':self.absolute_url()})  
     268        body.append(self.translate(u"text_mail_feedback_message_body_explanation", u"If you are surprised by getting this e-mail, you can simply ignore and delete it as someone may have accidentally inserted your address as a recipient.",domain='lemill')) 
     269        body.append('\n\n') 
     270        body.append(self.checkExercise()) 
     271        body.append('\n\n') 
     272        body.append(self.translate("text_mail_feedback_message_body_regards", "Best regards, LeMill", domain='lemill'))                      
     273        body=u''.join(body) 
     274        # Other email fields 
     275        if students_name: 
     276            msubject=self.translate("LeMill exercise '%s' by %s", domain='lemill') % (self.Title(), students_name) 
     277        else: 
     278            msubject=self.translate("LeMill exercise '%s' feedback", domain='lemill') % self.Title() 
     279        if teachers_email: 
     280            mto=teachers_email 
     281            mcc=students_email 
     282        else: 
     283            mto=teachers_email 
     284            mcc='' 
     285        # Send 
     286        try: 
     287            mhost.secureSend(mbody, mto=mto, mfrom="LeMill <no-reply@lemill.net>", subject=msubject, mcc=mcc) 
     288            msg = self.translate("text_mail_feedback_message_sent", "The e-mail has been sent.",domain='lemill') 
     289            return (msg, 'mess') 
     290        except: 
     291            msg = self.translate("The e-mail could not be sent.",domain='lemill') 
    535292            return (msg, 'warn') 
    536  
    537         if not self.lemill_tool.validateCaptcha(REQUEST.get('recaptcha_challenge_field'),REQUEST.get('recaptcha_response_field')): 
    538             msg = self.translate(u"Invalid answer for humanity test.",domain='lemill') 
    539             return (msg, 'warn') 
    540          
    541         exercise_body = self.getBodyText() 
    542  
    543         # Now we should compose the message body         
    544         if teachers_email: 
    545             mbody += self.translate(u"""Hello, 
    546  
    547 %(name)s has completed the LeMill exercise '%(exercise_title)s' at %(exercise_url)s. 
    548 """,domain='lemill') % {'name':to_unicode(students_name), 'exercise_title':to_unicode(self.Title()), 'exercise_url':to_unicode(self.absolute_url())}  
    549         else: 
    550             mbody += self.translate(u"""Hello %(name)s, 
    551  
    552 You have completed the LeMill exercise '%(exercise_title)s' at %(exercise_url)s. 
    553 """,domain='lemill') % {'name':to_unicode(students_name), 'exercise_title':to_unicode(self.Title()), 'exercise_url':to_unicode(self.absolute_url())} 
    554  
    555         mbody += self.translate(u"text_mail_feedback_message_body_explanation", u"If you are surprised by getting this e-mail, you can simply ignore and delete it as someone may have accidentally inserted your address as a recipient.",domain='lemill') + '\n\n' 
    556          
    557         # Add questions feedback composed before 
    558         mbody += mailbody 
    559                 
    560         mbody += summary        
    561                      
    562         mbody += '\n\n'+ self.translate(u"text_mail_feedback_message_body_regards", u"Best regards, LeMill",domain='lemill') 
    563         mbody = mbody.encode('utf-8') 
    564  
    565         if teachers_email: 
    566             message_from = "%s <%s>" % (students_name, students_email) 
    567             message_subject = self.translate(u"LeMill exercise '%(exercise_title)s' by %(student_name)s", domain='lemill') % {'exercise_title':to_unicode(self.Title()), 'student_name':to_unicode(students_name)} 
    568             try: 
    569                 mhost.secureSend(mbody, mto=teachers_email, mfrom=message_from, subject=message_subject, mcc=students_email, charset='utf-8') 
    570                 msg = self.translate(u"text_mail_feedback_message_sent_both", u"The e-mail has been sent to you and a teacher.",domain='lemill') 
    571                 return (msg, 'mess') 
    572             except: 
    573                 msg = self.translate(u"The e-mail could not be sent.",domain='lemill') 
    574                 return (msg, 'warn') 
    575         else: 
    576             message_from = "%s <%s>" % ('LeMill', students_email) 
    577             message_subject = self.translate(u"LeMill exercise '%(exercise_title)s' feedback", domain='lemill') % {'exercise_title':to_unicode(self.Title())} 
    578             try: 
    579                 mhost.secureSend(mbody, mto=students_email, mfrom=message_from, subject=message_subject, charset='utf-8') 
    580                 msg = self.translate(u"text_mail_feedback_message_sent_one", u"The e-mail has been sent to you.",domain='lemill') 
    581                 return (msg, 'mess') 
    582             except: 
    583                 msg = self.translate(u"The e-mail could not be sent.",domain='lemill') 
    584                 return (msg, 'warn') 
    585  
    586293 
    587294    def prepareForPDF(self): 
     
    599306        return text 
    600307 
    601  
    602308registerType(ExerciseMaterial, PROJECTNAME) 
  • trunk/FieldsWidgets.py

    r3014 r3045  
    3333from types import ListType, TupleType, StringType, UnicodeType, InstanceType 
    3434from messagefactory_ import i18nme as _ 
    35  
    36  
    37 import re 
    38 import os, shutil 
    39  
    40 STRING_TYPES = [StringType, UnicodeType] 
    41  
    42  
    43  
     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 
    4440 
    4541#StringWidget and TextAreaWidget need to update their macro, otherwise they try to use archetypes/skins/widgets -path only 
     
    9389    description='Textarea Widget' 
    9490) 
    95  
    96  
    9791 
    9892class TagsWidget(StringWidget): 
     
    267261        'helper_js' : ('AC_RunActiveContent.js',), 
    268262        'show_added' : True, 
    269         'chapter_count' : 1, 
    270263        'no_label' : True 
    271     }) 
    272   
     264    })  
    273265    security = ClassSecurityInfo() 
    274266 
     
    276268    def process_form(self, instance, field, form, empty_marker=None, 
    277269            emptyReturnsMarker=False): 
    278         """ goes through the form and tries to find all texts and references to pieces. """ 
    279         value = [] 
    280         fieldname=field.getName() 
    281         count= int(form.get('%s_count' % fieldname, 1)) 
    282         for i in range(0,count): 
    283             chapter_type= form.get('%s_type_%s' % (fieldname, i)) 
    284             text= form.get('%s_%s' % (fieldname, i)) 
    285             if chapter_type=='media_piece': 
    286                 file=form.get('%s_file_%s' % (fieldname, i)) 
    287                 if file and hasattr(file, 'filename'): 
    288                     value.append((file,'media_piece')) 
    289                 else: 
    290                     value.append((text, 'media_piece')) 
    291             if chapter_type=='text_block': 
    292                 value.append((text, 'text_block')) 
    293             if chapter_type=='embed_block': 
    294                 value.append((text, 'embed_block')) 
    295         if value == []: 
    296             return empty_marker 
    297         return value, {} 
     270        """ goes through the form, reorganizes chapters if necessary and delivers results to field writer (as dict). """ 
     271        changes = {} 
     272        changes['edited']=int(form.get('chapter_last_edited', 0)) 
     273        count=int(form.get('chapter_count',0)) 
     274        changes['count']=count 
     275        print 'processing widget ChapterWidget with form: ',form 
     276        new_order=[] 
     277        reorder=False 
     278        deleted=[] 
     279        # Orderings and deletions 
     280        for index in range(0,count): 
     281            order=int(form.get('chapter_order_%s' % index, 0)) 
     282            new_order.append(order) 
     283            if order!=index: 
     284                reorder=True 
     285            this_deleted = int(form.get('chapter_deleted_%s' % index, 0)) 
     286            if this_deleted: 
     287                deleted.append(index) 
     288        if reorder: 
     289            changes['new_order']=new_order 
     290        if deleted: 
     291            changes['deleted']=deleted 
     292        # Changes 
     293        chapter_type=form.get('chapter_type') 
     294        changes['chapter_type']=chapter_type 
     295        if chapter_type=='media_piece': 
     296            file=form.get('file', None) 
     297            if file and hasattr(file, 'filename'): 
     298                changes['file']=file 
     299            else: 
     300                changes['uid']=form.get('piece_uid') 
     301        elif chapter_type=='text_block': 
     302            changes['text']=form.get('chapter_textarea') 
     303        elif chapter_type=='embed_block': 
     304            changes['text']='' 
     305            changes['embed']=form.get('embed_textarea') 
     306        # Save & edit buttons 
     307        if form.get('saveChapter', None): 
     308            form.update({'chapter_edited':-1, 'chapter_anchor':changes['edited'], 'stay':1})         
     309        elif form.get('editChapter', None): 
     310            form.update({'chapter_anchor':form['chapter_edited'], 'stay':1})         
     311        # Add-buttons  
     312        elif form.get('add_text_block', None): 
     313            changes['new_chapters']=[{'text':'','type':'text_block'}] 
     314            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1}) 
     315        elif form.get('add_media_piece', None): 
     316            changes['new_chapters']=[{'text':'','type':'media_piece','uid':''}] 
     317            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1}) 
     318        elif form.get('add_embed_block', None): 
     319            changes['new_chapters']=[{'text':'','type':'embed_block', 'embed':''}] 
     320            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1}) 
     321        return changes, {} 
    298322 
    299323registerWidget(ChapterWidget, 
     
    327351        'chapter_count' : 3, 
    328352    }) 
    329  
    330  
    331353    security = ClassSecurityInfo() 
    332354 
     
    334356    def process_form(self, instance, field, form, empty_marker=None, 
    335357            emptyReturnsMarker=False): 
    336         """ 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. """ 
    337         fieldname=field.getName() 
    338         value = [] 
    339         count= int(form.get('%s_count' % fieldname, 1)) 
    340         for i in range(0,count): 
    341             chapter_type= form.get('%s_type_%s' % (fieldname, i)) 
    342             text= form.get('%s_%s' % (fieldname, i)) 
    343             if chapter_type in ['image_piece','audio_piece']: 
    344                 file=form.get('%s_file_%s' % (fieldname, i))     
    345                 if file and hasattr(file, 'filename'): 
    346                     value.append((file, chapter_type)) 
    347                 else: 
    348                     value.append((text, chapter_type)) 
    349  
    350             if chapter_type in ['pilot_keywords','pilot_questions']: 
    351                 # if stored in hidden field 
    352                 if text: 
    353                     if text.startswith('#keywords#!#'): 
    354                         keywordlist=text.split('#!#')[1:] 
    355                     value.append((keywordlist, chapter_type)) 
    356                 else: 
    357                     # if in active form field 
    358                     kw_counter=0 
    359                     kw='' 
    360                     target_len=3 
    361                     keywordlist=[] 
    362                     while(kw!=None and kw_counter<8): 
    363                         kw=form.get('%s_keyword_%s_%s' % (fieldname,i,kw_counter), None) 
    364                         if kw!=None:                      
    365                             keywordlist.append(kw) 
    366                         kw_counter=kw_counter+1 
    367                     if i==count-1: 
    368                         target_len=7 
    369                     while len(keywordlist)<target_len: 
    370                         keywordlist.append('') 
    371                     value.append((keywordlist, chapter_type)) 
    372              
    373         if value == []: 
    374             return empty_marker 
    375         return value, {} 
    376  
    377     def merge_keywords(self, value): 
    378         """ allow lists to be stored in forms as hidden fields """ 
    379         if type(value)==list or type(value)==tuple: 
    380             value='#!#'.join(value) 
    381             return ''.join(('#keywords#!#',value)) 
    382           
    383  
     358        """ goes through the form and sends changes forward to field writer """ 
     359        changes, empty= ChapterWidget.process_form(self, instance, field, form) 
     360        chapter_type=changes['chapter_type'] 
     361        if chapter_type=='pilot_section': 
     362            audio_file=form.get('audio_file', None) 
     363            if audio_file and hasattr(audio_file, 'filename'): 
     364                changes['audio_file']=audio_file 
     365            else: 
     366                changes['audio_uid']=form.get('audio_uid') 
     367            image_file=form.get('image_file', None) 
     368            if image_file and hasattr(image_file, 'filename'): 
     369                changes['image_file']=image_file 
     370            else: 
     371                changes['image_uid']=form.get('image_uid') 
     372            kws=[] 
     373            for i in range(0, 3): 
     374                kw=form.get('keywords_%s' % i, '') 
     375                if kw: 
     376                    kws.append(kw) 
     377            if kws: 
     378                changes['keywords']=kws 
     379            qs=[] 
     380            for i in range(0, 7): 
     381                q=form.get('question_%s' % i, '') 
     382                if q: 
     383                    qs.append(q) 
     384            if qs: 
     385                changes['questions']=qs 
     386        # Buttons  
     387        if form.get('add_pilot_section', None): 
     388            changes['new_chapters']={'text':'','type':'pilot_section','audio_uid':'', 'image_uid':'', 'keywords':[], 'questions':[]}, 
     389            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1}) 
     390        return changes, {} 
    384391 
    385392registerWidget(PilotWidget, 
     
    398405        'chapter_count' : 1, 
    399406    }) 
    400  
    401  
    402407    security = ClassSecurityInfo() 
    403408 
     
    406411            emptyReturnsMarker=False): 
    407412        """ 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. """ 
    408         fieldname=field.getName() 
    409         value = [] 
    410         count= int(form.get('%s_count' % fieldname, 1)) 
    411         for i in range(0,count): 
    412             chapter_type= form.get('%s_type_%s' % (fieldname, i)) 
    413             if chapter_type=='guidelines': 
    414                 text= form.get('%s_%s' % (fieldname, i),'') 
    415                 value.append((text,'guidelines')) 
    416             elif chapter_type=='text_block': 
    417                 text= form.get('%s_%s' % (fieldname, i)) 
    418                 value.append((text,'text_block')) 
    419             elif chapter_type=='media_piece': 
    420                 file=form.get('%s_file_%s' % (fieldname, i))     
    421                 text= form.get('%s_%s' % (fieldname, i))                 
    422                 if file and hasattr(file, 'filename'): 
    423                     value.append((file,'media_piece')) 
    424                 else: 
    425                     value.append((text, 'media_piece')) 
    426             elif chapter_type=='embed_block': 
    427                 text = form.get('%s_%s' % (fieldname, i)) 
    428                 value.append((text, 'embed_block')) 
    429             elif chapter_type == 'choice': 
    430                 question=form.get('%s_%s_question' % (fieldname, i)) 
    431                 correct = form.get('%s_%s_correct' % (fieldname, i)).strip() 
    432                 incorrect=form.get('%s_%s_incorrect' % (fieldname, i),'').strip() 
    433                 incorrect=[x.strip() for x in incorrect.split('\n') if x and not x.isspace()] 
    434                 value.append(([question, correct, incorrect], 'choice')) 
    435             elif chapter_type=='multiple_choices': 
    436                 question=form.get('%s_%s_question' % (fieldname, i)) 
    437                 correct=form.get('%s_%s_correct' % (fieldname, i),'').strip() 
    438                 correct=[x.strip() for x in correct.split('\n') if x and not x.isspace()] 
    439                 incorrect=form.get('%s_%s_incorrect' % (fieldname, i),'').strip() 
    440                 incorrect=[x.strip() for x in incorrect.split('\n') if x and not x.isspace()] 
    441                 value.append(([question, correct, incorrect],'multiple_choices')) 
    442             elif chapter_type=='fill_in_the_blanks': 
    443                 text=form.get('%s_%s_blanks' % (fieldname, i)) 
    444                 value.append((text,'fill_in_the_blanks')) 
    445             elif chapter_type=='open_ended': 
    446                 text=form.get('%s_%s_open' % (fieldname, i)) 
    447                 value.append((text,'open_ended')) 
    448  
    449         if value == []: 
    450             return empty_marker 
    451         return value, {} 
    452  
    453     def merge_keywords(self, value): 
    454         """ allow lists to be stored in forms as hidden fields """ 
    455         if type(value)==list or type(value)==tuple: 
    456             value='#!#'.join(value) 
    457             return ''.join(('#keywords#!#',value)) 
    458  
     413        changes, empty= ChapterWidget.process_form(self, instance, field, form) 
     414        chapter_type=changes['chapter_type']                 
     415        if chapter_type=='guidelines': 
     416            changes['text']=form.get('chapter') 
     417        elif chapter_type in ['choice','multiple_choices','poll']: 
     418            question=form.get('choice_question') 
     419            answers=[] 
     420            answer_count=form.get('choice_answer_count') 
     421            for i in range(0, answer_count): 
     422                answer={} 
     423                answer['text']=form.get('choice_answer_%s' % i, '') 
     424                answer['is_correct']= chapter_type=='poll' or int(form.get('answer_%s_correct' % i, '')) 
     425                answer['order']=int(form.get('choice_order_%s' % i, 0))                 
     426                answers.append(answer) 
     427                # put sorting here!!! 
     428            changes['question']=question 
     429            changes['answers']=answers 
     430        elif chapter_type=='fill_in_the_blanks': 
     431            changes['text']=form.get('fill_blanks','') 
     432        elif chapter_type=='open_ended': 
     433            changes['text']=form.get('open_ended','') 
     434        # Buttons  
     435        if form.get('add_exercise', None): 
     436            changes['new_chapters']=[{'text':'','type':'exercise'}] 
     437            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1}) 
     438        elif form.get('import_potatoes', None): 
     439            file=form.get('import_file', None) 
     440            if file and hasattr(file, 'filename'): 
     441                if file.filename.endswith('jcl'): 
     442                    text=self.importJClozeQuestion(file) 
     443                    changes['chapter_type']='fill_in_the_blanks' 
     444                    changes['text']=text 
     445                    form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1}) 
     446                elif file.filename.endswith('jcz'): 
     447                    new_chapters=self.importJQuizQuestions(file) 
     448                    if new_chapters: 
     449                        this_chapter= new_chapters.pop(0) 
     450                        changes['chapter_type']=this_chapter['type'] 
     451                        changes['answers']=this_chapter['answers'] 
     452                        changes['question']=this_chapter['text'] 
     453                        if new_chapters: 
     454                            changes['new_chapters']=new_chapters 
     455                            form.update({'chapter_edited':count, 'chapter_anchor':count, 'stay':1}) 
     456        return changes, {} 
     457    
     458    def importJQuizQuestions(self, file): 
     459        """ import hot potatoes questions made with jquiz """         
     460        dom = parse_xml(file.read()) 
     461        questions = dom.getElementsByTagName("question-record") 
     462        result=[] 
     463        for ques in questions: 
     464            questype = int(ques.getElementsByTagName("question-type")[0].childNodes[0].data) 
     465            if not (questype==1 or questype==4): 
     466                return             
     467            question = ques.getElementsByTagName("question")[0].childNodes[0].data.encode('utf-8') 
     468            # if jquiz question type is multiple choice(1) or multiple correct(4) 
     469            answers = ques.getElementsByTagName("answer") 
     470            # calculate how many correct answers (is it choice or choice_multiple type) 
     471            corrects=0 
     472            made_answers=[] 
     473            for answer in answers: 
     474                correct = int(answer.getElementsByTagName("correct")[0].childNodes[0].data) # 1-correct 0-false 
     475                if correct: 
     476                    corrects+=1 
     477                answertext = answer.getElementsByTagName("text")[0].childNodes[0].data.encode('utf-8') 
     478                if answertext: 
     479                    made_answers.append({'text':answertext, 'is_correct':correct}) 
     480            if corrects==len(result): 
     481                qtype='poll' 
     482            elif corrects==1: 
     483                qtype='choice' 
     484            else: 
     485                qtype='multiple_choices' 
     486            result.append({'type':qtype,'text':question,'answers':made_answers})         
     487        dom.unlink() 
     488        return result 
     489     
     490    def importJClozeQuestion(self, file): 
     491        """ import hot potatoes question made with JCloze """         
     492        dom = parse_xml(file.read()) 
     493        gap_fill = dom.getElementsByTagName("gap-fill")[0] 
     494        result=[] 
     495        for node in gap_fill.childNodes: 
     496            if node.nodeType==node.TEXT_NODE: 
     497                result.append(node.data) 
     498            else: 
     499                for answer in node.getElementsByTagName("answer"): 
     500                    text_tag=answer.getElementsByTagName("text")[0] 
     501                    text=text_tag.childNodes[0].data 
     502                    result.append('{%s}' % text) 
     503        result=u''.join(result) 
     504        #result=unescape(u''.join(result), {'&#x003C;':'<','&#x003E;':'>'}) 
     505        dom.unlink() 
     506        return result 
    459507 
    460508registerWidget(ExerciseWidget, 
     
    463511    used_for=('Products.LeMill.ChapterField',) 
    464512) 
    465  
    466  
    467513 
    468514class AudioWidget(FileWidget): 
     
    561607    description='Embed Widget, not modifiable by common means, only displays embed code' 
    562608) 
    563  
    564  
    565609 
    566610 
     
    700744        'referenceClass' : Reference, 
    701745        'referenceReferences' : False, 
    702         'deleteEmptyChapters' : True 
    703     }) 
     746        'index_method':'indexValue'}) 
    704747     
    705748    security = ClassSecurityInfo() 
    706749 
    707     ######################################## 
    708     # Validation methods for chapters 
    709  
    710     def isString(self, instance, chapter): 
    711         return type(chapter) in STRING_TYPES  
    712  
    713     def isList(self, instance, chapter): 
    714         return type(chapter) == list 
    715  
    716     def isPiece(self, instance, chapter, return_piece=False): 
    717         if type(chapter) not in STRING_TYPES: 
    718             return False 
    719         if not (chapter.isalnum() and len(chapter)==32 and len(chapter)==len(chapter.strip())): 
    720             if return_piece: 
    721                 return (False, None)  
    722             else: 
    723                 return False             
    724         obj=self.getObjectByUID(instance, chapter) 
    725         if return_piece: 
    726             ok = obj and hasattr(obj, 'isPiece') and obj.isPiece()             
    727             return (ok, obj) 
    728         else: 
    729             return obj and hasattr(obj,'isPiece') and obj.isPiece()                
    730  
    731     def isImagePiece(self, instance, chapter): 
    732         good_piece, obj = self.isPiece(instance, chapter, return_piece=True) 
    733         if good_piece and obj: 
    734             return obj.isImage() 
    735         return False 
    736  
    737     def isAudioPiece(self, instance, chapter): 
    738         good_piece, obj = self.isPiece(instance, chapter, return_piece=True) 
    739         if good_piece and obj: 
    740             return obj.isAudio() 
    741         return False 
    742           
    743  
    744  
    745     chapter_validation={ 
    746         'text_block':isString, 
    747         'media_piece':isPiece, 
    748         'image_piece':isImagePiece, 
    749         'audio_piece':isAudioPiece, 
    750         'caption':isString, 
    751         'embed_block':isString, 
    752         'fill_in_the_blanks':isString, 
    753         'choice':isList, 
    754         'multiple_choices':isList, 
    755         'guidelines':isString, 
    756         'open_ended':isString, 
    757         'pilot_keywords':isList, 
    758         'pilot_questions':isList 
    759     } 
    760  
    761     piece_types=['media_piece','image_piece','audio_piece'] 
    762  
    763  
    764     def getRaw(self, instance, **kwargs): 
     750    def getRaw(self, instance, version=None, **kwargs): 
    765751        """ Gets raw version for editing, as list """ 
    766         try: 
    767             version = instance.REQUEST.get('version',None) 
    768         except AttributeError: 
    769             version=None 
     752        print 'ChapterField.getRaw called with args ', kwargs 
    770753        if version: 
    771754            value = instance.getFieldHistory(self.getName(), version) or [] 
     755        elif '_initializing_' in kwargs: 
     756            print '**** initializing, no need to SET'  
     757            value = self.getDefault(instance) or []     
    772758        else: 
    773             value = ObjectField.get(self, instance,**kwargs) 
    774         if type(value) != list: 
    775             value = [value,] 
     759            value = ObjectField.get(self, instance, **kwargs) or [] 
     760        if not isinstance(value, list): 
     761            value = [] 
    776762        return value 
    777763 
    778  
    779764    def get(self, instance, **kwargs): 
    780         """ Gets cleaned version for display, as list """ 
    781         try: 
    782             version = instance.REQUEST.get('version',None) 
    783         except AttributeError: 
    784             version = None 
    785         value = self.getRaw(instance, **kwargs) 
    786         cleaned_text = getattr(instance, 'cleaned_%s' % self.getName(), None) 
    787         if cleaned_text and not version and type(cleaned_text) == list: 
    788             return cleaned_text         
     765        """ Get a cleaned version for display """ 
     766        print 'ChapterField.get called with args', kwargs 
     767        REQUEST=getattr(instance,'REQUEST',None) 
     768        if REQUEST: 
     769            version=REQUEST.get('version',None) 
     770            translation=REQUEST.get('translation',None) 
     771        else: 
     772            version=None 
     773            translation=None 
     774        if version or translation: # don't cache cleaned version 
     775            value = self.getRaw(instance, version=version, **kwargs) 
     776            if isinstance(value[0], tuple): # update chapters if necessary 
     777                value = self.updateChapterList(value) 
     778            value=self.cleanChapters(instance, value) 
     779            if translation: 
     780                value=self.mergeTranslationSource(instance,value) 
     781            return value 
     782        cleaned_value = getattr(instance, 'cleaned_%s' % self.getName(), None)  
     783        if cleaned_value: 
     784            if isinstance(cleaned_value[0], dict): 
     785                print 'no need to convert, giving cleaned as it is' 
     786                return cleaned_value 
     787            else: # Retroactively fix cleaned chapters 
     788                cleaned_value=self.updateChapterList(cleaned_value) 
     789                cleaned_value=self.updateCleanedChapters(instance, cleaned_value) 
     790                print 'converted and updated cleaned chapter list' 
     791                print cleaned_value 
     792                #print cleaned_value 
     793                return cleaned_value 
     794        print 'ChapterField is calling getRaw...' 
     795        value=self.getRaw(instance, **kwargs) 
     796        print '...ChapterField has called getRaw.' 
     797        if isinstance(value[0], tuple):  
     798            print 'raw needs to be converted...' 
     799            value = self.updateChapterList(value) 
     800        cleaned_value=self.updateCleanedChapters(instance, value) 
     801        print 'updated cleaned chapter'  
     802        #print cleaned_value 
     803        return cleaned_value 
     804 
     805    def textOnly(self, instance, **kwargs): 
     806        print '***** calling indexer for ChapterField' 
     807        listofdicts= getattr(instance, 'cleaned_%s' % self.getName(), None) 
     808        return '\n'.join([x['text'] for x in listofdicts])  
     809 
     810    def mergeTranslationSource(self, instance, chapter_list): 
     811        """ Adds original source texts to translated chapters, under key 'original' """ 
     812        return chapter_list 
     813        #trans_source=instance.get 
     814        #for chapter in chapter_list: 
     815         
     816 
     817    def cleanChapters(self, instance, chapters): 
    789818        ltool = getToolByName(instance, 'lemill_tool') 
    790         cleaned_text=[] 
    791         for (chapter, chapter_type) in value: 
    792             if chapter_type in ['pilot_keywords','pilot_questions','multiple_choices','choice']: 
    793                 cleaned_text.append((ltool.parse_text(chapter, start_with_p=False), chapter_type)) 
    794             elif chapter_type in ['image_piece','audio_piece','media_piece']: 
    795                 cleaned_text.append((chapter, chapter_type)) 
    796             elif chapter_type == 'embed_block': 
    797                 cleaned_text.append((ltool.parse_embed_content(chapter), chapter_type)) 
     819        cleaned_chapters=[] 
     820        parsed_fields=['text','keywords','questions','answers'] 
     821        for chapter in chapters: 
     822            new_chapter={} 
     823            new_chapter=copy(chapter) 
     824            for key, value in chapter.items(): 
     825                if key in parsed_fields: 
     826                    new_chapter[key]=ltool.parse_text(value, start_with_p=False) 
     827            cleaned_chapters.append(new_chapter)  
     828        return cleaned_chapters 
     829 
     830 
     831    def updateCleanedChapters(self, instance, chapters): 
     832        cleaned_chapters=self.cleanChapters(instance, chapters) 
     833        setattr(instance, 'cleaned_%s' % self.getName(), cleaned_chapters) 
     834        return cleaned_chapters 
     835 
     836    def confirmUpdated(self, chapters): 
     837        """ Edit macros use raw data, which may be in old tuple-format. Update if necessary. """         
     838        print 'confirmUpdated called with ', chapters  
     839        if chapters and type(chapters[0])==tuple: 
     840            chapters= self.updateChapterList(chapters) 
     841        return chapters 
     842 
     843         
     844 
     845    def _updatePilot(self, old_values): 
     846        """ in new dictionary based chapters it makes sense to use one chapter per slide, instead of three chapters  
     847        this method converts old chapter lists into new format """         
     848        new_scene=True 
     849        updated_values=[] 
     850        for cvalue, ctype in old_values: 
     851            if new_scene: 
     852                scene={'text':'', 'image_uid':'', 'audio_uid':'', 'keywords':[], 'questions':[]} 
     853                new_scene=False 
     854            if ctype=='audio_piece': 
     855                scene['audio_uid']=cvalue 
     856            elif ctype=='image_piece': 
     857                scene['image_uid']=cvalue 
     858            elif ctype=='pilot_keywords': 
     859                scene['keywords']=cvalue 
     860                updated_values.append(scene) 
     861                new_scene=True 
     862            elif ctype=='pilot_questions': 
     863                scene['questions']=cvalue 
     864                updated_values.append(scene) 
     865                new_scene=True 
     866        return updated_values             
     867 
     868 
     869    def updateChapterList(self, old_values): 
     870        """ Backward compatibility:  
     871            Converts a chapterlist to use dicts instead of tuples. Returns updated list """ 
     872        updated_values=[] 
     873        if 'pilot_keywords' in [ct for cv, ct in old_values]: # Pilots need little more work 
     874            return self._updatePilot(old_values) 
     875        for cvalue, ctype in old_values:                     
     876            if ctype in ['multiple_choices','poll','choices']: 
     877                q,correct,wrong=cvalue 
     878                if ctype=='multiple_choices': 
     879                    choices=[(c, 1) for c in correct] 
     880                    choices+=[(w, 0) for w in wrong] 
     881                    shuffle(choices) 
     882                elif ctype=='choices': 
     883                    choices=[(correct,1)] 
     884                    choices+=[(w, 0) for w in wrong] 
     885                    shuffle(choices) 
     886                elif ctype=='poll': 
     887                    choices=[(c,1) for c in correct+wrong] 
     888                new_chapter={'text':q, 'type':ctype, 'choices':choices} 
     889            elif ctype in ['image_piece','audio_piece','media_piece']: 
     890                new_chapter={'text':'', 'type':ctype, 'uid':cvalue} 
     891            elif ctype=='embed_block': 
     892                new_chapter={'text':'', 'type':'embed_block', 'embed':cvalue} 
    798893            else: 
    799                 cleaned_text.append((ltool.parse_text(chapter), chapter_type))      
    800         if not version: 
    801             setattr(instance, 'cleaned_%s' % self.getName(), cleaned_text) 
    802         return cleaned_text 
    803  
    804  
    805     def update_cleaned_text(self, instance, value): 
    806         ltool = getToolByName(instance, 'lemill_tool') 
    807         cleaned_text=[] 
    808         for (chapter, chapter_type) in value: 
    809             if chapter_type in ['pilot_keywords','pilot_questions','multiple_choices','choice']: 
    810                 cleaned_text.append((ltool.parse_text(chapter, start_with_p=False), chapter_type)) 
    811             elif chapter_type in ['image_piece','audio_piece','media_piece']: 
    812                 cleaned_text.append((chapter, chapter_type)) 
    813             elif chapter_type == 'embed_block': 
    814                 cleaned_text.append((ltool.parse_embed_content(chapter), chapter_type)) 
    815             else: 
    816                 cleaned_text.append((ltool.parse_text(chapter), chapter_type))      
    817         setattr(instance, 'cleaned_%s' % self.getName(), cleaned_text) 
     894                new_chapter={'text':cvalue, 'type':ctype} 
     895            changed=True 
     896            updated_values.append(new_chapter) 
     897        return updated_values 
    818898 
    819899    def set(self, instance, value, **kwargs): 
    820         #print 'GET:%s' % value 
    821         tool = getToolByName(instance, REFERENCE_CATALOG) 
     900        print 'ChapterField.set called with value: ', value, kwargs 
    822901        lt = getToolByName(instance, 'lemill_tool') 
    823         finalvalues=[] # final version of chapters list that replaces the 'value' 
    824         uids=[] # uids that should be referenced by these chapters 
    825         targetUIDs = [x.targetUID for x in tool.getReferences(instance, self.relationship)] # uids currently referenced 
    826         piece_types=['image_piece','audio_piece','media_piece'] 
    827  
    828         if value==None: value=[('', 'text_block')] 
    829         if type(value)==str:  
    830             if self.isUid(value): 
    831                 value=[(value, 'media_piece')]         
    832             else: 
    833                 value=[(value, 'text_block')]                     
    834          
    835         # tweak keyword arguments for addReference 
    836         addRef_kw = kwargs.copy() 
    837         addRef_kw.setdefault('referenceClass', self.referenceClass) 
    838         if addRef_kw.has_key('schema'): del addRef_kw['schema'] 
    839  
    840         #assume that values are given in list 
    841         num = 0 
    842         for (chapter, chapter_type) in value: 
    843             if chapter and chapter_type in piece_types and hasattr(chapter, 'filename'): 
    844                 new_piece = lt.createPieceFromFile(chapter, instance) 
    845                 if new_piece: 
    846                     chapter = new_piece.UID()                     
    847                     # Notice about new piece               
    848                     if new_piece.isImage(): 
    849                         lt.addPortalMessage(_('Image uploaded'))               
    850                     elif new_piece.isAudio(): 
    851                         lt.addPortalMessage(_('Audio clip uploaded'))               
    852                     elif new_piece.isSwf(): 
    853                         lt.addPortalMessage(_('Flash animation uploaded'))               
    854                     elif new_piece.isMovie(): 
    855                         lt.addPortalMessage(_('Video clip uploaded'))               
    856                     elif new_piece.isFLVVideo(): 
    857                         lt.addPortalMessage(_('Flash video uploaded'))               
     902        changed=False 
     903        if kwargs.get('_initializing_', False): 
     904            old_values=self.getDefault(instance) 
     905            changed=True 
     906        else: 
     907            old_values=self.getRaw(instance, **kwargs) 
     908        print 'old values returned:', old_values 
     909        references_to_add=[] 
     910        references_to_remove=[]         
     911        if isinstance(old_values, list) and old_values: 
     912            # check if this needs conversion 
     913            if isinstance(old_values[0], tuple): 
     914                print 'updating chapter list' 
     915                old_values=self.updateChapterList(old_values) 
     916                changed=True  
     917                # conversion done.        
     918        ### The normal case: widget gives a dictionary ###     
     919        if isinstance(value, dict):   
     920            edited=value['edited'] 
     921            new_values=copy(old_values) 
     922 
     923            if edited!=-1: # one chapter has been edited 
     924                ctype=value['chapter_type'] 
     925                old_chapter=old_values[edited]             
     926                # build _one_ chapter based on input 
     927                new_chapter={'type':ctype} 
     928                if ctype in ['multiple_choice','choices','poll']: 
     929                    new_chapter['text']=value['question'] 
     930                    choices=[] 
     931                    for answer in value['answers']: 
     932                        choices.append((answer['order'], answer['text'], answer['is_correct'])) 
     933                    choices.sort() 
     934                    choices=[(y,z) for (x,y,z) in choices] 
     935                    new_chapter['choices']=choices 
     936                elif ctype=='pilot_scene': 
     937                    new_chapter['text']=u'\n'.join(value['keywords'] or value['questions']) 
     938                    if value['audio_file']: 
     939                        new_piece = lt.createPieceFromFile(value['audio_file'], instance) 
     940                        uid=new_piece.UID() 
    858941                    else: 
    859                         lt.addPortalMessage(_('Cannot display this file type')) 
    860                          
    861             validator=self.chapter_validation.get(chapter_type, self.isString) 
    862             valid= validator(self, instance, chapter) 
    863             #try to set references 
    864             if not valid and self.deleteEmptyChapters:  
    865                 print 'invalid chapter: %s' % chapter 
    866             if valid and chapter_type in piece_types: 
    867                 #print '[#]', 
    868                 uids.append(chapter) # collect list of good uids that should be there   
    869                 if chapter not in targetUIDs: # if not there, add it 
    870                     __traceback_info__ = (instance, chapter, targetUIDs) 
    871                     tool.addReference(instance, chapter, self.relationship, **addRef_kw) 
    872             if valid and chapter:  
    873                 finalvalues.append((chapter,chapter_type)) 
    874             elif len(value)==1 or (not chapter and not self.deleteEmptyChapters): 
    875                 finalvalues.append((chapter,chapter_type)) 
    876             num += 1 
    877         for uid in targetUIDs: # delete bad references         
    878             if uid not in uids: 
    879                 tool.deleteReference(instance, uid, self.relationship)             
    880  
    881         # update cleaned text 
    882         self.update_cleaned_text(instance, finalvalues) 
    883          
    884         # finally get it done 
    885         #print 'SET: %s' % finalvalues 
    886         ObjectField.set(self, instance, finalvalues, **kwargs) 
    887  
    888  
    889  
    890     def add_new_chapter(self,instance, types=[('', 'text_block')]): 
    891         """ adds new field/chapter """ 
    892         value=ChapterField.getRaw(self,instance) 
    893         if type(types)==tuple: 
    894             types=[types] 
    895         elif type(types)==str: 
    896             types=[('',types)] 
    897         value=value+types        
    898         self.update_cleaned_text(instance,value) 
    899         ObjectField.set(self, instance, value) 
    900         return len(value)-len(types) 
    901  
    902     def add_new_mediapiece(self,instance): 
    903         """ adds new field/mediapiece """ 
    904         return self.add_new_chapter(instance, [('', 'media_piece')]) 
    905  
    906     def add_new_slide(self,instance): 
    907         """ adds new field/mediapiece """ 
    908         return self.add_new_chapter(instance, [('', 'image_piece'),('','caption')]) 
    909  
    910     def add_new_scene(self,instance): 
    911         """ adds new field/mediapiece """ 
    912         value=ChapterField.getRaw(self,instance) 
    913         types=[('', 'image_piece'),('','audio_piece'),(['','',''],'pilot_keywords')] 
    914         value=value[:-3]+types+value[-3:]      
    915         self.update_cleaned_text(instance,value) 
    916         ObjectField.set(self, instance, value) 
    917         return len(value)-3 
    918  
    919     def add_new_question(self,instance,question_type): 
    920         """ adds new field/chapter """ 
    921         if question_type=='multiple_choices': 
    922             return self.add_new_chapter(instance, [(['',[],[]], 'multiple_choices')]) 
    923         elif question_type == 'choice': 
    924             return self.add_new_chapter(instance, [(['','',[]], 'choice')]) 
    925         else: 
    926             return self.add_new_chapter(instance, [('', question_type)]) 
    927      
    928     def importJQuizQuestions(self, instance, file, REQUEST, **kwargs): 
    929         """ import hot potatoes questions made with jquiz """         
    930         letool = getToolByName(instance,'lemill_tool') 
    931         from xml.sax.saxutils import unescape 
    932         file = unescape(file.strip()) 
    933         import xml.dom.minidom 
    934         try: 
    935             dom = xml.dom.minidom.parseString(file) 
    936         except: 
    937             return 0 
    938         questions = dom.getElementsByTagName("question-record") 
    939         chapter_values = ChapterField.getRaw(self, instance) 
    940         for ques in questions: 
    941             # get question type 
    942             try: 
    943                 questype = ques.getElementsByTagName("question-type")[0].childNodes[0].data 
    944             except: 
    945                 questype = 0 
    946             # get question text 
    947             try: 
    948                 questext = ques.getElementsByTagName("question")[0].childNodes[0].data.encode('utf-8') 
    949                 questext = unescape(questext) 
    950                 questext = letool.html_to_text(questext) 
    951             except: 
    952                 questext = ""   
    953             # if jquiz question type is multiple choice(1) or multiple correct(4) 
    954             correct_answers = 0 
    955             if (questype in ['1','4']): 
    956                 answers = ques.getElementsByTagName("answer") 
    957                 # calculate how many correct answers (is it choice or choice_multiple type) 
    958                 for answer in answers: 
    959                     try: 
    960                         correct = answer.getElementsByTagName("correct")[0].childNodes[0].data # 1-correct 0-false 
    961                     except: 
    962                         correct = 0 
    963                     if (int(correct) == 1): 
    964                         correct_answers += 1 
    965                 # if choice type (only one correct answer) 
    966                 if (correct_answers == 1): 
    967                     rightanswer = '' 
    968                     wronganswers = [] 
    969                     counter = 0 
    970                     for answer in answers: 
    971                         try: 
    972                             answertext = answer.getElementsByTagName("text")[0].childNodes[0].data.encode('utf-8') 
    973                             answertext = unescape(answertext.strip()) 
    974                             answertext = letool.html_to_text(answertext) 
    975                             # replace newline with space, because answer has to be in one row  
    976                             answertext = answertext.replace("\n", " ") 
    977                         except: 
    978                             answertext = '' 
    979                         if answertext != '': 
    980                             try: 
    981                                 correct_ans = int(answer.getElementsByTagName("correct")[0].childNodes[0].data) 
    982                             except: 
    983                                 correct_ans = 0 
    984                             if (correct_ans == 1): 
    985                                 rightanswer = answertext 
    986                             else: 
    987                                 wronganswers.append(answertext) 
    988                     if not (questext == "" and rightanswer == "" and wronganswers == []): 
    989                         chapter = ([questext, rightanswer, wronganswers], 'choice') 
    990                         chapter_values.append(chapter)  
    991                 else: 
    992                     rightanswers = [] 
    993                     wronganswers = [] 
    994                     counter = 0 
    995                     for answer in answers: 
    996                         try: 
    997                             answertext = answer.getElementsByTagName("text")[0].childNodes[0].data.encode('utf-8') 
    998                             answertext = unescape(answertext.strip()) 
    999                             answertext = letool.html_to_text(answertext) 
    1000                             # replace newline with space, because answer has to be in one row  
    1001                             answertext = answertext.replace("\n", " ") 
    1002                         except: 
    1003                             answertext = '' 
    1004                         if answertext != '': 
    1005                             try:     
    1006                                 correct_ans = int(answer.getElementsByTagName("correct")[0].childNodes[0].data) 
    1007                             except: 
    1008                                 correct_ans = 0 
    1009                             if (correct_ans == 1): 
    1010                                 rightanswers.append(answertext) 
    1011                             else: 
    1012                                 wronganswers.append(answertext) 
    1013                     if not (questext == "" and rightanswers == [] and wronganswers == []): 
    1014                         chapter = ([questext, rightanswers, wronganswers], 'multiple_choices') 
    1015                         chapter_values.append(chapter)  
    1016         self.update_cleaned_text(instance, chapter_values) 
    1017         ObjectField.set(self, instance, chapter_values, **kwargs) 
    1018         return 1 
    1019      
    1020     def importJClozeQuestion(self, instance, file, REQUEST, **kwargs): 
    1021         """ import hot potatoes question made with JCloze """         
    1022         letool = getToolByName(instance,'lemill_tool') 
    1023         from xml.sax.saxutils import unescape 
    1024         file = file.strip() 
    1025         file = file.replace('&amp;', '&') 
    1026         import xml.dom.minidom 
    1027         try: 
    1028             dom = xml.dom.minidom.parseString(file) 
    1029         except: 
    1030             return 0 
    1031         gap_fill_tags = dom.getElementsByTagName("gap-fill") 
    1032         questiontext = "" 
    1033         if (len(gap_fill_tags) > 0): 
    1034             gap_fill_tag = gap_fill_tags[0] 
    1035             for child in gap_fill_tag.childNodes: 
    1036                 if child.nodeType == child.TEXT_NODE: 
    1037                     child_data = unescape(child.data, {'&#x003C;':'<','&#x003E;':'>'}) 
    1038                     questiontext += letool.html_to_text(child_data) 
    1039                 else: 
    1040                     # get correct answers 
    1041                     answers = child.getElementsByTagName("answer") 
    1042                     gap_answers = "" 
    1043                     for answer in answers: 
    1044                         answertexts = answer.getElementsByTagName("text") 
    1045                         if (len(answertexts) > 0): 
    1046                             answertext_tag = answertexts[0] 
    1047                             if (len(answertext_tag.childNodes) > 0): 
    1048                                 answertext = unescape(answertext_tag.childNodes[0].data) 
    1049                                 gap_answers += "{"+answertext+"}" 
    1050                     questiontext += gap_answers 
    1051         chapter_values = ChapterField.getRaw(self, instance) 
    1052         blanks_chapter = (questiontext, 'fill_in_the_blanks') 
    1053         chapter_values.append(blanks_chapter)  
    1054         self.update_cleaned_text(instance, chapter_values) 
    1055         ObjectField.set(self, instance, chapter_values, **kwargs) 
    1056      
    1057  
    1058     def getBackgroundImage(self, context, chapter): 
    1059         """ returns a background images """ 
    1060         res = "" 
    1061         uid = self.isUid(chapter) 
    1062         if uid: 
    1063             piece = self.getObjectByUID(context,uid) 
    1064             if piece and piece.isImage() and not piece.isDeleted(): 
    1065                 res = '''background-image:url('%s/image_large')''' % piece.absolute_url() 
    1066             else: 
    1067                 pass 
    1068         return res 
    1069  
    1070     def isUid(self, chapter): 
    1071         """ Checks if chapter seems, smells and feels like UID """ 
    1072         if type(chapter) in STRING_TYPES: 
    1073             if chapter.isalnum() and len(chapter)==32 and len(chapter)==len(chapter.strip()): # ok, it's an UID! 
    1074                 return chapter 
    1075         elif type(chapter)==tuple and len(chapter)==2: 
    1076             return self.isUid(chapter[0]) 
    1077         elif type(chapter) in (list, tuple, int) or hasattr(chapter, 'filename'): 
    1078             return False 
    1079         else: 
    1080             chapter=chapter.UID() 
    1081             return chapter 
     942                        uid=value['audio_uid'] 
     943                    old_uid=old_chapter.get('audio_uid','') 
     944                    if old_uid: 
     945                        if old_uid!=uid: 
     946                            references_to_remove.append(old_uid) 
     947                            references_to_add.append(uid) 
     948                    else: 
     949                        references_to_add.append(uid) 
     950                    new_chapter['audio_uid']=uid                 
     951                    if value['image_file']: 
     952                        new_piece = lt.createPieceFromFile(value['image_file'], instance) 
     953                        uid=new_piece.UID() 
     954                    else: 
     955                        uid=value['image_uid'] 
     956                    old_uid=old_chapter.get('image_uid','') 
     957                    if old_uid: 
     958                        if old_uid!=uid: 
     959                            references_to_remove.append(old_uid) 
     960                            references_to_add.append(uid) 
     961                    else: 
     962                        references_to_add.append(uid) 
     963                    new_chapter['image_uid']=uid 
     964                    new_chapter['questions']=value['questions'] 
     965                    new_chapter['keywords']=value['keywords'] 
     966                elif ctype in ['image_piece','audio_piece','media_piece']: 
     967                    file=value.get('file','') 
     968                    new_chapter['text']='' 
     969                    if file: 
     970                        new_piece = lt.createPieceFromFile(file, instance) 
     971                        uid=new_piece.UID() 
     972                    else: 
     973                        uid=value['uid'] 
     974                    old_uid=old_chapter.get('uid','') 
     975                    if old_uid: 
     976                        if old_uid!=uid: 
     977                            references_to_remove.append(old_uid) 
     978                            if uid: 
     979                                references_to_add.append(uid) 
     980                    elif uid: 
     981                        references_to_add.append(uid) 
     982                    new_chapter['uid']=uid 
     983                elif ctype == 'embed_block': 
     984                    new_chapter['text']='' 
     985                    new_chapter['embed']=value['embed'] 
     986                else:             
     987                    new_chapter['text']=value['text'] 
     988                if new_chapter!=old_chapter: 
     989                    changed=True 
     990                new_values[edited]=new_chapter 
     991            ## Deleting ## 
     992            deleted=value.get('deleted',[]) 
     993            n_o=value.get('new_order',[]) 
     994            if deleted: 
     995                vals=[] 
     996                for i, val in enumerate(new_values): 
     997                    if i in deleted: 
     998                        uid=val.get('uid','') 
     999                        if uid: 
     1000                            references_to_remove.append(uid) 
     1001                    else: 
     1002                        vals.append(val) 
     1003                new_values=vals 
     1004                changed=True 
     1005                if n_o: 
     1006                    n_o=[val for i, val in enumerate(n_o) if i not in deleted]             
     1007            ## Sorting ## 
     1008            if n_o: 
     1009                sortable=zip(n_o, new_values) 
     1010                sortable.sort() 
     1011                new_values=[val for o, val in sortable] 
     1012                changed=True                        
     1013            ## Adding new chapters ## 
     1014            new_chapters=value.get('new_chapters',[]) 
     1015            if new_chapters: 
     1016                new_values+=new_chapters 
     1017                changed=True 
     1018        ### The case where field's get-method feeds field's set-method (archetype update, copying)  
     1019        elif isinstance(value, list): 
     1020            print 'setting from a list' 
     1021            references_new=[] 
     1022            for chapter in value: 
     1023                uid=chapter.get('uid','') 
     1024                if uid: 
     1025                    references_new.append(uid) 
     1026            references_old=[] 
     1027            for chapter in old_values: 
     1028                uid=chapter.get('uid','') 
     1029                if uid: 
     1030                    references_old.append(uid) 
     1031            references_to_add=[uid for uid in references_new if uid not in references_old] 
     1032            references_to_remove=[uid for uid in references_old if uid not in references_new] 
     1033            if value!=old_values: 
     1034                changed=True 
     1035            new_values=value 
     1036        ### update references if necessary 
     1037        if references_to_add or references_to_remove: 
     1038            tool = instance.reference_catalog 
     1039            for uid in references_to_add: 
     1040                tool.addReference(instance, uid, self.relationship) 
     1041            for uid in references_to_remove: 
     1042                tool.deleteReference(instance, uid, self.relationship) 
     1043        if changed: 
     1044            # update cleaned text 
     1045            print 'trying to update cleaned chapters' 
     1046            self.updateCleanedChapters(instance, new_values)         
     1047            # set new value 
     1048            print 'new_values for ObjectField.set:', new_values 
     1049            ObjectField.set(self, instance, new_values, **kwargs) 
    10821050 
    10831051    def getObjectByUID(self, instance, uid, catalog_only=False): 
     
    10871055            return None # this is just a newly created placeholder 
    10881056        results=uid_catalog(UID=uid) 
    1089         if results: 
     1057        try: 
    10901058            return results[0].getObject() 
    1091         else: 
     1059        except IndexError: 
    10921060            return None 
    1093  
    1094     def getChapter(self, instance, chap_nr): 
    1095         """ get a *cleaned* content of one chapter """ 
    1096         value=ChapterField.get(self, instance) 
    1097         if len(value)<chap_nr: 
    1098             return '' 
    1099         else: 
    1100             return value[chap_nr] 
    1101  
    1102     def getRawChapter(self, instance, chap_nr): 
    1103         """ get a *raw* content of one chapter """ 
    1104         value=ChapterField.getRaw(self, instance) 
    1105         if len(value)<chap_nr: 
    1106             return '' 
    1107         else: 
    1108             return value[chap_nr] 
    1109  
    1110     def delChapter(self, instance, chap_nr_start, chap_nr_end=0): 
    1111         """ delete chapter """ 
    1112         tool = getToolByName(instance, REFERENCE_CATALOG) 
    1113         value = ChapterField.getRaw(self,instance) 
    1114         if not chap_nr_end: 
    1115             chap_nr_end=chap_nr_start+1 
    1116         for c in range(chap_nr_start, chap_nr_end):         
    1117             if value[c][1] in ['media_piece','audio_piece','image_piece'] and value[c][0]: 
    1118                 tool.deleteReference(instance, value[c][0], self.relationship) 
    1119         del value[chap_nr_start:chap_nr_end] 
    1120         if value==[]: value=[('','text_block')] 
    1121         self.update_cleaned_text(instance,value) 
    1122         ObjectField.set(self, instance, value)         
    1123  
    1124     def moveChapterUp(self, instance, chap_nr, granularity): 
    1125         """ Move chapter chap_nr, chunk size is defined by int granularity """ 
    1126         values=ChapterField.getRaw(self,instance)  
    1127         if chap_nr-granularity<0: 
    1128             return 0 
    1129         begin = values[:chap_nr-granularity] # minus the chunk before current chapter 
    1130         moving_part = values[chap_nr-granularity:chap_nr] # the chunk before current chapter 
    1131         end = values[chap_nr+granularity:] 
    1132         chapter = values[chap_nr:chap_nr+granularity] 
    1133         value= begin+chapter+moving_part+end 
    1134         self.update_cleaned_text(instance,value) 
    1135         ObjectField.set(self, instance, value) 
    1136  
    1137     def moveChapterDown(self, instance, chap_nr, granularity): 
    1138         """ Move chapter chap_nr, chunk size is defined by int granularity """ 
    1139         values=ChapterField.getRaw(self,instance) 
    1140         if chap_nr+2*granularity>len(values): 
    1141             return 0 
    1142         begin = values[:chap_nr] 
    1143         chapter = values[chap_nr:chap_nr+granularity] 
    1144         moving_part = values[chap_nr+granularity:chap_nr+granularity*2] # the chunk after current chapter 
    1145         end = values[chap_nr+granularity*2:] # the rest after current chapter and chunk after it 
    1146         value=begin+moving_part+chapter+end 
    1147         self.update_cleaned_text(instance,value) 
    1148         ObjectField.set(self, instance, value) 
    11491061 
    11501062    def getLength(self, piece): 
    11511063        as_time=piece.getLength() 
    1152         return {'hour':as_time/3600,'minute':(as_time % 3600)/60,'second':as_time % 60}             
     1064        return {'hour':as_time/3600,'minute':(as_time % 3600)/60,'second':as_time % 60}            
    11531065 
    11541066registerField(ChapterField, 
     
    12461158        else: 
    12471159            field_data = ObjectField.get(self, instance, **kwargs) 
    1248         if field_data == None: 
     1160        if not field_data: 
    12491161            return "" 
    12501162        links = re.findall('\{.*?\}', field_data) 
     
    12521164            linktext = link[1:-1] 
    12531165            if linktext.lower().startswith('http://') or linktext.lower().startswith('https://'): 
    1254                 field_data = field_data.replace(link, '<a href="'+linktext+'" >'+letool.shorten_url(linktext)+'</a>') 
     1166                repl='<a href="%s">%s</a>' % (linktext, letool.shorten_url(linktext)) 
     1167                field_data = field_data.replace(link, repl) 
    12551168            elif linktext.lower().startswith('uid:'): 
    12561169                uid = linktext[4:] 
    12571170                results = pc({'UID':uid}) 
    1258                 if len(results)>0: 
    1259                     field_data = field_data.replace(link, '<a href="'+results[0].getURL()+'" >'+results[0].Title+'</a>') 
    1260                 else: 
    1261                     field_data = field_data.replace(link, '') 
     1171                repl='' 
     1172                if results: 
     1173                    repl='<a href="%s">%s</a>' % (results[0].getURL(), results[0].Title) 
     1174                field_data = field_data.replace(link, repl) 
    12621175        field_data = field_data.strip() 
    12631176        value = field_data.split('\n') 
  • trunk/LargeSectionFolder.py

    r3018 r3045  
    329329    security.declarePrivate('initializeArchetype') 
    330330    def initializeArchetype(self, **kwargs): 
     331        print 'LargeSectionFolder.initializeArchetype called', kwargs 
     332        t=time.time() 
     333 
    331334        ret_val = ATBTreeFolder.initializeArchetype(self, **kwargs) 
    332335        # Enable topic syndication by default 
     
    338341                except: # might get 'Syndication Information Exists' 
    339342                    pass 
     343        print '...Archetype Initialized ', time.time()-t 
    340344        return ret_val         
    341345 
     
    728732 
    729733 
    730     def js_queryForPieces(self, keyword='', search_type='image'): 
     734    def js_queryForPieces(self, keyword='', search_type='image', user='', collection=False): 
    731735        """ javascript is querying for pieces that are images """ 
    732         # when this method is called by javascript, all arguments are packed to string 'keyword' 
    733         # typical value for keyword: 'foobar,audio=False' 
    734736        result = [] 
    735         q = {'SearchableText': keyword,  
    736              'portal_type': ['Piece', ], 
    737              'getState': 'public', 
    738         } 
     737        q = {'portal_type':'Piece', 'getState': 'public'} 
     738        if not (keyword or user): 
     739            return []  
     740        if keyword: 
     741            q['SearchableText']=keyword 
     742        if user: 
     743            lutool=getToolByName(self, 'lemill_usertool') 
     744            q['listCreators']=lutool.getAuthenticatedId() 
     745            q['sort_on']='modified' 
     746            q['sort_order']='descending' 
    739747        if search_type=='image' or search_type=='cover_image': 
    740748            q['getPiece_type']='image' 
     
    743751 
    744752        ltool = getToolByName(self, 'lemill_tool') 
    745         q_results = ltool.searchResultsWrapper(q) 
     753        if collection and user: 
     754            query={'portal_type':'Piece', 'getState':'public', 'Creator':q['listCreators']} 
     755            if 'getPiece_type' in q: 
     756                query['getPiece_type']=q['getPiece_type'] 
     757            reftool = getToolByName(self, 'reference_catalog') 
     758            catalog = getToolByName(self, 'portal_catalog') 
     759            q_results=[] 
     760            cols=ltool.searchResultsWrapper(portal_type='Collection', getState='public', Creator=q['listCreators']) 
     761            for col in cols: 
     762                refs=reftool(sourceUID=col.UID, relationship='relatesToContent') 
     763                resource_uids=[x.targetUID for x in refs] 
     764                query['UID']=resource_uids 
     765                res=catalog(query) 
     766                if res: 
     767                   q_results+=res 
     768        else: 
     769            q_results = ltool.searchResultsWrapper(q) 
    746770        for r in q_results: 
    747771            if not r.getPiece_type: 
     
    767791        # javascript can handle unicode, but not utf_8-encoded stuff. Unicode shouldn't be preceded with u. 
    768792        result=str(result) # when list is turned to string, unicode strings inside are left as [..., u'titleishere', ] 
    769         result=result.replace("u'","'") # and now unicode markers "u'" are removed   
     793        result=result.replace(" u'"," '") 
     794        result=result.replace(' u"',' "') # and now unicode markers "u'" are removed  
    770795        return result   
    771796 
  • trunk/LeMillFactoryTool.py

    r2670 r3045  
    3636    def __call__(self, *args, **kwargs): 
    3737        """call method""" 
    38         #t=time.time() 
     38        print 'FACTORY called' 
     39        t=time.time() 
    3940        self._fixRequest() 
    4041        factory_info = self.REQUEST.get(FACTORY_INFO, {}) 
     42        print factory_info 
    4143        stack = factory_info['stack'] 
    4244        type_name = stack[0] 
     
    4547        # do a passthrough if parent contains the id 
    4648        if hasattr(aq_parent(self), id): 
    47             #print "Factory __call__ (shorter): %s" % str(time.time()-t) 
     49            print "Factory __call__ (shorter): %s" % str(time.time()-t) 
    4850            return aq_parent(self).restrictedTraverse('/'.join(stack[1:]))(*args, **kwargs) 
    4951 
     52        print 'FACTORY REQUESTING TEMP FOLDER'  
    5053        tempFolder = self._getTempFolder(type_name) 
    5154        # Mysterious hack that fixes some problematic interactions with SpeedPack: 
     
    5760        else: 
    5861            obj = temp_obj 
    59         #print "Factory __call__ (longer): %s" % str(time.time()-t) 
     62        print "Factory __call__ (longer): %s" % str(time.time()-t) 
    6063        return mapply(obj, self.REQUEST.args, self.REQUEST, 
    6164                               call_object, 1, missing_name, dont_publish_class, 
     
    8083            return path_string  
    8184 
     85    def __bobo_traverse__(self, REQUEST, name): 
     86        print '__bobo_traverse__, yeah' 
     87        # __bobo_traverse__ can be invoked directly by a restricted_traverse method call 
     88        # in which case the traversal stack will not have been cleared by __before_publishing_traverse__ 
     89        name = str(name) # fix unicode weirdness 
     90        types_tool = getToolByName(self, 'portal_types') 
     91        if not name in types_tool.listContentTypes(): 
     92            return getattr(self, name) # not a type name -- do the standard thing 
     93        return self._getTempFolder(str(name)) # a type name -- return a temp folder 
     94 
     95 
    8296    def _getTempFolder(self, type_name): 
    83         #t=time.time() 
     97        t=time.time() 
    8498        factory_info = self.REQUEST.get(FACTORY_INFO, {}) 
    8599        tempFolder = factory_info.get(type_name, None) 
    86100        if tempFolder: 
    87101            tempFolder = aq_inner(tempFolder).__of__(self) 
    88             #print "Factory _getTempFolder (short): %s" % str(time.time()-t) 
     102            print "Factory _getTempFolder (short): %s" % str(time.time()-t) 
    89103            return tempFolder 
    90104         
     
    138152        factory_info[type_name] = tempFolder 
    139153        self.REQUEST.set(FACTORY_INFO, factory_info) 
    140         #print "Factory _getTempFolder (long): %s" % str(time.time()-t) 
     154        print "Factory _getTempFolder (long): %s" % str(time.time()-t) 
    141155        return tempFolder 
    142156 
  • trunk/LeMillTool.py

    r3026 r3045  
    975975    def stripHTML(self, html): 
    976976        """ based on this: http://stackoverflow.com/questions/753052/strip-html-from-strings-in-python """ 
     977        if not html: 
     978            return u'' 
    977979        s = MLStripper() 
    978980        s.feed(html) 
  • trunk/LearningResource.py

    r3029 r3045  
    129129 
    130130    def canDeleteOnCancel(self): 
    131         """ LearningResources have translations that should be deleted if just created """ 
    132         history=self.getHistory() 
    133         if not history: 
     131        """ LearningResources have translations that should be deleted if they have been just created,  
     132            also anything that has no history past the author's changes in ten minutes. """ 
     133        if self.isJustCreated(): 
    134134            return True 
    135135        summary=history[0].get('_summary','') 
     
    376376            bodytext = self.getBodyText() 
    377377            for chapter in bodytext: 
    378                 if chapter[1] in ['media_piece', 'embed_block']: 
     378                if chapter['type'] in ['media_piece', 'embed_block']: 
    379379                    score += 1 
    380380        # Set the value for field score 
  • trunk/Material.py

    r2998 r3045  
    5555        return True 
    5656 
     57    def getOnlyText(self): 
     58        field=self.getField('bodyText') 
     59        return field.textOnly(self) 
     60 
     61 
     62    def getOnlyRawText(self): 
     63        return '\n'.join([x['text'] for x in self.getRawBodyText()]) 
     64 
    5765    def prepareForPDF(self): 
    5866        #html= self.base_view() 
  • trunk/MultimediaMaterial.py

    r3002 r3045  
    2020from Products.Archetypes.public import * 
    2121from Products.CMFCore.utils import getToolByName 
    22  
    2322from config import PROJECTNAME, MODIFY_CONTENT, VIEW 
    2423from FieldsWidgets import ChapterField, ChapterWidget 
     
    4241        default_output_type = 'text/x-html-captioned', 
    4342        default_content_type = 'text/html', 
    44         default=[('','text_block'),], 
     43        default=[{'type':'text_block','text':''}], 
    4544        widget=ChapterWidget(label = "Body text", 
    4645            label_msgid = "label_bodytext", 
     
    5150 
    5251schema = material_schema + community_editing_schema + no_description + draft_schema + multimediamaterial_schema  
    53  
    5452schema = schema.copy() 
    5553schema.moveField('rights', pos='bottom') 
     
    5957schema.moveField('editingMode', before='hideDrafts') 
    6058 
    61  
    6259class MultimediaMaterial(Branchable, Material): 
    6360    """Web page""" 
    64  
    6561    schema = schema 
    66  
    6762    meta_type = "MultimediaMaterial" 
    6863    archetype_name = "MultimediaMaterial" 
     
    7166    security = ClassSecurityInfo() 
    7267    security.declareObjectPublic() 
    73  
    74     security.declarePrivate('manage_afterAdd') 
    75     def manage_afterAdd(self, item, container): 
    76         Material.manage_afterAdd(self, item, container) 
    77  
    78     def getOnlyText(self): 
    79         field=self.getField('bodyText') 
    80         values = field.get(self) 
    81         if not values: 
    82             return '' 
    83         if type(values[0])==tuple: 
    84             dump= '\n'.join([x[0] for x in values if x[1]=='text_block']) 
    85         else: 
    86             dump= '\n'.join([x for x in values]) 
    87         return dump 
    88  
    89     def getOnlyRawText(self): 
    90         field=self.getField('bodyText') 
    91         values = field.getRaw(self) 
    92         if values: 
    93             return '\n'.join([x[0] for x in values if x[1]=='text_block']) 
    94         return '' 
    95  
    96     security.declareProtected(MODIFY_CONTENT,'delChapter') 
    97     def delChapter(self, REQUEST): 
    98         """ delete chapter """ 
    99         field = self.getField('bodyText') 
    100         field.delChapter(self, int(REQUEST.get('delete'))) 
    101         return REQUEST.RESPONSE.redirect(self.absolute_url()+'/edit') 
    10268 
    10369    ############ Clean content view ######################### 
  • trunk/PILOTMaterial.py

    r3010 r3045  
    2626from Material import Material 
    2727from Branchable import Branchable 
     28from xml.dom.minidom import getDOMImplementation 
    2829 
    2930pilotmaterial_schema = Schema(( 
     
    6364        default_output_type = 'text/x-html-captioned', 
    6465        default_content_type = 'text/html', 
    65         default=[('', 'image_piece'),('','audio_piece'),(['','',''],'pilot_keywords'),('', 'image_piece'),('','audio_piece'),(['','','','','','',''],'pilot_questions'),], 
    66         deleteEmptyChapters = False, 
     66        default=[{'type':'image_piece','text':'', 'uid':''}, 
     67            {'type':'audio_piece','text':'', 'uid':''}, 
     68            {'type':'pilot_keywords','text':'', 'keywords':['','','']}, 
     69            {'type':'image_piece','text':'', 'uid':''}, 
     70            {'type':'audio_piece','text':'', 'uid':''}, 
     71            {'type':'pilot_questions','text':'', 'questions':['','','','','','','']}],           
    6772        widget=PilotWidget(label = "", 
    6873            label_msgid = "", 
     
    9499    meta_type = 'PILOTMaterial' 
    95100    archetype_name = 'PILOTMaterial' 
    96     default_location = 'content/pilots' 
    97      
     101    default_location = 'content/pilots'     
    98102    security = ClassSecurityInfo() 
    99103    security.declareObjectPublic() 
     
    109113    def getOnlyText(self): 
    110114        """ bodyTexts index_method: goes through all text fields in body text and concatenates them to a mass of text suitable for indexing """ 
    111         field=self.getField('bodyText') 
    112         values = field.get(self) 
    113         reslist=[] 
    114         if not values: 
    115             return '' 
    116         if type(values)==list: 
    117             if type(values[0])==tuple:         
    118                 for (chap, chaptype) in values: 
    119                     if chap==None: 
    120                         continue 
    121                     if type(chap)==list or type(chap)==tuple: 
    122                         chap=to_unicode('\n'.join(chap)) 
    123                     reslist.append(chap) 
    124             else: 
    125                 for chap in values: 
    126                     if chap==None: 
    127                         continue 
    128                     if type(chap)==list or type(chap)==tuple: 
    129                         chap=to_unicode('\n'.join(chap)) 
    130                     reslist.append(chap)                                 
    131         else: 
    132             return values 
    133  
    134         return u'\n'.join(reslist) 
     115        return u'\n'.join([item['text'] for item in self.getBodyText()]) 
    135116 
    136117    def getQuestions(self): 
    137118        """ Research questions are the last part of the bodytext, returned as a list """ 
    138         for chapter, chapter_type  in self.getRawBodyText(): 
    139             if chapter_type=='pilot_questions': 
    140                 return chapter 
     119        for item in self.getBodyText(): 
     120            if item['type']=='pilot_questions': 
     121                return item['questions'] 
    141122        else: 
    142123            return ['']*7 
    143  
    144124 
    145125    def getEmbedCode(self): 
     
    148128        return """<embed src="%s/player.swf" loop="false" quality="high" bgcolor="#000000" width="500" height="400" name="player" align="middle" allowScriptAccess="sameDomain" allowFullScreen="true" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="xml=%s/buildXML" />""" % (portal_url(), self.absolute_url()) 
    149129 
     130 
    150131    def buildXML(self, only_validate=False): 
    151         """ builds the xml-blueprint from bodytext for flash engine """ 
    152  
    153         def kw_cutter(k,maxlength): 
    154             """ method to add linebreaks to long keywords """  
    155             if len(k)>maxlength: 
    156                 cutend=maxlength 
    157                 cutbegin=0 
    158                 resl=[] 
    159                 while cutend<len(k): 
    160                     while k[cutend] not in ' +-': 
    161                         cutend=cutend-1 
    162                     resl.append(k[cutbegin:cutend]) 
    163                     cutbegin=cutend 
    164                     cutend=cutend+maxlength 
    165                 resl.append(k[cutbegin:]) 
    166                 k=' &amp;&amp;'.join(resl) 
    167             return k 
    168  
    169         bodyfield=self.getField('bodyText') 
    170         bodytext=self.getRawBodyText() 
    171         title=self.title 
    172         failure='' 
    173         def setFailure(new_failure, old_failure): 
    174             if (new_failure == 'fatal' and old_failure in ('soft', '')) or (new_failure == 'soft' and old_failure == ''): 
    175                 return new_failure 
    176             return old_failure 
    177         voice_urls=[] 
    178         bg_url=[] 
    179         kw=[] 
    180         has_title=0 
    181         title_begin='' 
    182         maxlength=40 # how many characters of keyword fits on one line 
    183         msg='' 
    184         f=-1 
    185         ### Collect and validate values ### 
    186         for (chapter, chapter_type) in bodytext: 
    187             f+=1 
    188             if chapter_type == 'image_piece':  
    189                 # backgrounds 
    190                 obj=bodyfield.getObjectByUID(self, chapter) 
    191                 url='missing' 
    192                 if obj!=None: 
    193                     if obj.isImage(): 
    194                         url='/'.join((obj.absolute_url(),'image_large')) 
    195                     else: 
    196                         msg=msg+'Background image %s is missing, using blank instead.' % str((f/3)+1) 
    197                         failure = setFailure('fatal', failure) 
    198                 else: 
    199                     msg=msg+'Background image %s is missing, using blank instead.' % str((f/3)+1)                     
    200                     failure = setFailure('fatal', failure) 
    201                 bg_url.append(url) 
    202  
    203             elif chapter_type == 'audio_piece':  
    204                 # voiceovers 
    205                 obj=bodyfield.getObjectByUID(self, chapter) 
    206                 url='missing' 
    207                 length=30 
    208                 if obj!=None: 
    209                     if obj.isAudio(): 
    210                         url=obj.absolute_url()+'/file' 
    211                         length=obj.getLength() 
    212                     else: 
    213                         msg=msg+'Voiceover for scene %s is missing, using 15 seconds of ambient noise instead.' % str((f/3)+1) 
    214                         failure = setFailure('soft', failure) 
    215                 else: 
    216                     msg=msg+'Voiceover for scene %s is missing, using 15 seconds of ambient noise instead.' % str((f/3)+1)                     
    217                     failure = setFailure('soft', failure) 
    218                 voice_urls.append((url,length)) 
    219              
    220             elif chapter_type == 'pilot_keywords' or chapter_type == 'pilot_questions':  
    221                 # keywords 
    222                 tkw=[to_unicode(x) for x in chapter] 
    223                 if len(tkw)>3 and f<len(bodytext)-2: 
    224                     tkw=tkw[:3] # three keywords max unless final scene 
    225                 if tkw==[]: 
    226                     tkw=[''] 
    227                     msg=msg+'Keywords for scene %s are missing' % str((f/3)+1) 
    228                     failure = setFailure('soft', failure) 
    229                 if voice_urls:                     
    230                     mplength=voice_urls[-1][1] # length of latest voiceover 
    231                 else:  
    232                     mplength=15 
    233                 if f==2: 
    234                     has_title=1 
    235                 cliplen = mplength/(len(tkw)+has_title)  # time reserved for each keyword 
    236                 i=0 
    237                 newlist=[] 
    238                 if has_title==1: 
    239                     i=1 
    240                     title_begin='1' 
    241                     title_end=str(cliplen-2) 
    242                     has_title=0                    
    243                 for x in tkw: 
    244                     newlist.append((x, i*cliplen+1, (i+1)*cliplen-2)) 
    245                     i=i+1 
    246                 kw.append(newlist) 
    247                 # cliplen = time allocated for each keyword 
    248                 # kw[n]=(keyword, startpoint, endpoint) 
    249  
    250         if len(voice_urls)!=len(bg_url) or len(bg_url)!=len(kw): 
    251             failure = setFailure('fatal', failure) 
    252         if title_begin=='': 
    253             msg=msg+"Scene doesn't have enough components, keywords are missing." 
    254             failure = setFailure('fatal', failure) 
    255  
    256         if only_validate==True: 
    257             return (failure,  
    258                 bg_url, 
    259                 [x[0] for x in voice_urls], 
    260                 [x[0][0] for x in kw]) 
    261  
    262         if failure=='fatal': 
    263             print 'fatal failure' 
    264             print msg 
    265             lt = getToolByName(self, 'lemill_tool') 
    266             lt.addPortalMessage(_('Building flash file for PILOT failed. ')) 
     132        lt=getToolByName(self, 'lemill_tool') 
     133        title=self.Title() 
     134        doc = getDOMImplementation().createDocument(None, "pilot", None) 
     135        pilot = doc.documentElement 
     136        title = doc.createElement('title') 
     137        title.appendChild(doc.createTextNode(self.Title())) 
     138        pilot.appendChild(title) 
     139                 
     140        new_scene=True 
     141        has_questions=False      
     142        for chapter in self.getBodyText(): 
     143            if new_scene: 
     144                scene=doc.createElement('scene')  
     145                new_scene = has_kw = False 
     146            backgroundimage=doc.createElement('backgroundimage') 
     147            img_obj=lt.getObjectByUID(chapter['uid']) 
     148            if img_obj: 
     149                backgroundimage.setAttribute('src', img_obj.absolute_url()+'/image_large')                 
     150            scene.appendChild(backgroundimage) 
     151            voiceover=doc.createElement('voiceover') 
     152            aud_obj=lt.getObjectByUID(chapter['uid']) 
     153            if aud_obj: 
     154                voiceover.setAttribute('src', aud_obj.absolute_url()+'/file')                 
     155            scene.appendChild(voiceover) 
     156            for word in chapter['keywords']: 
     157                keyword=doc.createElement('keyword') 
     158                keyword.appendChild(doc.createTextNode(word)) 
     159                scene.appendChild(keyword) 
     160                has_kw=True 
     161            for word in chapter['questions']: 
     162                keyword=doc.createElement('keyword') 
     163                keyword.appendChild(doc.createTextNode(word)) 
     164                scene.appendChild(keyword) 
     165                has_questions=True 
     166            if has_kw: 
     167                pilot.appendChild(scene) 
     168                new_scene=True 
     169            elif has_questions: 
     170                pilot.appendChild(scene) 
     171                break 
     172        if has_questions: 
     173            return doc.toprettyxml(encoding='utf-8') 
     174        else: 
    267175            return '' 
    268176 
    269         ### Build xml ### 
    270  
    271         scene_type=' type="title"' 
    272         xml=u'<?xml version="1.0" encoding="utf-8"?>\n<pilot>\n' 
    273         xml=xml+u'<title begin="'+title_begin+u'" end="'+title_end+u'">'+title+u'</title>\n' 
    274         for i in range(len(bg_url)): 
    275             xml=xml+u'<scene'+scene_type+u'>\n' 
    276             xml=xml+u'   <voiceover src="'+voice_urls[i][0]+u'" len="'+str(voice_urls[i][1])+u'"/>\n' 
    277             xml=xml+u'   <backgroundimage src="'+bg_url[i]+'" />\n' 
    278             for k in range(len(kw[i])):             
    279                 xml=xml+u'   <keyword begin="'+str(kw[i][k][1])+u'" end="'+str(kw[i][k][2])+u'">'+kw[i][k][0]+u'</keyword>\n' 
    280             xml=xml+u'</scene> \n' 
    281             if i < len(bg_url)-2: 
    282                 scene_type='' 
    283             else: 
    284                 scene_type=u' type="questions"' 
    285         xml=xml+u'</pilot>' 
    286         # We use UTF-16 because that way we get the BOM, which Flash XML parser requires. 
    287         return xml.encode('utf-16') 
     177#             
     178# 
     179#    def buildXMLOLD(self, only_validate=False): 
     180#        """ builds the xml-blueprint from bodytext for flash engine """ 
     181# 
     182#        def kw_cutter(k,maxlength): 
     183#            """ method to add linebreaks to long keywords """  
     184#            if len(k)>maxlength: 
     185#                cutend=maxlength 
     186#                cutbegin=0 
     187#                resl=[] 
     188#                while cutend<len(k): 
     189#                    while k[cutend] not in ' +-': 
     190#                        cutend=cutend-1 
     191#                    resl.append(k[cutbegin:cutend]) 
     192#                    cutbegin=cutend 
     193#                    cutend=cutend+maxlength 
     194#                resl.append(k[cutbegin:]) 
     195#                k=' &amp;&amp;'.join(resl) 
     196#            return k 
     197# 
     198#        bodyfield=self.getField('bodyText') 
     199#        bodytext=self.getRawBodyText() 
     200#        title=self.title 
     201#        failure='' 
     202#        def setFailure(new_failure, old_failure): 
     203#            if (new_failure == 'fatal' and old_failure in ('soft', '')) or (new_failure == 'soft' and old_failure == ''): 
     204#                return new_failure 
     205#            return old_failure 
     206#        voice_urls=[] 
     207#        bg_url=[] 
     208#        kw=[] 
     209#        has_title=0 
     210#        title_begin='' 
     211#        maxlength=40 # how many characters of keyword fits on one line 
     212#        msg='' 
     213#        f=-1 
     214#        ### Collect and validate values ### 
     215#        for (chapter, chapter_type) in bodytext: 
     216#            f+=1 
     217#            if chapter_type == 'image_piece':  
     218#                # backgrounds 
     219#                obj=bodyfield.getObjectByUID(self, chapter) 
     220#                url='missing' 
     221#                if obj!=None: 
     222#                    if obj.isImage(): 
     223#                        url='/'.join((obj.absolute_url(),'image_large')) 
     224#                    else: 
     225#                        msg=msg+'Background image %s is missing, using blank instead.' % str((f/3)+1) 
     226#                        failure = setFailure('fatal', failure) 
     227#                else: 
     228#                    msg=msg+'Background image %s is missing, using blank instead.' % str((f/3)+1)                     
     229#                    failure = setFailure('fatal', failure) 
     230#                bg_url.append(url) 
     231# 
     232#            elif chapter_type == 'audio_piece':  
     233#                # voiceovers 
     234#                obj=bodyfield.getObjectByUID(self, chapter) 
     235#                url='missing' 
     236#                length=30 
     237#                if obj!=None: 
     238#                    if obj.isAudio(): 
     239#                        url=obj.absolute_url()+'/file' 
     240#                        length=obj.getLength() 
     241#                    else: 
     242#                        msg=msg+'Voiceover for scene %s is missing, using 15 seconds of ambient noise instead.' % str((f/3)+1) 
     243#                        failure = setFailure('soft', failure) 
     244#                else: 
     245#                    msg=msg+'Voiceover for scene %s is missing, using 15 seconds of ambient noise instead.' % str((f/3)+1)                     
     246#                    failure = setFailure('soft', failure) 
     247#                voice_urls.append((url,length)) 
     248#             
     249#            elif chapter_type == 'pilot_keywords' or chapter_type == 'pilot_questions':  
     250#                # keywords 
     251#                tkw=[to_unicode(x) for x in chapter] 
     252#                if len(tkw)>3 and f<len(bodytext)-2: 
     253#                    tkw=tkw[:3] # three keywords max unless final scene 
     254#                if tkw==[]: 
     255#                    tkw=[''] 
     256#                    msg=msg+'Keywords for scene %s are missing' % str((f/3)+1) 
     257#                    failure = setFailure('soft', failure) 
     258#                if voice_urls:                     
     259#                    mplength=voice_urls[-1][1] # length of latest voiceover 
     260#                else:  
     261#                    mplength=15 
     262#                if f==2: 
     263#                    has_title=1 
     264#                cliplen = mplength/(len(tkw)+has_title)  # time reserved for each keyword 
     265#                i=0 
     266#                newlist=[] 
     267#                if has_title==1: 
     268#                    i=1 
     269#                    title_begin='1' 
     270#                    title_end=str(cliplen-2) 
     271#                    has_title=0                    
     272#                for x in tkw: 
     273#                    newlist.append((x, i*cliplen+1, (i+1)*cliplen-2)) 
     274#                    i=i+1 
     275#                kw.append(newlist) 
     276#                # cliplen = time allocated for each keyword 
     277#                # kw[n]=(keyword, startpoint, endpoint) 
     278# 
     279#        if len(voice_urls)!=len(bg_url) or len(bg_url)!=len(kw): 
     280#            failure = setFailure('fatal', failure) 
     281#        if title_begin=='': 
     282#            msg=msg+"Scene doesn't have enough components, keywords are missing." 
     283#            failure = setFailure('fatal', failure) 
     284# 
     285#        if only_validate==True: 
     286#            return (failure,  
     287#                bg_url, 
     288#                [x[0] for x in voice_urls], 
     289#                [x[0][0] for x in kw]) 
     290# 
     291#        if failure=='fatal': 
     292#            print 'fatal failure' 
     293#            print msg 
     294#            lt = getToolByName(self, 'lemill_tool') 
     295#            lt.addPortalMessage(_('Building flash file for PILOT failed. ')) 
     296#            return '' 
     297# 
     298#        ### Build xml ### 
     299# 
     300#        scene_type=' type="title"' 
     301#        xml=u'<?xml version="1.0" encoding="utf-8"?>\n<pilot>\n' 
     302#        xml=xml+u'<title begin="'+title_begin+u'" end="'+title_end+u'">'+title+u'</title>\n' 
     303#        for i in range(len(bg_url)): 
     304#            xml=xml+u'<scene'+scene_type+u'>\n' 
     305#            xml=xml+u'   <voiceover src="'+voice_urls[i][0]+u'" len="'+str(voice_urls[i][1])+u'"/>\n' 
     306#            xml=xml+u'   <backgroundimage src="'+bg_url[i]+'" />\n' 
     307#            for k in range(len(kw[i])):             
     308#                xml=xml+u'   <keyword begin="'+str(kw[i][k][1])+u'" end="'+str(kw[i][k][2])+u'">'+kw[i][k][0]+u'</keyword>\n' 
     309#            xml=xml+u'</scene> \n' 
     310#            if i < len(bg_url)-2: 
     311#                scene_type='' 
     312#            else: 
     313#                scene_type=u' type="questions"' 
     314#        xml=xml+u'</pilot>' 
     315#        # We use UTF-16 because that way we get the BOM, which Flash XML parser requires. 
     316#        return xml.encode('utf-16') 
    288317 
    289318 
  • trunk/Resource.py

    r3034 r3045  
    8080        """Processes the schema looking for data in the form, taken from Archetypes BaseObject 
    8181        """ 
     82        print 'Resource.processForm called with: ', data, values 
    8283        is_new_object = self.checkCreationFlag() 
     84        print 'is_new_object:', is_new_object   
    8385        self._processForm(data=data, metadata=metadata, 
    8486                          REQUEST=REQUEST, values=values) 
     
    8688        # Post create/edit hooks 
    8789        if is_new_object: 
     90            print 'calling at_post_create_script'  
    8891            self.at_post_create_script() 
    8992        else: 
     93            print 'calling at_post_edit_script'  
    9094            self.at_post_edit_script() 
    9195        event.notify(objectevent.ObjectModifiedEvent(self)) 
     
    154158    def _processForm(self, data=1, metadata=None, REQUEST=None, values=None): 
    155159 
     160        print 'Resource._processForm called' 
    156161        # from Archetypes/BaseObject minus reindexObject at the end 
    157162        request = REQUEST or self.REQUEST 
     
    207212            if result is _marker or result is None: 
    208213                continue 
    209  
    210214            # Set things by calling the mutator 
    211215            mutator = field.getMutator(self) 
     
    225229    def dumpme(self,item): 
    226230        """ print """ 
     231 
    227232        print 'dump: %s' % item 
    228233 
     
    324329                return desc 
    325330        # try bodytext 
    326         if hasattr(self, 'getRawBodyText'): 
     331        if hasattr(self, 'getRawBodyText'):             
    327332            desc=self.getRawBodyText() 
     333            print desc 
    328334            if isinstance(desc, list): 
    329335                desc=desc[0] 
    330336                if isinstance(desc, tuple): 
    331337                    desc=desc[0] 
     338                elif isinstance(desc, dict): 
     339                    desc=desc['text'] 
    332340            desc_len=0 
    333341            desc_result=[] 
     
    790798 
    791799 
     800    security.declarePublic('isJustCreated') 
     801    def isJustCreated(self): 
     802        """ Plone's normal 'just created' detection methods do not work with chapterized resources. 
     803            let's use history to find if recent changes are from the same author and there aren't many of them. """ 
     804        history = self.getHistory() 
     805        if not history: 
     806            return True 
     807        lutool = getToolByName(self, 'lemill_usertool') 
     808        auth_id = lutool.getAuthenticatedId() 
     809        now = time.time() 
     810        for entry in history: 
     811            if auth_id!=entry['_by'] or now - entry['_timestamp'] > 600: 
     812                return False 
     813        return True 
     814 
    792815    security.declarePublic('isMinorEditHappening') 
    793816    def isMinorEditHappening(self): 
  • trunk/config.py

    r3014 r3045  
    353353        'title'  : 'Web page', 
    354354        'description' : 'Create a web page with images, sound and movie clips.', 
    355         'fields' : ['title', 'bodyText', 'mediapieces'], 
     355        'fields' : ['title', 'bodyText'], 
    356356        'meta_type' : 'MultimediaMaterial', 
    357357        'icon' : 'images/default_multimedia_page.png', 
     
    361361        'title'  : 'Exercise', 
    362362        'description' : 'Create a web page with exercises.', 
    363         'fields' : ['title', 'bodyText', 'mediapieces'], 
     363        'fields' : ['title', 'bodyText'], 
    364364        'meta_type' : 'ExerciseMaterial', 
    365365        'icon' : 'images/default_exercise.png', 
  • trunk/skins/lemill/OAI-script.py

    r3014 r3045  
    245245        if userMD: 
    246246            userMD=userMD[0] 
    247             names=userMD.getNicename.split(' ') 
    248             names.reverse() 
    249             names=';'.join(names) 
    250             result.append(''.join(('<contribute><role><source>LOMv1.0</source><value>author</value></role><entity>BEGIN:VCARD\nVERSION:3.0\nN:',html_quote(names),';;;\nFN:',html_quote(userMD.getNicename),'\nEND:VCARD</entity></contribute>'))) 
     247            nn=userMD.getNicename 
     248            try: 
     249                names=nn.split(' ') 
     250                names.reverse() 
     251                names=';'.join(names) 
     252            except AttributeError: 
     253                names='' 
     254                nn='' 
     255            result.append(''.join(('<contribute><role><source>LOMv1.0</source><value>author</value></role><entity>BEGIN:VCARD\nVERSION:3.0\nN:',html_quote(names),';;;\nFN:',html_quote(nn),'\nEND:VCARD</entity></contribute>'))) 
    251256    result.append(''.join(('</lifeCycle><metaMetadata><identifier><catalog>oai</catalog><entry>',obj.getId,'</entry></identifier><language>',language,'</language><metadataSchema>LREv3.0</metadataSchema></metaMetadata><technical><location>',obj.getURL(),'</location></technical><educational>'))) 
    252257    result.append('<learningResourceType><source>LREv3.0</source>') 
  • trunk/skins/lemill/activity_edit.cpt

    r2953 r3045  
    4040        <metal:eula_here use-macro="here/multimediamaterial_edit/macros/eula" /> 
    4141 
    42         <input class="context" 
    43             tabindex="" 
    44             type="submit" 
    45             name="form.button.form_submit" 
    46             value="Save" i18n:domain="plone" 
    47             i18n:attributes="value label_save;" 
    48             tal:attributes="tabindex tabindex/next; 
    49             disabled python:test(isLocked, 'disabled', None);" 
    50             /> 
    51         <input class="standalone" 
    52             tabindex="" 
    53             type="submit" 
    54             name="form.button.cancel" 
    55             value="Cancel" i18n:domain="plone"  
    56             i18n:attributes="value label_cancel;" 
    57             tal:attributes="tabindex tabindex/next" 
    58             /> 
    59         </div> 
    60         <input type="hidden" name="form.submitted" value="1" /> 
     42        <metal:save_buttons use-macro="here/base_edit/macros/save_buttons" /> 
    6143 
    6244        </form> 
  • trunk/skins/lemill/base_edit.cpt

    r2949 r3045  
    5858        <metal:use_body use-macro="body_macro"> 
    5959 
    60         <tal:comment replace="nothing"> 
    61         This is copied here to add form.button to button names 
    62         </tal:comment> 
    63  
    64         <metal:buttons fill-slot="buttons" tal:define="fieldset_index python:fieldsets.index(fieldset); 
    65         n_fieldsets python:len(fieldsets)"> 
    66         <input class="context" 
     60        <metal:save_buttons define-macro="save_buttons"> 
     61        <div class="form_submit"> 
     62        <input class="context savebutton" 
    6763            tabindex="" 
    6864            type="submit" 
    6965            name="form.button.form_submit" 
    70             value="Save" 
     66            value="Save" i18n:domain="plone" 
    7167            i18n:attributes="value label_save;" 
    72             tal:attributes="tabindex tabindex/next; 
    73             disabled python:test(isLocked, 'disabled', None);" 
     68            tal:attributes="tabindex tabindex/next;" 
    7469            /> 
    7570        <input class="standalone" 
     
    7772            type="submit" 
    7873            name="form.button.cancel" 
    79             value="Cancel" 
     74            value="Cancel" i18n:domain="plone"  
    8075            i18n:attributes="value label_cancel;" 
    8176            tal:attributes="tabindex tabindex/next" 
    8277            /> 
    83              
    84              
    85         </metal:buttons> 
     78        </div> 
     79        <div class="required_notes"> 
     80          <ul id="req_list" tal:repeat="req context/getRequiredFieldNames"> 
     81            <li tal:attributes="id string:required_${req}; onclick string:focusToField('${req}'); onmouseover string:showFieldHint(this, '${req}'); onmouseout string:hideFieldHint(this, '${req}');" class="req_link"><span class="hint_label" tal:content="req" i18n:translate="" /> <span i18n:translate="is_required">is required</span></li> 
     82          </ul> 
     83        </div> 
     84        <input type="hidden" name="form.submitted" value="1" /> 
     85        </metal:save_buttons> 
    8686 
    8787        </metal:use_body> 
  • trunk/skins/lemill/base_edit.cpt.metadata

    r2701 r3045  
    88validators.PresentationMaterial= validate_base_silently 
    99validators.PILOTMaterial= validate_base_silently 
    10 validators.MultimediaMaterial= validate_base_silently 
     10validators.MultimediaMaterial= validate_reloading_page 
     11validators.MultimediaMaterial.save_and_move= validate_base_silently 
    1112validators.ExerciseMaterial = validate_base_silently 
    1213validators.LeMillPrintResource = validate_base,validate_printresource 
  • trunk/skins/lemill/blogpost_view.pt

    r2878 r3045  
    2323        <!-- body macro where all the fields are --> 
    2424        <metal:body define-macro="body"> 
     25        <div class="documentByLine" tal:define="blog here/getBlog | here/getGroupBlog | nothing" tal:condition="blog"><a href="" i18n:translate="byline_link_to_back" tal:attributes="href blog/absolute_url">Back to <span tal:replace="blog/getNicename" i18n:name="name"/>'s page</a></div> 
    2526                 
    2627        <metal:fieldMacro use-macro="python:here.widget('bodyText',mode='view')"/> 
  • trunk/skins/lemill/collection_edit.cpt

    r3041 r3045  
    3636                            </td> 
    3737                      
    38                         <td ><img src="images/cross.gif" class="delete_button" width="20" height="20" alt="Remove" onclick="javascript:collectionDelete(this);"/> 
     38                        <td ><img src="images/cross_lt.gif" class="delete_button" width="20" height="20" alt="Remove" onclick="javascript:collectionDelete(this);"/> 
    3939                        <input type="hidden" class="deletion_marker" tal:attributes="name python:'%s_deleted' % obj.UID()" value="0"/> 
    4040                        </td> 
     
    5252                            <td><a href="" class="collection_item" tal:attributes="href python:here.getPathInsideCollection(obj)" tal:content="obj/title_or_id">Media Computer - From Math to Augmentation</a></td> 
    5353                      
    54                         <td><img src="images/cross.gif" class="delete_button" width="20" height="20" alt="Remove" onclick="javascript:collectionDelete(this);"/> 
     54                        <td><img src="images/cross_lt.gif" class="delete_button" width="20" height="20" alt="Remove" onclick="javascript:collectionDelete(this);"/> 
    5555                        <input type="hidden" class="deletion_marker" tal:attributes="name python:'%s_deleted' % obj.UID()" value="0"/> 
    5656                    </td>    
     
    6767                            <td><a href="" class="collection_item" tal:attributes="href python:here.getPathInsideCollection(obj)" tal:content="obj/title_or_id">Media Computer - From Math to Augmentation</a></td> 
    6868                      
    69                         <td><img src="images/cross.gif" class="delete_button" width="20" height="20" alt="Remove" onclick="javascript:collectionDelete(this);"/> 
     69                        <td><img src="images/cross_lt.gif" class="delete_button" width="20" height="20" alt="Remove" onclick="javascript:collectionDelete(this);"/> 
    7070                        <input type="hidden" class="deletion_marker" tal:attributes="name python:'%s_deleted' % obj.UID()" value="0"/> 
    7171                        </td> 
     
    7979                <tr tal:repeat="obj resources" class="choice_row"> 
    8080                   <tal:defines define="index repeat/obj/index; deleted obj/isDeleted"> 
     81<<<<<<< .mine 
     82                    <td valign="top" style="border-right: 1px solid #8cacbb" width="20"><img src="images/sorthandle.png" width="20" height="20" valign="top" class="handle" /> 
     83                    <input type="hidden" class="orderkeeper" value="0" name="col_order_0" id="col_order_0" tal:attributes="value index;name python:'col_order_%s' % index" /></td>  
     84======= 
    8185                    <td valign="top" style="border-right: 1px solid #8cacbb" width="20"><img src="images/sorthandle.png" width="20" height="20" valign="top" class="handle"/> 
    8286                    <input type="hidden" class="orderkeeper" value="0" name="col_order_0" id="col_order_0" tal:attributes="value index;name python:'col_order_%s' % index"/></td>  
     87>>>>>>> .r3043 
    8388 
    8489                    <td class="coverimage_cell_small"><img src="" alt="" class="coverimage_small" tal:attributes="src obj/getCoverImageURL; alt obj/title_or_id | nothing" /></td> 
     
    8792                            </td> 
    8893                      
    89                         <td><img src="images/cross.gif" class="delete_button" width="20" height="20" alt="Remove" onclick="javascript:collectionDelete(this);"/> 
     94                        <td><img src="images/cross_lt.gif" class="delete_button" width="20" height="20" alt="Remove" onclick="javascript:collectionDelete(this);"/> 
    9095                        <input type="hidden" class="deletion_marker" tal:attributes="name python:'%s_deleted' % obj.UID()" value="0"/> 
    9196                        </td> 
  • trunk/skins/lemill/content_edit.cpy

    r2939 r3045  
    1919# * mysterious current-lang thingy  
    2020 
    21 # Basically only thing left is to do processForm() and yell for errors. 
     21# Basically only thing left is to do processForm() and shout about errors. 
    2222 
    23  
     23# Checked, this is ok, doesn't take much time.  
     24context.dumpme('content_edit.cpy reached') 
    2425try: 
    2526    new_context = context.portal_factory.doCreate(context, id) 
     
    2829    new_context = context 
    2930new_context.processForm() 
     31new_context.dumpme('content_edit.cpy: processForm completed') 
     32 
     33if not state.errors: 
     34    from Products.Archetypes import transaction_note 
     35    transaction_note('Edited %s %s at %s' % (new_context.meta_type, 
     36                                             new_context.title_or_id(), 
     37                                             new_context.absolute_url())) 
     38if REQUEST.form.get('stay',0): 
     39    edit = REQUEST.form.get('chapter_edited',-1) 
     40    anchor = REQUEST.form.get('chapter_anchor',0) 
     41    return state.set(next_action='redirect_to:string:edit?chapter_edited=%s#%s' % (edit, anchor), status='stay', context=new_context) 
    3042 
    3143if state.errors: 
     
    4658#context.remove_creation_mark(old_id) 
    4759 
    48 if not state.errors: 
    49     from Products.Archetypes import transaction_note 
    50     transaction_note('Edited %s %s at %s' % (new_context.meta_type, 
    51                                              new_context.title_or_id(), 
    52                                              new_context.absolute_url())) 
    5360 
    54 return state.set(status='success', 
    55                  context=new_context) 
     61return state.set(status='success', context=new_context) 
  • trunk/skins/lemill/content_edit.cpy.metadata

    r2949 r3045  
    77[actions] 
    88action.success = redirect_to_action:string:view 
    9  
    10 action.success.MultimediaMaterial = traverse_to:string:script_chapterController 
    11 action.success.PILOTMaterial = traverse_to:string:script_chapterController 
    12 action.success.ExerciseMaterial = traverse_to:string:script_chapterController 
    13 action.success.LessonPlan.lessonplan_form_submit:redirect_to_action:string:metadata 
    14 action.success.SchoolProjectMaterial.schoolproject_form_submit = redirect_to_action:string:metadata 
    15  
     9action.success..save_and_move = traverse_to:string:metadata 
    1610action.failure = traverse_to_action:string:edit 
  • trunk/skins/lemill/exercisematerial_view.pt

    r2012 r3045  
    33    metal:use-macro="here/main_template/macros/master" 
    44    i18n:domain="lemill"> 
    5  
    65    <body> 
    7  
    86        <metal:main fill-slot="main"> 
    97            <tal:macro metal:define-macro="body"> 
    10                 <tal:block tal:define="visibleFields python:context.getTemplate('exercisepage').get('fields'); 
    11                                        fields python:here.Schema().viewableFields(here)"> 
    12                     <tal:fields repeat="field fields"> 
    13                         <tal:cond condition="python:field.getName() in visibleFields and field.getName() not in ['title']"> 
    14                             <metal:fieldMacro use-macro="python:here.widget(field.getName(), mode='view')" /> 
    15                         </tal:cond> 
    16                     </tal:fields> 
    17                 </tal:block> 
     8                <metal:fieldMacro use-macro="python:here.widget('bodyText', mode='view')" /> 
    189            </tal:macro> 
    1910        </metal:main> 
    20  
    2111    </body> 
    2212</html> 
  • trunk/skins/lemill/feedback_view.pt

    r3042 r3045  
    11<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" 
    2       lang="en" 
    3       metal:use-macro="here/main_template/macros/master" 
    4       i18n:domain="lemill"> 
    5 <body> 
     2    lang="en" 
     3    metal:use-macro="here/main_template/macros/master" 
     4    i18n:domain="lemill"> 
     5    <body> 
     6        <metal:main fill-slot="main"> 
     7            <tal:macro metal:define-macro="body"> 
     8            <pre tal:content="here/checkExercise"> 
     9                Exercise results here 
     10            </pre> 
     11            <p><a i18n:translate="back_to_resource_link" href="" tal:attributes="href string:${here_url}/view">Return to exercise</a></p> 
     12            </tal:macro> 
     13        </metal:main> 
     14    </body> 
     15</html> 
    616 
    7 <metal:main fill-slot="main"> 
    8       <tal:defs define="web_feedback python:here.showWebFeedback(request);"> 
    9  
    10         <h1><span tal:replace="title_string | here/title_or_id" /> 
    11             <span i18n:domain="lemill" i18n:translate="text_deleted_heading" tal:condition="here/isDeleted">(DELETED)</span> 
    12         </h1> 
    13          
    14         <p><a i18n:translate="back_to_resource_link" href="" tal:attributes="href string:${here_url}/view">Return to view exercise</a></p> 
    15          
    16         <tal:messages condition="python:web_feedback[1][0]"> 
    17         <tal:message tal:condition="python:web_feedback[1][1]=='mess'"> 
    18             <div class="portalMessage" tal:content="python:web_feedback[1][0]"> 
    19                 This is a placeholder for a message. 
    20             </div> 
    21             </tal:message> 
    22             <tal:warningmessage tal:condition="python:web_feedback[1][1]=='warn'"> 
    23             <div class="portalWarningMessage" tal:content="python:web_feedback[1][0]"> 
    24                 This is a placeholder for a message. 
    25             </div> 
    26             </tal:warningmessage> 
    27         </tal:messages> 
    28  
    29         <tal:message condition="not: python:request.has_key('submit_web_feedback')"> 
    30             <div class="portalWarningMessage" i18n:translate="text_web_feedback_message_not_generated"> 
    31                 Unable to generate the feedback. You have not submitted the form. 
    32             </div> 
    33         </tal:message> 
    34          
    35         <tal:defs tal:condition="python:request.has_key('submit_web_feedback')"  
    36                   define="field python:context.Schema().getField('bodyText'); 
    37                           all_chapters python:web_feedback[0];"> 
    38                            
    39             <div tal:repeat="chapter_data all_chapters"> 
    40                 <tal:defs define="chapter python:chapter_data['question_text']; 
    41                                   type python:chapter_data['chapter_type'];"> 
    42                      
    43                     <div tal:condition="python:type == 'text_block'"> 
    44                         <metal:view_text_block use-macro="here/widget_chapter/macros/view_text_block" /> 
    45                     </div> 
    46                      
    47                     <div tal:condition="python:type == 'media_piece'"> 
    48                         <metal:view_text_block use-macro="here/widget_chapter/macros/view_media_piece" /> 
    49                     </div> 
    50                      
    51                     <div tal:condition="python:type == 'embed_block'"> 
    52                         <metal:view_text_block use-macro="here/widget_chapter/macros/view_embed_block" /> 
    53                     </div> 
    54                      
    55                     <div tal:condition="python:type == 'choice'"> 
    56                         <p tal:content="structure chapter"></p> 
    57                         <tal:defs define="variants python:chapter_data['variants']"> 
    58                             <div tal:repeat="variant variants"> 
    59                                 <span tal:attributes="class python:variant[2]"> 
    60                                     <input type="radio" disabled="disabled" 
    61                                         tal:attributes="checked python:test(variant[2] in ['correct_mc','incorrect_fitb'], 'checked', '')" /> 
    62                                     <span tal:content="python:variant[1]" /> 
    63                                 </span> 
    64                             </div> 
    65                         </tal:defs> 
    66                     </div> 
    67                      
    68                     <div tal:condition="python: type == 'multiple_choices'"> 
    69                         <p tal:content="structure chapter"></p> 
    70                         <tal:defs define="variants python:chapter_data['variants']; 
    71                                           percentage python:chapter_data['percentage'];"> 
    72                             <div tal:repeat="variant variants"> 
    73                                 <span tal:attributes="class python:test(variant[2] in ['correct','checked_correct'], 'correct_mc', 'incorrect_mc')"> 
    74                                     <input type="checkbox" disabled="disabled" 
    75                                         tal:attributes="checked python:test(variant[2] in ['checked_correct','checked_incorrect'], 'checked', '')" /> 
    76                                     <span tal:content="python:variant[1]" /> 
    77                                 </span> 
    78                             </div> 
    79                             <br /> 
    80                             <strong i18n:translate="web_feedback_mc_result">You got <span i18n:name="percentage" tal:content="python:str(percentage)+'% '" />correct!</strong> 
    81                        </tal:defs> 
    82                    </div>     
    83  
    84                     <div tal:condition="python:type == 'fill_in_the_blanks'"> 
    85                         <tal:defs define="gaps_count python:chapter_data['gaps_count']; 
    86                                           correct_gaps python:chapter_data['correct_gaps'];"> 
    87                             <div tal:replace="structure chapter" /> 
    88                             <strong i18n:translate="web_feedback_fitb_result">You got <span i18n:name="right_n" tal:content="correct_gaps" />/<span i18n:name="all_n" tal:content="gaps_count" /> correct!</strong> 
    89                         </tal:defs> 
    90                     </div> 
    91                      
    92                     <div tal:condition="python: type == 'open_ended'"> 
    93                         <div tal:replace="structure chapter" /> 
    94                         <strong><span i18n:translate="">Your answer was</span>:</strong><br /> 
    95                         <span tal:define="answer python:chapter_data['answer']; 
    96                                           answer python:test(answer=='','-',answer)"   
    97                              tal:replace="structure answer" /> 
    98                     </div> 
    99                      
    100                     <div class="visualClear" style="height:20px">&nbsp;</div> 
    101                      
    102                 </tal:defs> 
    103             </div> 
    104             <div tal:replace="structure python:context.getTestSummary()" /> 
    105         </tal:defs> 
    106       </tal:defs> 
    107     </metal:main> 
    108   </body> 
    109 </html> 
  • trunk/skins/lemill/go_back.cpy

    r3014 r3045  
    2222    # XXX disabled mark creation flag 
    2323    ##context.remove_creation_mark() 
    24     redirect_to = context.getFolderWhenPortalFactory().absolute_url() 
     24    redirect_to = context.getSectionFolder(bottom=True).absolute_url() 
    2525    context.lemill_tool.addPortalMessage(gettext('Resource cancelled.')) 
    2626elif context.canDeleteOnCancel(): 
     
    2828        redirect_to = '%s/view' % translation_of.absolute_url()   
    2929    else: 
    30         redirect_to = context.aq_parent.absolute_url() 
     30        redirect_to = context.getSectionFolder(bottom=True).absolute_url() 
    3131    context.aq_parent.manage_delObjects([obj_id]) 
    3232    context.lemill_tool.addPortalMessage(gettext('Resource cancelled.')) 
  • trunk/skins/lemill/header.pt

    r3026 r3045  
    1414    <script type="text/javascript" tal:attributes="src string:$portal_url/master.js"></script> 
    1515    <style type="text/css" media="all" tal:content="string:@import url($portal_url/master.css);">@import "master.css";</style> 
     16    <style type="text/css" media="all" tal:content="string:@import url($portal_url/lemill_grid.css);">@import "lemill_grid.css";</style> 
    1617    <style type="text/css" media="all" tal:content="here/getSectionColors" /> 
    1718    <!-- Internet Explorer CSS Fixes --> 
  • trunk/skins/lemill/lemill_portfolio_view.pt

    r2982 r3045  
    99 
    1010<metal:block fill-slot="column_one_slot"> 
    11 <td id="tb-portal-column-one" width="210" valign="top" tal:define="here here/getMemberFolder | here/getBlog | context; portlet_path here/getPortletPath|nothing"> 
     11<td id="tb-portal-column-one" width="210" valign="top" tal:define="here here/getMemberFolder | here/getBlog | nocall:context; portlet_path here/getPortletPath|nothing"> 
    1212            <tal:hasportlet condition="portlet_path"><metal:portletmacro metal:use-macro="python:path(portlet_path)" /></tal:hasportlet>             
    1313</td> 
     
    3535 
    3636    <tal:no_robots condition="nocall:memberfolder"> 
     37    <div class="documentByLine"><a href="" i18n:translate="byline_link_to_back" tal:attributes="href memberfolder_url">Back to <span tal:replace="memberfolder/getNicename" i18n:name="name"/>'s page</a></div> 
    3738 
    3839    <div class="tagcloud" tal:condition="not:has_args" > 
  • trunk/skins/lemill/macros_messages.pt

    r3042 r3045  
    1212<metal:draft define-macro="draft"><div class="portalMessage" i18n:domain="lemill" i18n:translate="text_draft_status">This learning resource is in draft status.</div></metal:draft> 
    1313 
    14 <metal:old_version define-macro="old_version"><div class="portalMessage" i18n:domain="lemill" i18n:translate="text_old_history_version">You are viewing old version from <span i18n:name="thetime" tal:define="old_version_time python: here.getTimeForOldHistory(showOldVersionMessage)"  tal:content="python:DateTime(old_version_time).strftime('%d.%m.%Y %H:%M:%S')">Fri Oct 27 04:49:26 2006</span>.</div> 
     14<metal:old_version define-macro="old_version"><div class="portalMessage" i18n:domain="lemill" i18n:translate="text_old_history_version">You are viewing old version from <span i18n:name="thetime" tal:define="old_version_time python: here.getTimeForOldHistory(context.REQUEST.get('version'))"  tal:content="python:DateTime(old_version_time).strftime('%d.%m.%Y %H:%M:%S')">Fri Oct 27 04:49:26 2006</span>.</div> 
    1515</metal:old_version> 
    1616 
  • trunk/skins/lemill/main_template.pt

    r3042 r3045  
    88  <head><metal:block metal:use-macro="here/header/macros/html_header" /><metal:block metal:define-slot="css_slot" /><metal:block metal:define-slot="javascript_head_slot" /><metal:block metal:define-slot="head_slot" /></head> 
    99  <body dir="ltr" tal:attributes="class here/getSectionFromURL;" metal:define-slot="bodyslot"><metal:topslot metal:define-slot="topslot"> 
    10 <table metal:define-macro="personal_toolbar" summary="Personal toolbar" width="760" border="0" align="center" cellpadding="0" cellspacing="0"> 
    11     <tr> 
    12         <td align="left"><tal:langs repeat="lang context/lemill_tool/buildLanguageLinks"><a href="" tal:attributes="href python:lang[1]"  
    13                 tal:content="python:lang[0]" rel="nofollow"/><tal:block condition="not:repeat/lang/end"> | </tal:block></tal:langs></td> 
    14         <td align="right"> <tal:not_readonly tal:condition="not:site_properties/readonly_mode"> 
    15             <ul tal:condition="not:isAnon" id="tb-portal-personaltools">     
    16                 <li class="portalUser">  
    17                     <a class="visualCaseSensitive" 
    18                         tal:content="memberid" 
    19                         tal:attributes="href memberfolder/absolute_url | portal/community/my_page" 
    20                         href="my_page"> 
    21                          PLACEHOLDER 
    22                     </a> 
    23                 </li> 
    24                 <li><a tal:attributes="href string:${portal_url}/logout" i18n:translate="">Log out</a></li> 
    25             </ul><ul tal:condition="isAnon" id="tb-portal-personaltools">     
    26                 <li><a tal:attributes="href string:${portal_url}/login_form" i18n:translate="">Log in</a></li> 
    27                 <li><a href="" tal:attributes="href string:${portal_url}/join_form" i18n:translate="">Join</a></li> 
    28             </ul> 
    29         </tal:not_readonly></td> 
    30     </tr> 
    31     <tr tal:condition="site_properties/readonly_mode" style="background-color:#FEF49C; padding:0.5em"> 
    32       <td align="center" colspan="2"> 
    33         <b i18n:translate="readonly_mode_notification">We are updating LeMill and the service is in read-only mode. You can browse LeMill, but not log in or edit content. The service will be back in few hours. Apologies for the inconvenience.</b>  
    34       </td> 
    35 </tr></table> 
     10<metal:toolbar define-macro="personal_toolbar"><div class="languages grid"> 
     11     <tal:langs repeat="lang context/lemill_tool/buildLanguageLinks"><a href="" tal:attributes="href python:lang[1]"  
     12                tal:content="python:lang[0]" rel="nofollow"/><tal:block condition="not:repeat/lang/end"> | </tal:block></tal:langs> 
     13</div> 
     14<div class="login grid" tal:condition="not:site_properties/readonly_mode"> 
     15    <ul tal:condition="not:isAnon" id="tb-portal-personaltools">     
     16        <li class="portalUser">  
     17            <a class="visualCaseSensitive" 
     18                tal:content="memberid" 
     19                tal:attributes="href memberfolder/absolute_url | portal/community/my_page" 
     20                href="my_page"> 
     21                    PLACEHOLDER 
     22            </a> 
     23        </li> 
     24        <li><a tal:attributes="href string:${portal_url}/logout" i18n:translate="">Log out</a></li> 
     25    </ul><ul tal:condition="isAnon" id="tb-portal-personaltools">     
     26        <li><a tal:attributes="href string:${portal_url}/login_form" i18n:translate="">Log in</a></li> 
     27        <li><a href="" tal:attributes="href string:${portal_url}/join_form" i18n:translate="">Join</a></li> 
     28    </ul> 
     29</div> 
     30<div class="read_only_mode grid" tal:condition="site_properties/readonly_mode" style="background-color:#FEF49C; padding:0.5em"> 
     31    <b i18n:translate="readonly_mode_notification">We are updating LeMill and the service is in read-only mode. You can browse LeMill, but not log in or edit content. The service will be back in few hours. Apologies for the inconvenience.</b>  
     32</div></metal:toolbar> 
    3633<table id="tb-portal-header" summary="Header" width="760" border="0" align="center" cellpadding="0" cellspacing="0"> 
    3734    <tr tal:define="section_name here/getSectionFromURL; georgian python:here.portal_languages.getLanguageCookie()=='ka'"> 
  • trunk/skins/lemill/master.css

    r3040 r3045  
    5252 
    5353h1 { 
    54     font-size: 160%; 
     54    font-size: 21px; 
    5555    margin-top: 10px; 
    5656} 
    57  
    58 h2{ 
    59     font-size: 120%; 
    60     border-bottom: none; 
    61     margin-top: 25px; 
    62     margin-bottom: 5px; 
    63     font-weight: bold; 
    64 } 
    65  
    66 h3 { 
    67     font-size: 100%; 
    68     border-bottom: none; 
    69     font-weight: bold; 
    70 } 
    71  
    72 h4 { 
    73     font-size: 110%; 
    74     border-bottom: none; 
    75     font-weight: bold; 
    76 } 
    77  
    78 h5 { 
    79     font-size: 100%; 
    80     border-bottom: none; 
    81     font-weight: bold; 
    82 } 
    83  
    84 h6 { 
    85     font-size: 85%; 
    86     border-bottom: none; 
    87     font-weight: bold; 
    88 } 
    89  
    9057 
    9158 
     
    135102dd { 
    136103    line-height: 1.5em; 
    137     margin-bottom: 1em; 
    138104} 
    139105 
     
    145111    width: auto; 
    146112} 
     113 
     114fieldset.deleted { 
     115    border: 1px solid #cc0000; 
     116    color: #cc0000; 
     117    text-decoration: line-through; 
     118} 
     119 
     120.right { 
     121    float: right; 
     122} 
     123 
    147124legend { 
    148125    background: white; 
    149126    padding: 0.5em; 
    150     font-size: 90%; 
     127    font-size: 11px; 
     128} 
     129 
     130div.handle { 
     131    height:22px; 
     132    margin-left: 0em; 
     133    margin-right: 0em; 
     134    cursor: move; 
     135    padding: 2px; 
     136    padding-right: 2em;     
     137    background:#eeeeff; 
     138} 
     139 
     140span.handle { 
     141    background:#eeeeff url('images/pattern.gif') no-repeat left top; 
     142    border:1px solid #8cacbb; 
     143    cursor: move; 
     144    font-size:11px; 
     145    color:transparent; 
     146    display: block; 
     147}     
     148 
     149span.handle_large { 
     150    width: 500px; 
     151    height:18px; 
     152    position:absolute; 
     153    right:0px; 
     154    top:0px;     
     155} 
     156 
     157 
     158 
     159span.handle_small { 
     160    position:relative; 
     161    left:-100px; 
     162    top:-0.7em; 
     163    width: 100px; 
     164    height:10px; 
     165} 
     166.sortable_row { 
     167    position:relative; 
    151168} 
    152169 
     
    161178 
    162179textarea { 
    163     font: 100% Monaco, "Courier New", Courier, monospace; 
     180    font: Monaco, "Courier New", Courier, monospace; 
    164181    border: 1px solid #8cacbb; 
    165182    color: black; 
     
    169186 
    170187input { 
    171     font-family: "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif; 
    172188    visibility: visible; 
    173189    border: 1px solid #8cacbb; 
     
    176192    background: White url(input_background.gif) repeat-x; 
    177193} 
    178 button { 
    179     font-family: "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif; 
     194 
     195 
     196button, input:submit { 
    180197    visibility: visible; 
    181198    border: 1px solid #8cacbb; 
     
    185202    padding: 1px; 
    186203    cursor: pointer; 
    187     font-size: 85%; 
    188     text-transform: none;     
    189 } 
     204    font-size: 11px; 
     205    text-transform: none; 
     206    text-decoration: none;    
     207} 
     208 
     209 
    190210select { 
    191211    border: 1px solid #8cacbb; 
     
    215235} 
    216236q { 
    217     font-family: Baskerville, Georgia, serif; 
    218237    font-style: italic; 
    219     font-size: 120%; 
    220238} 
    221239blockquote { 
     
    227245code, tt { 
    228246    font-family: Monaco, "Courier New", Courier, monospace; 
    229     font-size: 120%; 
    230247    color: black; 
    231248    background-color: #dee7ec; 
     
    234251pre { 
    235252    font-family: Monaco, "Courier New", Courier, monospace; 
    236     font-size: 100%; 
    237253    padding: 1em; 
    238254    border: 1px solid #8cacbb; 
    239255    color: black; 
    240256    background-color: #dee7ec; 
    241     overflow: auto; 
    242257    max-width: 520px; 
    243 } 
     258    overflow-x: auto; /* Use horizontal scroller if needed; for Firefox 2, not needed in Firefox 3 */ 
     259    white-space: pre-wrap; /* css-3 */ 
     260    white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ 
     261    white-space: -pre-wrap; /* Opera 4-6 */ 
     262    white-space: -o-pre-wrap; /* Opera 7 */ /* 
     263    width: 99%; */ 
     264    word-wrap: break-word; /* Internet Explorer 5.5+ */ 
     265    } 
    244266 
    245267ins { 
     
    253275} 
    254276 
     277/* Form required field helpers */  
     278 
     279div.form_submit, div.required_notes { 
     280    display: inline-block; 
     281} 
     282 
     283ul#req_list { 
     284    list-style: none;  
     285    list-style-image: url(images/required.gif); 
     286} 
     287 
     288li.req_link { 
     289    color: red; 
     290    text-decoration: underline; 
     291    cursor: pointer; 
     292}     
    255293 
    256294 
     
    305343.discreet { 
    306344    color: #76797c; 
    307     font-size: 85%; 
     345    font-size: 11px; 
    308346    font-weight: normal; 
    309347} 
     
    317355    border: 1px solid #ffa500; 
    318356    color: black; 
    319     font-size: 85%; 
     357    font-size: 11px; 
    320358    font-weight: bold; 
    321359    margin: 1em 0em; 
     
    348386 
    349387.formHelp { 
    350     font-size: 90%; 
     388    font-size: 11px; 
    351389    color: #76797c; 
    352390    margin: 0 0 0.2em 0; 
     
    362400} 
    363401 
     402 
    364403.error { 
    365404    /* Class for error indication in forms */ 
     405    background-color: #ffce7b; 
     406} 
     407 
     408 
     409/*.error { 
    366410    background-color: #ffce7b; 
    367411    border: 1px solid #ffa500; 
     
    370414    width: 95%; 
    371415} 
    372  
    373 .error .fieldRequired { 
    374    color: #ffce7b; 
    375 } 
     416*/ 
    376417 
    377418/* Archetypes ? */  
     
    379420#archetypes-fieldname-title input { 
    380421font-family:"Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif; 
    381 font-size:160%; 
     422font-size:21px; 
    382423font-weight:normal; 
    383424width:99%; 
     
    403444    background: #dee7ec url(images/linkOpaque.gif) 9px 1px no-repeat; 
    404445    cursor: pointer; 
    405     font-size: 85%; 
     446    font-size: 11px; 
    406447    padding: 1px 1px 1px 15px; 
    407448    text-transform: none; 
     
    410451.context, 
    411452.documentEditable * .context { 
    412     background: transparent url(images/linkTransparent.gif) 9px 1px no-repeat; 
     453    background: white; 
    413454    cursor: pointer; 
    414     font-size: 85%; 
     455    font-size: 11px; 
    415456    padding: 1px 1px 1px 15px; 
    416457    text-transform: none; 
    417458    overflow: visible; 
    418 } 
     459    text-decoration: none; 
     460} 
     461 
     462.context:disabled { 
     463    color: gray; 
     464    cursor: default; 
     465    border-color: gray;     
     466} 
     467 
     468 
    419469.destructive, 
    420470.documentEditable * .destructive { 
     
    422472    border: 1px solid #ffa500; 
    423473    cursor: pointer; 
    424     font-size: 85%; 
     474    font-size: 11px; 
    425475    padding: 1px 1px 1px 15px; 
    426476    text-transform: none; 
     
    431481 
    432482.LSRes { 
    433     font-family:  "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif; 
    434483    visibility: visible; 
    435484    color: black; 
     
    498547    top: 0; 
    499548    white-space: normal; 
    500     font-family:  "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif; 
    501549    visibility: visible; 
    502550    text-align: left; 
     
    580628    line-height: 80px; 
    581629    list-style-type: none; 
    582     font-family: "Trebuchet MS", Geneva, Arial, Helvetica, sans-serif; 
    583     font-weight: bold; 
    584     font-size: 16px; 
    585630    color: #999999; 
    586631} 
     
    650695 
    651696.documentByLine { 
    652     font-size: 85%; 
    653     font-weight: normal; 
     697    font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 
     698    font-size: 11px; 
    654699    color: #76797c; 
    655700    margin: 0em 0em 1.5em 0em; 
     
    671716    width: 196px; 
    672717    color: #ffffff; 
    673     font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; 
    674718    font-weight: bold; 
    675     font-size: 13px; 
    676719} 
    677720 
     
    726769} 
    727770 
    728 .piece-edit-box { 
    729     min-width: 500px; 
    730     min-height: 500px;  
    731     text-align: left; 
    732     padding: 18px 24px; 
    733     margin-top: 4px; 
    734     background-repeat: no-repeat;  
    735     background-position: top left; 
    736 } 
    737  
    738 .piece-edit-box #pres-text { 
    739     font-size: 110%; 
    740 } 
    741771 
    742772.one-piece textarea { 
     
    751781#slideCounterDiv { 
    752782    display: inline; 
    753     font-size: 130%; 
    754783} 
    755784 
     
    784813ul.expanded a.browse_title_d{display:none} 
    785814 
    786 span.size1 { font-size: 80%; line-height: 80%; } 
    787 span.size2 { font-size: 90%; line-height: 90%; } 
    788 span.size3 { font-size: 100%; line-height: 100%; } 
    789 span.size4 { font-size: 110%; line-height: 110%; } 
    790 span.size5 { font-size: 120%; line-height: 120%; } 
    791 span.size6 { font-size: 130%; line-height: 130%; } 
    792 span.size7 { font-size: 140%; line-height: 140%; } 
    793 span.size8 { font-size: 150%; line-height: 150%; } 
    794815 
    795816 
     
    848869    display: block; 
    849870    padding: .3em 5px; 
    850     font-size: .9em; 
     871    font-size: 11px; 
    851872    line-height: 1.1; 
    852873    w\idth: 164px;  /* Moz, IE6 */ 
     
    903924 
    904925#archetypes-fieldname-description textarea { 
    905     font: 100% Monaco, "Courier New", Courier, monospace; 
     926    font-family: Monaco, "Courier New", Courier, monospace; 
    906927    font-weight: normal; 
    907928} 
     
    920941} 
    921942 
    922 .deleteButtonPosition { 
     943.extraLeftMargin { 
    923944    margin-left:24px; 
    924945} 
     
    9811002    } 
    9821003     
    983 .learning_story_title { 
    984     font-family: "Trebuchet MS", Geneva, Arial, Helvetica, sans-serif; 
    985     font-weight: normal; 
    986     font-size: 18px; 
    987     } 
    9881004 
    9891005#sidebar { 
     
    9921008    color: inherit; 
    9931009    background-color: #77bb22; 
    994     font-family: "Trebuchet MS", Geneva, Arial, Helvetica, sans-serif; 
    995     font-weight: normal; 
    996     font-size: 18px; 
    9971010} 
    9981011 
    9991012.new_story { 
    10001013    margin-top: 2em; 
    1001     font-family: "Trebuchet MS", Geneva, Arial, Helvetica, sans-serif; 
    1002     font-weight: normal; 
    1003     font-size: 18px; 
    10041014    } 
    10051015 
     
    10281038    height: 84px; 
    10291039    background-color: #eeeeee; 
    1030     font-family: "Trebuchet MS", Geneva, Arial, Helvetica, sans-serif; 
    1031     font-weight: normal; 
    1032     font-size: 18px; 
    10331040    } 
    10341041 
     
    10561063    height: 24px; 
    10571064    color: #ffffff; 
    1058     font-family: "Trebuchet MS", Geneva, Arial, Helvetica, sans-serif; 
    1059     font-weight: normal; 
    1060     font-size: 16px; 
    10611065    text-align: center; 
    10621066    } 
     
    11191123} 
    11201124 
     1125 
     1126/* Piece chooser  
     1127*/ 
     1128 
     1129div.tab { 
     1130    display:inline-block; 
     1131    border: 1px solid #bbbbbb; 
     1132    padding: 4px 8px 2px 8px; 
     1133    margin: 0.5em; 
     1134    margin-bottom: 0; 
     1135    color: #2299bb; 
     1136    background-color: white; 
     1137    cursor: pointer;     
     1138} 
     1139 
     1140div.selected_tab { 
     1141    border-bottom-style: none; 
     1142    font-weight: bold; 
     1143    color: black; 
     1144    cursor: default; 
     1145} 
     1146 
     1147div.tab_sheet { 
     1148    display:none; 
     1149    background-color: white; 
     1150    margin-top: 0; 
     1151    border: 1px solid #bbbbbb; 
     1152    min-height:200px; 
     1153    padding-left: 2em; 
     1154    padding-top: 2em; 
     1155} 
     1156div.selected_tab_sheet { 
     1157    display:block; 
     1158} 
     1159 
    11211160.piece_chooser dl { 
    1122     float:left; 
     1161    display:inline-block; 
    11231162    width:130px; 
    11241163    height:155px; 
     
    11391178.piece_chooser dd { 
    11401179    padding: .2em; 
    1141     font-size: 80%; 
     1180    font-size: 11px; 
    11421181    margin: 0; 
    11431182    height:55px; 
    11441183    overflow:hidden; 
    11451184    text-align:center; 
     1185    margin-bottom:0em; 
     1186    line-height: 1em; 
    11461187} 
    11471188 
    11481189.contrast span, b.contrast { 
    11491190    color:white; 
    1150     background:gray; 
     1191    background-color:gray; 
    11511192    padding: 5px 
    11521193} 
     1194 
     1195#piece-edit-box { 
     1196    text-align: left; 
     1197    padding-top: 18px; 
     1198    background-repeat: no-repeat;  
     1199    background-position: top left; 
     1200} 
     1201 
     1202#piece-edit-box #pres-text { 
     1203} 
     1204/** 
     1205*/ 
    11531206 
    11541207.frontpage-blog { 
     
    11561209    padding: 10px 16px; 
    11571210    background-color: #eeeeee; 
    1158     font-family: "Trebuchet MS", Geneva, Arial, Helvetica, sans-serif; 
    1159     font-weight: normal; 
    1160     font-size: 12px; 
    11611211} 
    11621212 
     
    11661216    height: 50px; 
    11671217    background-color: #eeeeee; 
    1168     font-family: "Trebuchet MS", Geneva, Arial, Helvetica, sans-serif; 
    1169     font-weight: normal; 
    1170     font-size: 12px; 
    11711218} 
    11721219/* Exercise web-based feedback */ 
     
    12081255    vertical-align: top; 
    12091256    margin: 1em 0em; 
    1210     font-size: 94%; 
    12111257    clear: both; 
    12121258} 
     
    12771323/* ListBox */ 
    12781324.defaultSkin .mceListBox, .defaultSkin .mceListBox a {display:block} 
    1279 .defaultSkin .mceListBox .mceText {padding-left:4px; width:70px; text-align:left; border:1px solid #CCC; border-right:0; background:#FFF; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden} 
    1280 .defaultSkin .mceListBox .mceOpen {width:9px; height:20px; background:url(../../img/icons.gif) -741px 0; margin-right:2px; border:1px solid #CCC;} 
     1325.defaultSkin .mceListBox .mceText {padding-left:4px; width:70px; text-align:left; border:1px solid #CCC; border-right:0; background:#FFF; font-family:Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden} 
     1326.defaultSkin .mceListBox .mceOpen {width:9px; height:20px; background:url(images/icons.gif) -741px 0; margin-right:2px; border:1px solid #CCC;} 
    12811327.defaultSkin table.mceListBoxEnabled:hover .mceText, .defaultSkin .mceListBoxHover .mceText, .defaultSkin .mceListBoxSelected .mceText {border:1px solid #A2ABC0; border-right:0; background:#FFF} 
    12821328.defaultSkin table.mceListBoxEnabled:hover .mceOpen, .defaultSkin .mceListBoxHover .mceOpen, .defaultSkin .mceListBoxSelected .mceOpen {background-color:#FFF; border:1px solid #A2ABC0} 
     
    12851331.defaultSkin .mceOldBoxModel .mceListBox .mceText {height:22px} 
    12861332.defaultSkin .mceOldBoxModel .mceListBox .mceOpen {width:11px; height:22px;} 
    1287 .defaultSkin select.mceNativeListBox {font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:7pt; background:#F0F0EE; border:1px solid gray; margin-right:2px;} 
     1333.defaultSkin select.mceNativeListBox {font-family:Verdana,Arial; font-size:11px; background:#F0F0EE; border:1px solid gray; margin-right:2px;} 
    12881334 
    12891335/* Menu */ 
     
    12951341.defaultSkin .mceMenu td {height:20px} 
    12961342.defaultSkin .mceMenu a {position:relative;padding:3px 0 4px 0} 
    1297 .defaultSkin .mceMenu .mceText {position:relative; display:block; font-family:Tahoma,Verdana,Arial,Helvetica; color:#000; cursor:default; margin:0; padding:0 25px 0 25px; display:block} 
     1343.defaultSkin .mceMenu .mceText {position:relative; display:block; font-family:Verdana,Arial,Helvetica; color:#000; cursor:default; margin:0; padding:0 25px 0 25px; display:block} 
    12981344.defaultSkin .mceMenu span.mceText, .defaultSkin .mceMenu .mcePreview {font-size:11px} 
    12991345.defaultSkin .mceMenu pre.mceText {font-family:Monospace} 
     
    13111357 
    13121358.defaultSkin span.mceText {color: black; text-decoration: none; font-size: 13px; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;} 
    1313 .defaultSkin .preview_heading span.mceText {font-size: 120%;font-weight: bold;} 
     1359.defaultSkin .preview_heading span.mceText {font-family: "Lucida Grande", Verdana, Lucida, Helvetica, Arial, sans-serif;font-size: 21px;font-weight: bold;} 
    13141360.defaultSkin .preview_code span.mceText {font-family: Monaco, "Courier New", Courier, monospace;font-size: 100%; color: black; background-color: #dee7ec;} 
    13151361 
  • trunk/skins/lemill/master.js

    r3040 r3045  
    3939*/ 
    4040    $(".sortable").sortable({  
    41         handle : '.handle', items : '.choice_row',  
     41        handle : '.handle', items : '.sortable_row',  
    4242        update : function(event, ui) { 
    4343            /* Write new indexes for every row  
    4444            */ 
    45             siblings=ui.item.parent().children('.choice_row'); 
     45            siblings=ui.item.parent().children('.sortable_row'); 
    4646            siblings.each(function(index){ 
    4747                $(this).find('.orderkeeper:first').attr('value',index); 
     
    4949        } 
    5050        }); 
     51 
     52/* 3. Disable submit if the form has problems 
     53*/ 
     54    $('.requiredfield').each(function() {watchRequiredField(this);}); 
    5155 
    5256}); 
     
    5862        row.find('a.collection_item:first').addClass('deletedText').click(collectionRestore); 
    5963        row.find('input.deletion_marker').attr('value',1) 
     64        row.find('img.handle').hide(); 
    6065        $(img).hide(); 
    6166} 
    6267function collectionRestore(){     
    6368        row=$(this).parents('tr:first') 
    64         row.find('img.delete_button:first').show(); 
    65         row.find('input.deletion_marker').attr('value',0) 
     69        row.find('img.delete_button').show(); 
     70        row.find('img.handle').show(); 
     71        row.find('input.deletion_marker').attr('value',0)         
    6672        $(this).removeClass('deletedText'); 
    6773        $(this).unbind('click', collectionRestore); 
     
    7884} 
    7985 
    80 function questionWizard(mode, index) { 
    81     switch(mode) { 
    82     case 0: 
    83         $('#question_type_selection_'+index).toggle('slide', {direction:'left'}); 
    84         $('#multiple_choice_edit_'+index).toggle('slide', {direction:'right'}); 
    85         break;         
    86     case 1: 
    87         $('#question_type_selection_'+index).toggle('slide', {direction:'left'}); 
    88         $('#fill_blanks_edit_'+index).toggle('slide', {direction:'right'}); 
    89         break; 
    90     case 2: 
    91         $('#question_type_selection_'+index).toggle('slide', {direction:'left'}); 
    92         $('#open_ended_edit_'+index).toggle('slide', {direction:'right'}); 
    93         break; 
    94     case 3: 
    95         $('#question_type_selection_'+index).toggle('slide', {direction:'left'}); 
    96         $('#hot_potatoes_edit_'+index).toggle('slide', {direction:'right'}); 
    97         break; 
    98     case 4: 
    99         previewMultipleChoice(index); 
    100         $('#multiple_choice_edit_'+index).toggle('slide', {direction:'left'}); 
    101         $('#preview_'+index).toggle('slide', {direction:'right'}); 
    102         break;         
    103     case 5: 
    104         $('#fill_blanks_edit_'+index).toggle('slide', {direction:'left'}); 
    105         $('#preview_'+index).toggle('slide', {direction:'right'}); 
    106         break;         
    107     case 6: 
    108         $('#open_ended_edit_'+index).toggle('slide', {direction:'left'}); 
    109         $('#preview_'+index).toggle('slide', {direction:'right'}); 
    110         break;         
    111     }; 
    112 } 
    113  
     86/* Validation helpers: 
     87    required fields can call these methods and make the edit page notify about 
     88    missing values and disable save-button.  
     89    */  
     90 
     91function quickWatchRequiredField(here){ 
     92    h=$(here); 
     93    if (h.val().length < 3){ 
     94        watchRequiredField(here); 
     95        } 
     96}     
     97function watchRequiredField(here){ 
     98    here=$(here); 
     99    target=$('li#required_'+here.attr('id')); 
     100    // replace with the actual label of the field instead of id 
     101    label=here.closest('div.field').find('label'); 
     102    target.find('span.hint_label').text(label.text()); 
     103    if (here.val()!=''){ 
     104        target.hide(); 
     105    } else { 
     106        target.show(); 
     107    } 
     108    checkSubmitEnabled(); 
     109} 
     110function checkSubmitEnabled(){ 
     111    disabled=''; 
     112    lis=$('ul#req_list').find('li').each(function(i,li) { 
     113    if ($(li).css('display')!='none') {     
     114        disabled='disabled'; 
     115        } 
     116    }); 
     117    $('input.savebutton').attr('disabled',disabled);  
     118} 
     119function focusToField(field_id){ 
     120    $('#'+field_id).focus();     
     121} 
     122function showFieldHint(here, field_id){ 
     123    $(here).addClass('error'); 
     124    $('#'+field_id).closest('div.field').find('label').addClass('error')     
     125} 
     126function hideFieldHint(here, field_id){ 
     127    $(here).removeClass('error'); 
     128    $('#'+field_id).closest('div.field').find('label').removeClass('error'); 
     129} 
     130 
     131 
     132 
     133 
     134/* Exercise template 
     135*/ 
     136 
     137function questionSelection(){ 
     138    qtype=$('input#chapter_type').val() 
     139    $('#'+qtype+'_edit').hide('slide', {direction:'right'}) 
     140    $('#question_selection').show('slide', {direction:'left'})     
     141} 
     142 
     143function editQuestion(qtype){ 
     144    $('input#chapter_type').val(qtype); 
     145    $('#question_selection').hide('slide', {direction:'left'}); 
     146    $('#multiple_choice_edit').show('slide', {direction:'right'}); 
     147} 
     148 
     149function previewQuestion(){ 
     150    qtype=$('input#chapter_type').val() 
     151    $('#'+qtype+'_edit').toggle('slide', {direction:'left'}) 
     152    $('#preview').toggle('slide', {direction:'right'}) 
     153} 
     154 
     155 
     156/* Add a new answer choice box when creating a multiple choice exercise */ 
     157function addAnswer(here) { 
     158    here=$(here).parents('div.inner_edit:first'); 
     159    rows=here.find('.sortable_row'); 
     160    row_index=rows.size(); 
     161    last_row=rows.last(); 
     162    // cloning will remove the checked radio button (2 choices become 4) 
     163    is_correct= (last_row.find('input:radio:checked').val()=='1') ? true : false; 
     164    new_row=last_row.clone(); 
     165    //new_row.hide(); 
     166    last_row.after(new_row); 
     167    new_row.find('input:radio').attr({ 
     168        name:'answer_'+row_index+'_correct',  
     169        id:'answer_'+row_index+'_correct' 
     170    }); 
     171    new_row.find('textarea').attr({ 
     172        name:'choice_answer_'+row_index,  
     173        id:'choice_answer_'+row_index 
     174    }); 
     175    new_row.find('input.orderkeeper').attr({ 
     176        name:'choice_order_'+row_index,  
     177        id:'choice_order_'+row_index, 
     178        value:row_index 
     179    }); 
     180    check_me= (is_correct) ? last_row.find('input:radio:first') : last_row.find('input:radio:last'); 
     181    check_me.attr('checked', '1'); 
     182    $('input#choice_answer_count').val(row_index); 
     183    //new_row.fadeIn();     
     184    outer_div=here.parents('.chapter_edit'); 
     185    tableheight=here.find('table:first').outerHeight() 
     186    if (tableheight>outer_div.height()){ 
     187        outer_div.height(tableheight+10); 
     188    } 
     189} 
     190 
     191 
     192function questionnairePollingCheck(obj) { 
     193    if (obj.checked) { 
     194        $('img#cross').attr('src','images/tick.gif'); 
     195    } else { 
     196        $('img#cross').attr('src','images/cross.gif'); 
     197    } 
     198} 
    114199 
    115200function previewMultipleChoice(index) { 
    116     question='Vai niin.'; 
    117     choices=['Hah','hei','huh']; 
     201    here=$('div#preview_'+index); 
     202    chapter=here.parents('div.chapter_edit:first'); 
     203    question=chapter.find('#chapter_'+index+'_question').val(); 
     204    choices=[]; 
     205    chapter.find('textarea.multiple_choice_answer').each(function(){ 
     206        val=$(this).val(); 
     207        if (val) { 
     208            row='<p><input type="radio" name="preview" value="" />'+$(this).val()+'</p>'; 
     209            choices.push(row); 
     210            } 
     211    }); 
    118212    s='<table><tr><td valign="middle" align="left"><p>'; 
    119213    s+=question; 
    120214    s+='</p></td></tr><tr><td>'; 
    121     for (choice in choices) { 
    122         s+=choice; 
     215    for (i in choices) { 
     216        s+=choices[i]; 
    123217        }        
    124218    s+='</td></tr></table>'; 
    125     $('#preview_'+index+' > div.innermost').html(s); 
    126     clicked='javascript:questionWizard(4, '+index+')'; 
    127     $('#preview_'+index+' > a.backlink').click(function(){questionWizard(4, index)}); 
    128     } 
    129  
    130 /*      tal:define="question python:chapter[0]; answers python:context.getAllAnswers(chapter)"> 
    131 *        <tr> 
    132 *        <td valign="middle" align="left"> 
    133 *            <p tal:content="structure question">PLACEHOLDER</p> 
    134 *        </td> 
    135 *        </tr> 
    136 *        <tr> 
    137 *        <td> 
    138 *            <span tal:repeat="answer answers" tal:omit-tag=""> 
    139 *            <p> 
    140 *                <input type="radio" value="" 
    141 *                tal:attributes="id python:'exercise_%s_checkbox_%s' % (index,answer[0]); 
    142 *                name python:'exercise_%s_checkbox' % (index); 
    143 *                value python:answer[0]" /> 
    144 *                <input type="hidden" value="" 
    145 *                tal:attributes="id python:'exercise_%s_hiddencheck_%s' % (index, answer[0]); 
    146 *                name python:'exercise_%s_hiddencheck_%s' % (index, answer[0]); 
    147 *                value repeat/answer/index" /> 
    148 *                <span tal:replace="structure python:answer[1]"></span> 
    149 *            </p> 
    150 *            </span> 
    151 *        </td>                     
    152 *        </tr> 
    153 *    </table> 
    154 */ 
     219    here.find('div.innermost').html(s); 
     220    backlink=here.find('a.backlink'); 
     221    backlink.unbind('click'); 
     222    backlink.click(function(event){ 
     223        event.preventDefault(); 
     224        event.stopImmediatePropagation() 
     225        questionWizard(4, index); 
     226    }); 
     227} 
     228 
     229function previewFillTheBlanks() { 
     230    here=$('div#preview'); 
     231    textarea=$('textarea#fill_blanks'); 
     232    inputfield= '<input type="text" value="" name="preview_text" id="preview_text" />'; 
     233    var regex_doubles=/(\}\{)/ig; 
     234    var regex_linebreaks=/\n/g; 
     235    var regex_braces=/(\{.*?\}+)/ig; 
     236    textarea_text=textarea.val(); 
     237    textarea_text=textarea_text.replace(regex_linebreaks, '<br/>'); 
     238    textarea_text=textarea_text.replace(regex_doubles, '/'); 
     239    textarea_text=textarea_text.replace(regex_braces, inputfield); 
     240    s='<p>'+textarea_text+'</p>'; 
     241    here.find('div.innermost').html(s); 
     242} 
     243 
     244function previewOpenEnded() { 
     245    here=$('div#preview'); 
     246    textarea=$('textarea#open_field'); 
     247    s='<p>'+textarea.val()+'</p>'; 
     248    s+='<textarea cols="40" rows="8"></textarea>' 
     249    here.find('div.innermost').html(s); 
     250} 
     251 
     252 
    155253 
    156254function getLatestBlogPost(lang){ 
     
    162260    $("span#title_from_blog").html(html);                
    163261 }); 
     262} 
     263 
     264function deleteChapter(button){ 
     265    fieldset=$(button).parents('fieldset:first'); 
     266    fieldset.find('input.undeleteButton').show(); 
     267    fieldset.find('div.handle').addClass('deleted'); 
     268    $(button).hide(); 
     269    fieldset.find('input.editChapter').hide(); 
     270    fieldset.find('div.handle').css('background','#ffcccc'); 
     271    fieldset.addClass('deleted'); 
     272    fieldset.find('input.deletionkeeper').val('1'); 
     273} 
     274 
     275function undeleteChapter(button){ 
     276    fieldset=$(button).parents('fieldset:first'); 
     277    fieldset.find('input.editChapter').show(); 
     278    fieldset.find('input.deleteButton').show(); 
     279    fieldset.find('div.handle').css('background','#eeeeff'); 
     280    $(button).hide(); 
     281    fieldset.removeClass('deleted'); 
     282    fieldset.find('input.deletionkeeper').val('0'); 
    164283} 
    165284 
     
    464583 
    465584 
    466 function search_for_pieces(event, index, search_type){ 
    467     $('#piece-search-results-'+index).slideDown(); 
    468     val=$('#piece-search-'+index).attr("value") 
    469     if (!val) return;        
    470     $('#piece-search-results-'+index+' > b').text('Please wait while we process your query'); 
    471     $.ajax({ url: "js_queryForPieces", data:{keyword:val, search_type:search_type}, dataType:'text', success:  
     585/* Piece chooser  
     586*/ 
     587 
     588function clickedTab(tab, sheet_id){ 
     589    this_tab=$(tab); 
     590    selected_tab=$('.selected_tab:first'); 
     591     
     592    if (this_tab.hasClass('selected_tab')) { 
     593        $('div#tab_spacer').slideDown(); 
     594        this_tab.removeClass('selected_tab'); 
     595        tab_sheet=$('.selected_tab_sheet:first');         
     596        tab_sheet.removeClass('selected_tab_sheet'); 
     597    } else if (selected_tab.length==0) { 
     598        $('div#tab_spacer').slideUp(); 
     599        this_tab.addClass('selected_tab'); 
     600        this_tab_sheet=$('div#'+sheet_id); 
     601        this_tab_sheet.addClass('selected_tab_sheet'); 
     602        updateChooser(this_tab_sheet); 
     603    } else { 
     604    this_tab.addClass('selected_tab'); 
     605    selected_tab.removeClass('selected_tab'); 
     606    selected_tab_sheet=$('.selected_tab_sheet:first'); 
     607    selected_tab_sheet.removeClass('selected_tab_sheet'); 
     608    this_tab_sheet=$('div#'+sheet_id); 
     609    this_tab_sheet.addClass('selected_tab_sheet'); 
     610    updateChooser(this_tab_sheet); 
     611    } 
     612     
     613} 
     614 
     615function updateChooser(tab_sheet){ 
     616    id=tab_sheet.attr('id'); 
     617    if (id=='tab_sheet_pieces'){ 
     618        if (tab_sheet.find('.piece_chooser:first').css('display')=='none'){ 
     619            piece_type=$('input#piece_type').val(); 
     620            data={search_type:'image', user:'True', search_type:piece_type}; 
     621            fetchPieces(tab_sheet, data); 
     622        } 
     623    } else if (id=='tab_sheet_collections'){ 
     624        if (tab_sheet.find('.piece_chooser:first').css('display')=='none'){ 
     625            piece_type=$('input#piece_type').val(); 
     626            data={search_type:'image', user:'True', collection:'True', search_type:piece_type}; 
     627            fetchPieces(tab_sheet, data); 
     628        } 
     629    } 
     630 
     631} 
     632 
     633function search_for_pieces(here, event){ 
     634    tab=$(here).parents('.tab_sheet:first'); 
     635    val=$('input#piece-search').val(); 
     636    piece_type=$('input#piece_type').val(); 
     637    if (!val) return; 
     638    data={keyword:val, search_type:piece_type}; 
     639    fetchPieces(tab, data); 
     640} 
     641 
     642function fetchPieces(tab, data){                     
     643    message=tab.find('.message:first'); 
     644    message.show(); 
     645    message.text('Please wait while we process your query'); 
     646    $.ajax({ url: "js_queryForPieces", data:data, dataType:'text', success:  
    472647        function(data){     
    473648            pieces = eval(data); 
    474649            if (pieces.length==0){ 
    475                 $('#piece-search-results-'+index+' > b').text('No results found'); 
     650                message.text('No results found'); 
    476651                return ; 
    477652            }     
    478             div=$('#piece-search-results-'+index+'-body'); 
     653            div=tab.find('.piece_chooser:first'); 
     654            div.html(''); 
    479655            for ( var i=0; i<pieces.length; i++)  
    480656            { 
     
    487663                div.append('<dl><dt><img src="'+thumb+'" /></dt><dd>'+title+'</dd></dl>'); 
    488664                dl=div.find('dl').last(); 
    489                 dl.data({'data':piece, 'index':index}); 
     665                dl.data({'data':piece}); 
    490666                if (pt=='image'){ 
    491667                    dl.click(insert_image); 
     
    497673            } 
    498674            div.slideDown(); 
    499             $('#piece-search-results-'+index+' > b').text('Found '+pieces.length+' media pieces.'); 
    500  
     675            message.text('Found '+pieces.length+' media pieces.'); 
    501676        }}); 
    502677} 
     
    504679function insert_image(){ 
    505680    data=$(this).data()['data']; 
    506     index=$(this).data()['index']; 
    507681    image_url = data[4]; 
    508682    uid = data[0]; 
    509     $('#piece-edit-box-'+index).css('backgroundImage','url('+image_url+')'); 
    510     $('#piece-media-box-'+index).hide(); 
    511     $('#piece-controls-edit-'+index).addClass('contrast'); 
    512     $('#bodyText_'+index).attr('value', uid); 
    513     $('#piece-search-results-'+index).slideUp(); 
     683    $('#piece-edit-box').css('backgroundImage','url('+image_url+')'); 
     684    $('#piece-media-box').hide(); 
     685    $('#piece-edit-controls').addClass('contrast'); 
     686    $('#piece_uid').val(uid); 
     687    $('div#tab_spacer').slideDown(); 
     688    $('.selected_tab:first').removeClass('selected_tab'); 
     689    tab_sheet=$('.selected_tab_sheet:first');         
     690    tab_sheet.find('.message:first').hide(); 
     691    tab_sheet.removeClass('selected_tab_sheet'); 
    514692} 
    515693function insert_coverimage(){ 
    516694    data=$(this).data()['data']; 
    517     index=$(this).data()['index']; 
    518695    cover_url = data[1]; 
    519696    uid = data[0]; 
    520     $('#piece-media-box-'+index).html('<img src="'+cover_url+'"><br/>'); 
    521     $('#bodyText_'+index).attr('value', uid); 
    522     $('#piece-search-results-'+index).slideUp();     
     697    $('#piece-media-box').html('<img src="'+cover_url+'"><br/>'); 
     698    $('#piece_uid').val(uid); 
     699    $('#piece-search-results').slideUp();     
    523700} 
    524701function insert_media(){ 
    525702    data=$(this).data()['data']; 
    526     index=$(this).data()['index']; 
    527703    image_url = data[4]; 
    528     title=piece[2]; 
     704    title = data[2]; 
    529705    cover_url = data[1]; 
    530706    uid = data[0]; 
    531     $('#piece-edit-box-'+index).css('backgroundImage','none'); 
    532     $('#piece-media-box-'+index).html('<img src="'+cover_url+'"><br/><b>'+title+'</b>'); 
    533     $('#piece-controls-edit-'+index).removeClass('contrast'); 
    534     $('#bodyText_'+index).attr('value', uid); 
    535     $('#piece-search-results-'+index).slideUp(); 
     707    $('#piece-edit-box').css('backgroundImage','none'); 
     708    $('#piece-media-box').html('<img src="'+cover_url+'"><br/><b>'+title+'</b>').show(); 
     709    $('#piece-edit-controls').removeClass('contrast'); 
     710    $('#piece_uid').val(uid); 
     711    $('#piece-search-results').slideUp(); 
     712    $('.selected_tab:first').removeClass('selected_tab'); 
     713    tab_sheet=$('.selected_tab_sheet:first');         
     714    tab_sheet.find('.message:first').hide(); 
     715    tab_sheet.removeClass('selected_tab_sheet'); 
    536716     
    537717} 
    538718 
    539 function edit_slide(index){ 
    540     document.getElementById('bodyText_edited').value=index; 
    541     document.getElementById('edit_button_'+index).style.display='none'; 
    542     document.getElementById('save_button_'+index).style.display='inline'; 
    543     document.getElementById('piece-controls-edit-'+index).style.display='block'; 
    544     document.getElementById('piece-controls-view-'+index).style.display='none'; 
    545     document.getElementById('caption_edit_'+(parseInt(index)+1)).style.display='block'; 
    546     document.getElementById('caption_view_'+(parseInt(index)+1)).style.display='none'; 
     719function edit_slide(){ 
     720    document.getElementById('edit_button').style.display='none'; 
     721    document.getElementById('save_button').style.display='inline'; 
     722    document.getElementById('piece-controls-edit').style.display='block'; 
     723    document.getElementById('piece-controls-view').style.display='none'; 
     724    document.getElementById('caption_edit').style.display='block'; 
     725    document.getElementById('caption_view').style.display='none'; 
    547726} 
    548727 
     
    8961075 
    8971076$(function() { 
     1077    portal_url=$('input#portal_url:first').val(); 
    8981078    $('textarea.mceEditor').tinymce({    
    899        script_url : 'tiny_mce/tiny_mce.js', 
     1079       script_url : portal_url+'/tiny_mce/tiny_mce.js', 
    9001080       theme : "lemill", 
    901        content_css : "master.css", 
     1081       content_css : portal_url+"/master.css", 
    9021082       plugins : "table"//inlinepopups", 
    9031083    }); 
  • trunk/skins/lemill/multimediamaterial_edit.cpt

    r2949 r3045  
    4848        <p>If you do not want your writing to be edited, used, and redistributed at will, then do not submit it here. All text that you did not write yourself, except brief excerpts, must be available under terms consistent with LeMill's Terms of Use before you submit it.</p> 
    4949        </metal:eula_def> 
    50         <input class="context" 
     50        </div> 
     51        <metal:save_buttons define-macro="chapteredit_save_buttons"> 
     52        <div class="form_submit"> 
     53        <input class="context savebutton" 
    5154            tabindex="" 
    5255            type="submit" 
    53             name="form.button.multimedia_form_submit" 
     56            name="form.button.save_and_move" 
    5457            value="Save" i18n:domain="plone" 
    5558            i18n:attributes="value label_save;" 
    56             tal:attributes="tabindex tabindex/next; 
    57             disabled python:test(isLocked, 'disabled', None);" 
     59            tal:attributes="tabindex tabindex/next;" 
    5860            /> 
    5961        <input class="standalone" 
    6062            tabindex="" 
    6163            type="submit" 
    62             name="form.button.cancel" 
     64            name="cancel" 
    6365            value="Cancel" i18n:domain="plone"  
    6466            i18n:attributes="value label_cancel;" 
     
    6668            /> 
    6769        </div> 
     70        <div class="required_notes"> 
     71          <ul id="req_list" tal:repeat="req context/getRequiredFieldNames"> 
     72            <li tal:attributes="id string:required_${req}; onclick string:focusToField('${req}'); onmouseover string:showFieldHint(this, '${req}'); onmouseout string:hideFieldHint(this, '${req}');" class="req_link"><span class="hint_label" tal:content="req" i18n:translate="" /> <span i18n:translate="is_required">is required</span></li> 
     73          </ul> 
     74        </div> 
    6875        <input type="hidden" name="form.submitted" value="1" /> 
    69  
     76        </metal:save_buttons> 
    7077        </form> 
    7178            </metal:macro> 
  • trunk/skins/lemill/piece_view.pt

    r3014 r3045  
    66 
    77<metal:main fill-slot="main"> 
    8         <metal:header define-macro="header"> 
    9         <h1 tal:content="object_title" class="documentFirstHeading"> 
    10             PLACEHOLDER 
    11         </h1> 
    12         </metal:header> 
    13         <metal:body define-macro="body" tal:define="image context/isImage; 
    14                         mp3 context/isAudio"> 
    15     <tal:block tal:condition="image" tal:define="size python:context.getField('image').get_size(context)"> 
    16     <img src="" alt="" 
    17         tal:attributes="src string:${here_url}/image_large; 
    18                 alt context/Title; 
    19                 title context/Title;" class="media_piece" /> 
    20     <p tal:condition="size"><a i18n:translate="label_download_hires" tal:attributes="href string:${here_url}/at_download/file">Download high resolution version (<tal:block i18n:name="size" tal:content="python:size/1024">500</tal:block> kB)</a></p> 
    21     </tal:block> 
    22     <tal:noimageblock tal:condition="not:image"> 
    23     <tal:mp3block condition="mp3"> 
    24         <img tal:condition="context/isAudio" 
    25              src="images/default_soundclip.png"/> 
    26         <div>&nbsp;</div> 
    27         <metal:audioplayer use-macro="here/macros_audioplayer/macros/audioplayer_plain"/> 
    28 <a class="discreet" tal:attributes="href string:${here_url}/at_download/file">(<tal:block i18n:domain="plone" i18n:translate="Download">Download</tal:block>)</a> 
    29     </tal:mp3block> 
     8    <metal:header define-macro="header"> 
     9     <h1 tal:content="object_title" class="documentFirstHeading">PLACEHOLDER</h1> 
     10    </metal:header> 
     11    <metal:body define-macro="body" tal:define="piece_type context/getPiece_type | nothing; 
     12    piece nocall:context; 
     13    piece_url here_url; 
     14    macro_mapping python:{'image':'here/piece_macros/macros/image_with_download', 
     15    'audio':'here/piece_macros/audioplayer_with_download', 
     16    'flash':'here/piece_macros/macros/swf_macro', 
     17    'flv-video':'here/macros_flowplayer/macros/flowplayer_piece', 
     18    'kml':'here/piece_macros/macros/kml_macro_with_download', 
     19    'unknown':'here/piece_macros/macros/download_only'}"> 
     20    <metal:view_macro use-macro="python:path(macro_mapping[piece_type])" /> 
    3021 
    31     <tal:flvblock condition="context/isFLVVideo"> 
    32         <span metal:use-macro="here/macros_flowplayer/macros/flowplayer_piece" /> 
    33         <br /> 
    34         </tal:flvblock> 
    35  
    36         <tal:kmlblock condition="context/isKml" tal:define="piece_url here_url"> 
    37             <metal:kml_macro define-macro="kml_macro">     
    38                 <div tal:replace="structure python:context.lemill_tool.getGoogleMapsAPI()" /> 
    39                 <script> 
    40                     // only if browser is compatible, show the iframe 
    41                     function isBrowserCompatible() { 
    42                         if (GBrowserIsCompatible()) { 
    43                             document.getElementById("map_iframe").style.display = ""; 
    44                         } 
    45                     }     
    46                 </script> 
    47                 <iframe onload="javascript:isBrowserCompatible()" style="display:none" 
    48                     tal:define="kml_file_url python:piece_url+'/file'" tal:attributes="src python:piece_url+'/map?kml_file='+kml_file_url"  
    49                     frameborder="0" height="370" width="520" marginwidth="0" marginheight="0" scrolling="no" id="map_iframe" ></iframe> 
    50             </metal:kml_macro> 
    51              
    52             <p><tal:block i18n:translate="text_about_kml_download">This is a KML file that can be used with Google Earth and several other globe and map applications</tal:block>: 
    53                 <br /> 
    54                 <a href="" tal:attributes="href string:${here_url}/at_download/file"><tal:block i18n:translate="" i18n:domain="plone">Download</tal:block> (<tal:block tal:content="python:context.getFile().get_size()/1024">500</tal:block> kB)</a> 
    55             </p> 
    56         </tal:kmlblock>     
    57     <tal:swfblock condition="context/isSwf" tal:define="piece_url here_url; piece python:context"> 
    58     <metal:swf_macro define-macro="swf_macro"> 
    59     <div class="media_piece"> 
    60         <script language="javascript" tal:content="python:' 
    61             AC_FL_RunContent(\n 
    62                 \'codebase\', \'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0\',\n 
    63                 \'width\', \'1\',\n 
    64                 \'height\', \'1\',\n 
    65                 \'src\', \'%s/file\',\n 
    66                 \'quality\', \'high\',\n 
    67                 \'pluginspage\', \'http://www.macromedia.com/go/getflashplayer\',\n 
    68                 \'align\', \'middle\',\n 
    69                 \'play\', \'true\',\n 
    70                 \'loop\', \'true\',\n 
    71                 \'scale\', \'showall\',\n 
    72                 \'wmode\', \'window\',\n 
    73                 \'devicefont\', \'false\',\n 
    74                 \'id\', \'%s\',\n 
    75                 \'bgcolor\', \'#ffffff\',\n 
    76                 \'name\', \'%s\',\n 
    77                 \'menu\', \'true\',\n 
    78                 \'allowFullScreen\', \'false\',\n 
    79                 \'allowScriptAccess\',\'sameDomain\',\n 
    80                 \'movie\', \'%s/file\',\n 
    81                 \'salign\', \'\'\n 
    82             );' % (piece_url, piece.createAlphaNumericId(), piece.createAlphaNumericId(), piece_url)"> 
    83         </script> 
    84         <noscript> 
    85         <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="1" height="1" id="swfPiece" align="middle" tal:attributes="id piece/createAlphaNumericId"> 
    86         <param name="allowScriptAccess" value="sameDomain" /> 
    87         <param name="movie" value="file" tal:attributes="value python:'%s/file' % piece_url" /> 
    88         <param name="quality" value="high" /> 
    89         <param name="bgcolor" value="#ffffff" /> 
    90         <embed src="file" quality="high" bgcolor="#ffffff" width="1" height="1" name="swfPiece" swliveconnect="true" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" tal:attributes="src python:'%s/file' % piece_url; name piece/createAlphaNumericId" /> 
    91         </object> 
    92         </noscript> 
    93         <script language="javascript" type="text/javascript" tal:content="python:'\nvar swfTimerVar_%s;\nfunction swfFunc_%s(){\nvar swfArgs = new Array();\nswfArgs[0] = \'swfTimerVar_%s\';\nswfArgs[1] = window.document.%s;\nvar swfFunc = function(){\nSWFtimerFnc(swfArgs);\n};\nswfTimerVar_%s = setInterval(swfFunc, 1);\n}\nswfFunc_%s()' % ((piece.createAlphaNumericId(),) * 6)" /> 
     22    <tal:is_source define="source context/getSourceFile" condition="source"><br /> 
     23        <div> 
     24            <span i18n:translate="label_download_source_file">Download source file</span>: 
     25            <a tal:attributes="href string:${here_url}/at_download/source" tal:content="python: '%s (%s KB)' % (source.filename, source.get_size()/1024)" >filename.png (500 KB)</a> 
     26        </div> 
     27    </tal:is_source> 
     28    <tal:is_desc condition="context/getDescription"><br /> 
     29    <div><label><tal:block i18n:domain="plone" i18n:translate="label_description">Description</tal:block>:</label> <br /> 
     30<metal:fieldMacro use-macro="python:here.widget('description', mode='view')"/> 
    9431    </div> 
    95     </metal:swf_macro> 
    96     </tal:swfblock> 
    97     <div tal:condition="python:(not mp3) and (not context.isFLVVideo()) and (not context.isSwf() and (not context.isKml()))"><tal:block i18n:domain="plone" i18n:translate="">Download</tal:block>: <metal:fieldMacro use-macro="python:here.widget('file',mode='view')"/></div> 
    98     <div tal:condition="mp3"><tal:block i18n:translate="label_playing_time">Playing time</tal:block>: <span tal:replace="python:DateTime(context.getLength()).strftime('%M:%S')">3:20</span></div> 
    99     </tal:noimageblock> 
    100         <tal:is_source define="source context/getSourceFile" condition="source"> 
    101             <br /> 
    102             <div> 
    103                 <span i18n:translate="label_download_source_file">Download source file</span>: 
    104                 <a tal:attributes="href string:${here_url}/at_download/source"> 
    105                     <span tal:content="source/filename" /> 
    106                     (<tal:block tal:content="python:source.get_size()/1024">500</tal:block> KB) 
    107                 </a> 
    108             </div> 
    109         </tal:is_source> 
    110         <tal:is_desc condition="context/getDescription"> 
    111         <br /> 
    112         <div><label><tal:block i18n:domain="plone" i18n:translate="label_description">Description</tal:block>:</label> <br /> 
    113     <metal:fieldMacro use-macro="python:here.widget('description', mode='view')"/> 
    114         </div> 
    115         </tal:is_desc> 
    116         <tal:is_tags condition="context/getTags"> 
    117         <br /> 
    118         <div><label><tal:block i18n:translate="label_tags">Tags</tal:block>:</label> <br /> 
    119     <metal:fieldMacro use-macro="python:here.widget('tags',mode='view')"/> 
    120         </div> 
    121         </tal:is_tags> 
    122     <tal:referenced_by define="resources python: here.getResourcesUsingPiece(return_objects=True)"> 
    123         <div tal:condition="resources"><br /><label><tal:block i18n:translate="label_resources_using_this_piece">Used in</tal:block>:</label> <br /> 
    124         <ul> 
    125         <tal:loop repeat="res resources"> 
    126         <li tal:condition="python:res"><a href="" tal:content="res/Title" tal:attributes="href python:'%s/view' %res.absolute_url()" /></li> 
    127         </tal:loop> 
    128         </ul>         
    129     </div> 
    130     </tal:referenced_by> 
    131         <br /> 
    132         <div><label><tal:block i18n:translate="label_license">License</tal:block>:</label> <br /> 
    133     <metal:fieldMacro use-macro="python:here.widget('rights',mode='view')"/> 
    134     </div> 
    135         </metal:body>                         
     32    </tal:is_desc> 
     33    <tal:is_tags condition="context/getTags"><br /> 
     34    <div><label><tal:block i18n:translate="label_tags">Tags</tal:block>:</label> <br /> 
     35<metal:fieldMacro use-macro="python:here.widget('tags',mode='view')"/> 
     36    </div> 
     37    </tal:is_tags> 
     38<tal:referenced_by define="resources python: here.getResourcesUsingPiece(return_objects=True)"> 
     39    <div tal:condition="resources"><br /><label><tal:block i18n:translate="label_resources_using_this_piece">Used in</tal:block>:</label> <br /> 
     40    <ul> 
     41    <tal:loop repeat="res resources"> 
     42    <li tal:condition="python:res"><a href="" tal:content="res/Title" tal:attributes="href python:'%s/view' %res.absolute_url()" /></li> 
     43    </tal:loop> 
     44    </ul>         
     45</div> 
     46</tal:referenced_by> 
     47    <br /> 
     48    <div><label><tal:block i18n:translate="label_license">License</tal:block>:</label> <br /> 
     49<metal:fieldMacro use-macro="python:here.widget('rights',mode='view')"/> 
     50</div> 
     51    </metal:body>                         
    13652</metal:main> 
    13753</body> 
  • trunk/skins/lemill/script_chapterController.cpy

    r3014 r3045  
    1 ## Script (Python) "upload_piece" 
    2 ##bind container=container 
    3 ##bind context=context 
    4 ##bind namespace= 
    5 ##bind script=script 
    6 ##bind state=state 
    7 ##bind subpath=traverse_subpath 
    8 ##title=Add Link 
    9 ## 
    10  
    11 from Products.LeMill import i18nme as gettext 
    12 from Products.CMFCore.utils import getToolByName 
    13 lt=getToolByName(context, 'lemill_tool') 
    14  
    15 def do_nothing(a=0,b=0,c=0): 
    16     pass 
    17      
    18      
    19 def repairPilotOrder(): 
    20     body=context.getRawBodyText() 
    21     i = questions_index = last_keywords_index = 0 
    22     for chapter, chapter_type in body: 
    23         if chapter_type=='pilot_questions': 
    24             questions_index=i 
    25         elif chapter_type=='pilot_keywords': 
    26             last_keywords_index=i 
    27         i+=1 
    28     if last_keywords_index>questions_index: # switch last questions and keywords 
    29         temp=body[questions_index] 
    30         body[questions_index]=body[last_keywords_index] 
    31         body[last_keywords_index]=temp         
    32         context.setBodyText(body) 
    33      
    34   
     1# Script (Python) "upload_piece" 
     2#bind container=container 
     3#bind context=context 
     4#bind namespace= 
     5#bind script=script 
     6#bind state=state 
     7#bind subpath=traverse_subpath 
     8#title=Add Link 
     9# 
     10# 
     11#from Products.LeMill import i18nme as gettext 
     12#from Products.CMFCore.utils import getToolByName 
     13#lt=getToolByName(context, 'lemill_tool') 
     14# 
     15#def do_nothing(a=0,b=0,c=0): 
     16#    pass 
     17#     
     18#     
     19#def repairPilotOrder(): 
     20#    body=context.getRawBodyText() 
     21#    i = questions_index = last_keywords_index = 0 
     22#    for chapter, chapter_type in body: 
     23#        if chapter_type=='pilot_questions': 
     24#            questions_index=i 
     25#        elif chapter_type=='pilot_keywords': 
     26#            last_keywords_index=i 
     27#        i+=1 
     28#    if last_keywords_index>questions_index: # switch last questions and keywords 
     29#        temp=body[questions_index] 
     30#        body[questions_index]=body[last_keywords_index] 
     31#        body[last_keywords_index]=temp         
     32#        context.setBodyText(body) 
     33#     
     34#  
    3535# ok, this is supposed to collect all of the functionalities of ChapterField-related scripts in one place 
    36  
     36# 
    3737# 0) when we arrive here process_form script from widget has already ran and the ChapterField.set too, so the new stuff is already in place and main point is to deal with after effects (deliver error messages) and do button-activities 
    38  
    39 button = state.button 
    40 if not button: 
    41     button = '' 
    42  
    43 REQUEST=context.REQUEST 
    44  
     38# 
     39#button = state.button 
     40#if not button: 
     41#    button = '' 
     42# 
     43#REQUEST=context.REQUEST 
     44# 
    4545# 1): Materials automatically route everything here, so we need to check if we should just move forward 
    46  
    47 forwarders={ 
    48 'form_submit':'redirect_to_action:string:view', 
    49 'multimedia_form_submit':'redirect_to_action:string:metadata', 
    50 'exercise_form_submit':'redirect_to_action:string:metadata', 
    51 'pilot_form_submit':'traverse_to:string:script_storeResearchQuestions', 
    52 'pilot_edit_scenes':'traverse_to:string:script_storeResearchQuestions', 
    53 'pilot_form_preview':'redirect_to:string:pilot_preview' 
    54 } 
    55 if button in forwarders.keys(): 
    56     return state.set(next_action=forwarders[button]) 
    57  
    58  
     46# 
     47#forwarders={ 
     48#'form_submit':'redirect_to_action:string:view', 
     49#'multimedia_form_submit':'redirect_to_action:string:metadata', 
     50#'exercise_form_submit':'redirect_to_action:string:metadata', 
     51#'pilot_form_submit':'traverse_to:string:script_storeResearchQuestions', 
     52#'pilot_edit_scenes':'traverse_to:string:script_storeResearchQuestions', 
     53#'pilot_form_preview':'redirect_to:string:pilot_preview' 
     54#} 
     55#if button in forwarders.keys(): 
     56#    return state.set(next_action=forwarders[button]) 
     57# 
     58# 
    5959# 2) find out where we're coming from, what we're supposed to do 
    60  
    61 mmedia=False 
    62 pilot=False 
    63 exercise=False 
    64 edit_page='edit' 
    65 edit_nr = int(REQUEST.get('bodyText_edited',-1)) 
    66 kwargs={} 
    67 up_message=u'' 
    68 down_message=u'' 
    69 del_message=u'' 
    70 save_message=u'' 
    71 add_message=u'' 
    72  
    73 if context.portal_type=='MultimediaMaterial': 
    74     mmedia=True 
    75     granul=1 
    76     up_message=gettext('Moved chapter/piece up') 
    77     down_message=gettext('Moved chapter/piece down') 
    78     save_message=gettext('Chapter saved') 
    79 elif context.portal_type=='PILOTMaterial': 
    80     pilot=True 
    81     granul=3 
    82     edit_page='edit_scenes'     
    83     up_message=gettext('Moved scene up') 
    84     down_message=gettext('Moved scene down') 
    85     save_message=gettext('Scene saved') 
    86 elif context.portal_type=='ExerciseMaterial': 
    87     exercise=True 
    88     granul=1 
    89     up_message=gettext('Moved exercise portion up') 
    90     down_message=gettext('Moved exercise portion down') 
    91     save_message='Exercise saved' 
    92  
     60# 
     61#mmedia=False 
     62#pilot=False 
     63#exercise=False 
     64#edit_page='edit' 
     65#edit_nr = int(REQUEST.get('bodyText_edited',-1)) 
     66#kwargs={} 
     67#del_message=u'' 
     68#save_message=u'' 
     69#add_message=u'' 
     70# 
     71#if context.portal_type=='MultimediaMaterial': 
     72#    mmedia=True 
     73#    granul=1 
     74#    save_message=gettext('Chapter saved') 
     75#elif context.portal_type=='PILOTMaterial': 
     76#    pilot=True 
     77#    granul=3 
     78#    edit_page='edit_scenes'     
     79#    save_message=gettext('Scene saved') 
     80#elif context.portal_type=='ExerciseMaterial': 
     81#    exercise=True 
     82#    granul=1 
     83#    save_message='Exercise saved' 
     84# 
    9385# 3) deleting something? 
    94  
    95 deleted_nr = int(REQUEST.get('bodyText_deleted', -1)) 
    96  
    97 if deleted_nr>-1 and 'del' in button: 
    98     if 'delChapter' in button and pilot:     
    99         del_message=gettext('Scene removed') 
    100     elif 'delChapter' in button and (mmedia or exercise): 
    101         del_message=gettext('Chapter removed') 
    102     else: 
    103         delsize = 0 
    104         del_message='' 
    105  
    106     if del_message: 
    107         context.getField('bodyText').delChapter(context, deleted_nr, deleted_nr+granul) 
    108         lt.addPortalMessage(del_message) 
    109     if edit_nr>deleted_nr: # if list is one shorter, indexes have changed 
    110         edit_nr=edit_nr-granul 
    111  
     86# 
     87#deleted_nr = int(REQUEST.get('bodyText_deleted', -1)) 
     88# 
     89#if deleted_nr>-1 and 'del' in button: 
     90#    if 'delChapter' in button and pilot:     
     91#        del_message=gettext('Scene removed') 
     92#    elif 'delChapter' in button and (mmedia or exercise): 
     93#        del_message=gettext('Chapter removed') 
     94#    else: 
     95#        delsize = 0 
     96#        del_message='' 
     97# 
     98#    if del_message: 
     99#        context.getField('bodyText').delChapter(context, deleted_nr, deleted_nr+granul) 
     100#        lt.addPortalMessage(del_message) 
     101#    if edit_nr>deleted_nr: # if list is one shorter, indexes have changed 
     102#        edit_nr=edit_nr-granul 
     103# 
    112104# 4) are we adding chapters? 
    113  
    114 if 'add_' in button: 
    115     if 'add_textareas' in button: 
    116         edit_nr=context.getField('bodyText').add_new_chapter(context)      
    117         add_message=gettext('Added a new chapter') 
    118     elif 'add_mediapieces' in button: 
    119         edit_nr=context.getField('bodyText').add_new_mediapiece(context)      
    120         add_message=gettext('Added a new media piece') 
    121     elif 'add_embedblocks' in button: 
    122         edit_nr=context.getField('bodyText').add_new_chapter(context, [('','embed_block')]) 
    123         add_message=gettext('Added a new embed block') 
    124     elif 'add_scene' in button: 
    125         edit_nr=context.getField('bodyText').add_new_scene(context) 
    126         repairPilotOrder()      
    127         add_message=gettext('Added a new scene') 
    128     elif 'add_exercise_textarea' in button: 
    129         edit_nr=context.getField('bodyText').add_new_question(context,'text_block') 
    130         add_message=gettext('Added a new chapter') 
    131     elif 'add_exercise_mediapiece' in button: 
    132         edit_nr=context.getField('bodyText').add_new_question(context,'media_piece') 
    133         add_message=gettext('Added a new media piece') 
    134     elif 'add_exercise_embedblock' in button: 
    135         edit_nr=context.getField('bodyText').add_new_question(context,'embed_block') 
    136         add_message=gettext('Added a new embed block') 
    137     elif 'add_question' in button: 
    138         questype = REQUEST.get('questiontype') 
    139         if (questype == 'Multiple choice question'): 
    140             edit_nr=context.getField('bodyText').add_new_question(context,'choice') 
    141             add_message=gettext('Added a new multiple choice question') 
    142         elif (questype == 'Multiple response question'): 
    143             edit_nr=context.getField('bodyText').add_new_question(context,'multiple_choices') 
    144             add_message=gettext('Added a new multiple response question') 
    145         elif (questype == 'Fill-in-the-blanks exercise'): 
    146             edit_nr=context.getField('bodyText').add_new_question(context,'fill_in_the_blanks') 
    147             add_message=gettext('Added a new fill-in-the-blanks exercise') 
    148         if (questype == 'Open-ended question'): 
    149             edit_nr=context.getField('bodyText').add_new_question(context,'open_ended') 
    150             add_message=gettext('Added a new open ended question') 
    151     elif 'add_hotpotatoes_questions' in button: 
    152         edit_nr = -1 
    153         quesfile = REQUEST.get('hotpotatoes_file') 
    154         if quesfile: 
    155             success = 0 
    156             filename = quesfile.filename 
    157             if (filename.lower().endswith('.jqz') or filename.lower().endswith('.jcl')): 
    158                 filecontent = quesfile.read() 
    159                 if (filecontent.strip().startswith('<?xml')): 
    160                     if ('<hotpot-jquiz-file>' in filecontent): 
    161                         success = 1 
    162                         upload_success = context.getField('bodyText').importJQuizQuestions(context, filecontent, context.REQUEST) 
    163                         if (upload_success == 0): 
    164                             lt.addPortalMessage('Invalid file.', type="warn") 
    165                     elif ('<hotpot-jcloze-file>' in filecontent): 
    166                         success = 1 
    167                         upload_success = context.getField('bodyText').importJClozeQuestion(context, filecontent, context.REQUEST) 
    168                         if (upload_success == 0): 
    169                             lt.addPortalMessage('Invalid file.', type="warn") 
    170             if (success == 0): 
    171                 lt.addPortalMessage(gettext('You can import only questions made with JQuiz or JCloze.'), type="warn") 
    172  
    173     if del_message: 
    174         lt.addPortalMessage(del_message) 
    175          
    176 elif 'import_hotpotatoes_questions' in button: 
    177     # for showing hotpotatoes upload form, just set edit_nr to -2 
    178     edit_nr = -2 
    179 elif 'cancel_hotpotatoes_upload' in button: 
    180     edit_nr = -1 
    181  
    182 # 5) are we moving chapters? 
    183 if 'moveChapterUp' in button: 
    184     # this is bit ugly, but I don't want to create another hidden field in templates, 
    185     # so moving uses the same variable deleted_nr as deleting 
    186     context.getField('bodyText').moveChapterUp(context, deleted_nr, granul) 
    187     if edit_nr==deleted_nr: #probably we still want to keep focus on same edited section 
    188         edit_nr=edit_nr-granul 
    189     if edit_nr+granul==deleted_nr: # move replaced the current edited chapter 
    190         edit_nr=edit_nr+granul 
    191     if pilot: 
    192         repairPilotOrder() 
    193     lt.addPortalMessage(up_message) 
    194  
    195 if 'moveChapterDown' in button: 
    196     context.getField('bodyText').moveChapterDown(context, deleted_nr, granul) 
    197     if edit_nr==deleted_nr: #probably we still want to keep focus on same edited section 
    198         edit_nr=edit_nr+granul 
    199     if edit_nr-granul==deleted_nr: # move replaced the current edited chapter 
    200         edit_nr=edit_nr-granul 
    201     if pilot: 
    202         repairPilotOrder() 
    203     lt.addPortalMessage(down_message) 
    204  
     105# 
     106#if 'add_' in button: 
     107#    if 'add_textareas' in button: 
     108#        edit_nr=context.getField('bodyText').add_new_chapter(context)      
     109#        add_message=gettext('Added a new chapter') 
     110#    elif 'add_mediapieces' in button: 
     111#        edit_nr=context.getField('bodyText').add_new_mediapiece(context)      
     112#        add_message=gettext('Added a new media piece') 
     113#    elif 'add_embedblocks' in button: 
     114#        edit_nr=context.getField('bodyText').add_new_chapter(context, [('','embed_block')]) 
     115#        add_message=gettext('Added a new embed block') 
     116#    elif 'add_scene' in button: 
     117#        edit_nr=context.getField('bodyText').add_new_scene(context) 
     118#        repairPilotOrder()      
     119#        add_message=gettext('Added a new scene') 
     120#    elif 'add_question' in button: 
     121#        edit_nr=context.getField('bodyText').add_new_question(context,'exercise') 
     122#        add_message=gettext('Added a new exercise block') 
     123# 
     124#    if del_message: 
     125#        lt.addPortalMessage(del_message) 
     126#             
     127#    elif 'add_hotpotatoes_questions' in button: 
     128#        edit_nr = -1 
     129#        quesfile = REQUEST.get('hotpotatoes_file') 
     130#        if quesfile: 
     131#            success = 0 
     132#            filename = quesfile.filename 
     133#            if (filename.lower().endswith('.jqz') or filename.lower().endswith('.jcl')): 
     134#                filecontent = quesfile.read() 
     135#                if (filecontent.strip().startswith('<?xml')): 
     136#                    if ('<hotpot-jquiz-file>' in filecontent): 
     137#                        success = 1 
     138#                        upload_success = context.getField('bodyText').importJQuizQuestions(context, filecontent, context.REQUEST) 
     139#                        if (upload_success == 0): 
     140#                            lt.addPortalMessage('Invalid file.', type="warn") 
     141#                    elif ('<hotpot-jcloze-file>' in filecontent): 
     142#                        success = 1 
     143#                        upload_success = context.getField('bodyText').importJClozeQuestion(context, filecontent, context.REQUEST) 
     144#                        if (upload_success == 0): 
     145#                            lt.addPortalMessage('Invalid file.', type="warn") 
     146#            if (success == 0): 
     147#                lt.addPortalMessage(gettext('You can import only questions made with JQuiz or JCloze.'), type="warn") 
     148#         
     149#elif 'import_hotpotatoes_questions' in button: 
     150#    for showing hotpotatoes upload form, just set edit_nr to -2 
     151#    edit_nr = -2 
     152#elif 'cancel_hotpotatoes_upload' in button: 
     153#    edit_nr = -1 
     154# 
    205155# 6) are we dealing with translation? 
    206  
    207 translation=REQUEST.get('translation',0) 
    208 if translation: 
    209     edit_page='base_translate' 
    210  
     156# 
     157#translation=REQUEST.get('translation',0) 
     158#if translation: 
     159#    edit_page='base_translate' 
     160# 
    211161# 8) Make a note if saving 
    212  
    213 if (button=='editChapter' and edit_nr==-1) or button=='Save': 
    214     lt.addPortalMessage(save_message) 
    215  
     162# 
     163#if (button=='editChapter' and edit_nr==-1) or button=='Save': 
     164#    lt.addPortalMessage(save_message) 
     165# 
    216166# 9) set the next_action      
    217  
    218 kwargs['next_action']='redirect_to:string:%s?edit=%s&b=0&#%s' % (edit_page,edit_nr,edit_nr) 
    219 return state.set(**kwargs ) 
     167# 
     168#raise hell 
     169# 
     170#kwargs['next_action']='redirect_to:string:%s?edit=%s&b=0&#%s' % (edit_page,edit_nr,edit_nr) 
     171#return state.set(**kwargs ) 
  • trunk/skins/lemill/validate_base.vpy

    r3014 r3045  
    1111 
    1212errors = {} 
     13context.dumpme('validate_base.vpy called') 
    1314errors = context.validate(REQUEST=context.REQUEST, errors=errors, data=1, metadata=0) 
    1415 
    1516if errors: 
     17    context.dumpme('errors from validate_base.vpy: %s' % errors ) 
    1618    context.lemill_tool.addPortalMessage(gettext('Please correct the indicated errors.')) 
    1719    return state.set(status='failure', errors=errors) 
  • trunk/skins/lemill/validate_base_silently.vpy

    r3014 r3045  
    1111 
    1212errors = {} 
     13context.dumpme('*** validation starting at validate_base_silently ***') 
    1314errors = context.validate(REQUEST=context.REQUEST, errors=errors, data=1, metadata=0) 
    14  
     15context.dumpme('*** validation done.') 
    1516 
    1617if errors: 
    1718    context.lemill_tool.addPortalMessage(gettext('Please correct the indicated errors.')) 
    18     return state.set(status='failure', errors=errors) 
     19    state=state.set(status='failure', errors=errors) 
     20    context.dumpme(state) 
     21    return state 
    1922else: 
    2023    return state 
  • trunk/skins/lemill/widget_chapter.pt

    r3018 r3045  
    44      xmlns:i18n="http://xml.zope.org/namespaces/i18n" 
    55      i18n:domain="lemill"> 
    6  
    76  <head><title></title></head> 
    8  
    97  <body> 
    108 
     
    1210 
    1311<metal:view_macro define-macro="view" tal:define="value accessor">  
    14     <tal:chapters repeat="chaptertuple value">             
    15         <tal:defs tal:define="chapter python:chaptertuple[0]; 
    16         type python:chaptertuple[1]; 
    17         index repeat/chaptertuple/index;"> 
    18         <div tal:condition="python:type=='text_block'" tal:omit-tag=""> 
    19             <metal:view_text_block use-macro="here/widget_chapter/macros/view_text_block" /> 
    20         </div> 
    21         <div tal:condition="python:type=='media_piece'" tal:omit-tag=""> 
    22             <metal:view_text_block use-macro="here/widget_chapter/macros/view_media_piece" /> 
    23         </div> 
    24         <div tal:condition="python:type=='embed_block'" tal:omit-tag=""> 
    25             <metal:view_embed_block use-macro="here/widget_chapter/macros/view_embed_block" /> 
    26         </div> 
     12    <tal:chapters repeat="chapter value">             
     13        <tal:defs tal:define="text chapter/text; 
     14            type chapter/type; 
     15            index repeat/chapter/index;"> 
     16            <metal:view_block use-macro="python:path('here/widget_chapter/macros/view_'+type)" /> 
    2717        </tal:defs> 
    2818    </tal:chapters> 
     
    3222 
    3323<metal:view_text_block define-macro="view_text_block"> 
    34     <div tal:replace="structure chapter" /> 
     24<div tal:replace="structure text" /> 
    3525</metal:view_text_block> 
    3626 
    3727<metal:view_media_piece define-macro="view_media_piece"> 
    38     <tal:inner_defs define="UID python:field.isUid(chapter);"> 
    39     <tal:piece_defs condition="UID" define="piece python:field.getObjectByUID(context, UID); 
    40     abs_url piece/absolute_url | nothing">     
    41     <tal:we_got_piece condition="python:piece and not piece.isDeleted()"> 
    42     <tal:image condition="piece/isImage">  
    43         <a href="" tal:attributes="href abs_url | here_url"> 
    44         <img src="" tal:define="pieceurl piece/absolute_url | python:'default_movieclip.png'" 
    45         tal:attributes="src string:${pieceurl}/image_large; alt piece/Title | nothing" class="media_piece"/> 
    46         </a> 
    47     </tal:image> 
    48     <tal:audio condition="piece/isAudio"> 
    49         <div style="height:20px" class="visualClear">&nbsp;</div> 
    50         <metal:audioplayer use-macro="here/macros_audioplayer/macros/audioplayer"/> 
    51     </tal:audio> 
    52     <tal:flvblock condition="piece/isFLVVideo"> 
    53         <span metal:use-macro="here/macros_flowplayer/macros/flowplayer_chapter_view" /> 
    54     </tal:flvblock> 
    55     <tal:swf condition="piece/isSwf"> 
    56         <tal:piece_url define="piece_url abs_url"> 
    57          <span metal:use-macro="here/piece_view/macros/swf_macro" /> 
    58         </tal:piece_url> 
    59     </tal:swf> 
    60     <tal:kml condition="piece/isKml"> 
    61         <tal:piece_url define="piece_url abs_url"> 
    62             <span metal:use-macro="here/piece_view/macros/kml_macro" /> 
    63         </tal:piece_url> 
    64     </tal:kml>                     
    65     </tal:we_got_piece> 
    66     </tal:piece_defs> 
    67     </tal:inner_defs> 
     28<tal:piece_defs define="piece python:field.getObjectByUID(context, chapter.get('uid','')); 
     29    piece_url piece/absolute_url | nothing; 
     30    piece_type piece/getPiece_type | nothing;  
     31    macro_mapping python:{'image':'here/piece_macros/macros/image', 
     32        'audio':'here/macros_audioplayer/macros/audioplayer', 
     33        'flash':'here/piece_macros/macros/swf_macro', 
     34        'flv-video':'here/macros_flowplayer/macros/flowplayer_chapter_view', 
     35        'kml':'here/piece_macros/macros/kml_macro'}">     
     36        <tal:we_got_piece condition="python:piece and not piece.isDeleted()"> 
     37            <metal:view use-macro="python:path(macro_mapping[piece_type])" /> 
     38        </tal:we_got_piece> 
     39</tal:piece_defs> 
    6840</metal:view_media_piece> 
    6941 
    7042<metal:view_embed_block define-macro="view_embed_block"> 
    71     <div class="embed_content" tal:condition="python:chapter" tal:content="structure chapter" ></div> 
     43<div class="embed_content" tal:content="structure chapter/embed | python:''" ></div> 
    7244</metal:view_embed_block> 
    7345 
    74  
    7546<!-- Main edit macro --> 
    7647 
    77 <metal:define define-macro="area_edit"> 
    78   <tal:define 
    79       define="inputname fieldName; 
    80               not_empty python: value and len(value[0]) or (len(value)>1);  
    81               edit_chapter request/edit | python:0; 
    82               edit_chapter python: test(not_empty, int(edit_chapter), 0); 
     48<metal:define define-macro="edit"> 
     49 <metal:use use-macro="field_macro | here/field/macros/edit"> 
     50  <metal:fill fill-slot="widget_body"> 
     51  <tal:define define="edit_chapter request/chapter_edited | python:0; 
     52              edit_chapter python: int(edit_chapter); 
    8353              translation translation | python:0; 
     54              value python:field.confirmUpdated(value); 
    8455              chapter_count python:len(value); 
    85               text_edit python:True; 
    86               granularity python:1; 
    87               "> 
    88     <input type="hidden" name="chapter_count" id="chapter_count" value="1" tal:attributes="value chapter_count; name python:'%s_count' % fieldName; id python:'%s_count' % fieldName"/> 
    89     <input type="hidden" name="chapter_edited" id="chapter_edited" value="0" tal:attributes="value edit_chapter; name python:'%s_edited' % fieldName ;id python:'%s_edited' % fieldName "/> 
    90     <input type="hidden" name="chapter_deleted" id="chapter_deleted" value="-1" tal:attributes="name python:'%s_deleted' % fieldName;id python:'%s_deleted' % fieldName"/> 
    91     <input type="hidden" name="chapter_uploaded" id="chapter_uploaded" value="-1" tal:attributes="name python:'%s_uploaded' % fieldName;id python:'%s_uploaded' % fieldName"/> 
    92  
    93    <tal:chapters repeat="chaptertuple value"> 
    94       <tal:definitions define="index repeat/chaptertuple/index; 
    95       chapter python:chaptertuple[0]; 
    96       type python:chaptertuple[1]; 
    97       cleaned_chapter python:field.getChapter(context, index)[0]; 
     56              edit_chapter python: test(edit_chapter<chapter_count, edit_chapter, chapter_count-1);             
     57              cleaned_chapters here/getBodyText; 
     58              macro_mapping python:{'text_block':'here/widget_chapter/macros/%s_text_block', 
     59                'media_piece':'here/widget_chapter/macros/%s_media_piece', 
     60                'embed_block':'here/widget_chapter/macros/%s_embed_block'}; 
     61              chapter_names python:{'text_block':'text chapter', 'media_piece':'media piece', 'embed_block':'embedded content'};"> 
     62    <input type="hidden" name="chapter_count" id="chapter_count" value="1" tal:attributes="value chapter_count"/> 
     63    <input type="hidden" name="chapter_edited" id="chapter_edited" value="0" tal:attributes="value edit_chapter"/> 
     64    <input type="hidden" name="chapter_last_edited" id="chapter_last_edited" value="0" tal:attributes="value edit_chapter"/> 
     65    <input type="hidden" name="chapter_type" id="chapter_type" tal:attributes="value python:value[edit_chapter]['type']"/> 
     66   <div class="sortable"> 
     67   <tal:chapters repeat="chapter value"> 
     68    <tal:definitions define="index repeat/chapter/index; 
     69      text chapter/text; 
     70      type chapter/type; 
     71      cleaned_chapter python:cleaned_chapters[index]; 
    9872       ">        
    99  
    100        <a name="#" tal:attributes="name index"></a> 
    101        <fieldset>            
     73     <a name="#" tal:attributes="name index"></a> 
     74     <div class="sortable_row">         
     75     <fieldset>    
     76       <metal:legend define-macro="legendbuttons"> 
     77            <div class="handle"> 
     78                <img src="images/pattern.gif" tal:attributes="src string:${portal_url}/images/pattern.gif" alt="" width="18" height="10" style="padding-top:4px;padding-left:4px" /> 
     79                <input class="editChapter " 
     80                    type="submit" 
     81                    name="editChapter" 
     82                    value="Edit" i18n:domain="plone" 
     83                    tal:condition="python:index!=edit_chapter" 
     84                    i18n:attributes="value label_edit;" 
     85                    tal:attributes="tabindex tabindex/next; 
     86                    onClick python:'''javascript:document.getElementById('chapter_edited').value=%s''' %  index"/>                     
     87                <input class="saveChapter" 
     88                    tabindex="" 
     89                    type="submit" 
     90                    name="saveChapter" 
     91                    value="Save" i18n:domain="plone" 
     92                    tal:condition="python:index==edit_chapter" 
     93                    i18n:attributes="value label_save;" 
     94                    tal:attributes="tabindex tabindex/next;" 
     95                    onClick="$('chapter_edited').val('-1')"/> 
     96                <span tal:content="python:chapter_names[type]" i18n:translate="" class="discreet">text chapter</span> 
     97 
     98                <input class="right deleteButton" 
     99                    type="button" 
     100                    name="deleteButton" 
     101                    value="Delete" i18n:domain="plone" 
     102                    i18n:attributes="value;" 
     103                    tal:attributes="tabindex tabindex/next;" 
     104                    onClick="deleteChapter(this);" /> 
     105                <input type="button" 
     106                    name="undeleteButton" 
     107                    class="right undeleteButton" 
     108                    value="Undelete" 
     109                    i18n:attributes="value;" 
     110                    tal:attributes="tabindex tabindex/next;" 
     111                    style="display:none" 
     112                    onClick="undeleteChapter(this);"/> 
     113                <input type="hidden" 
     114                    tal:attributes="id string:chapter_deleted_$index; name string:chapter_deleted_$index" 
     115                    class="deletionkeeper" 
     116                    value="0" /> 
     117                <input type="hidden"  
     118                    class="orderkeeper"  
     119                    tal:attributes="name string:chapter_order_$index; id string:chapter_order_$index; value index" />        
     120            </div> 
     121        </metal:legend> 
    102122        <tal:view_mode condition="python:index!=edit_chapter"> 
    103          
    104            <tal:legend metal:use-macro="here/widget_chapter/macros/legend_view_buttons" /> 
    105            <div tal:condition="python: type=='text_block'" tal:omit-tag="" >             
    106             <metal:block use-macro="here/widget_chapter/macros/edit_view_text_block" /> 
    107            </div> 
    108            <div tal:condition="python: type=='media_piece'" tal:omit-tag="" > 
    109               <metal:block use-macro="here/widget_chapter/macros/edit_view_media_piece" /> 
    110            </div> 
    111            <div tal:condition="python: type=='embed_block'" tal:omit-tag="" > 
    112               <metal:block use-macro="here/widget_chapter/macros/edit_view_embed_block" /> 
    113            </div> 
    114            <input type="hidden" tal:attributes="id python:'%s_%s' % (fieldName,index); 
    115                                                name python:'%s_%s' % (fieldName,index); 
    116                                                value chapter" /> 
    117       </tal:view_mode> 
    118  
     123           <metal:block use-macro="python:path(macro_mapping[type] % 'edit_view')" />                
     124        </tal:view_mode> 
    119125      <tal:edit_mode condition="python:index==edit_chapter"> 
    120        <a name="edit"></a> 
    121        <legend> 
    122             <input class="context" 
    123                 tabindex="" 
    124                 type="submit" 
    125                 name="form.button.editChapter" 
    126                 value="Save" i18n:domain="plone" 
    127                 i18n:attributes="value label_save;" 
    128                 tal:attributes="tabindex tabindex/next; 
    129                 disabled python:test(isLocked, 'disabled', None); 
    130                 onClick python:'''javascript:document.getElementById('%s_edited').value=%s''' % (fieldName, -1)"                    /> 
    131        </legend> 
    132        <div tal:condition="python: type=='text_block'"> 
    133            <metal:block metal:use-macro="here/widget_chapter/macros/edit_text_block" /> 
    134        </div> 
    135        <div tal:condition="python: type=='media_piece'"> 
    136            <metal:block metal:use-macro="here/widget_chapter/macros/edit_media_piece" /> 
    137        </div> 
    138        <div tal:condition="python: type=='embed_block'"> 
    139            <metal:block metal:use-macro="here/widget_chapter/macros/edit_embed_block" /> 
    140        </div> 
    141        <input type="hidden" 
    142              name="" 
    143              value="html" 
    144              tal:attributes="name python:'%s_text_format_%s' % (fieldName, index);" /> 
     126           <a name="edit"></a> 
     127           <metal:block use-macro="python:path(macro_mapping[type] % 'edit')" />                
    145128      </tal:edit_mode> 
    146  
    147       </fieldset> 
    148       <input type="hidden" tal:attributes="value type; 
    149           id python:'%s_type_%s' % (fieldName, index); 
    150           name python:'%s_type_%s' % (fieldName, index)"/>        
     129     </fieldset> 
     130     </div> 
    151131     </tal:definitions> 
    152132   </tal:chapters> 
    153  
     133   </div> 
    154134   <tal:buttons condition="not:translation"> 
    155135       <input class="context" 
    156136           tabindex="" 
    157137           type="submit" 
    158            name="form.button.add_mediapieces" 
     138           name="add_media_piece" 
    159139           value="Add media piece" 
    160140           i18n:attributes="value label_add_media_piece;" 
     
    164144           tabindex="" 
    165145           type="submit" 
    166            name="form.button.add_textareas" 
     146           name="add_text_block" 
    167147           value="Add text block" 
    168148           i18n:attributes="value label_add_textarea;" 
     
    172152           tabindex="" 
    173153           type="submit" 
    174            name="form.button.add_embedblocks" 
     154           name="add_embed_block" 
    175155           value="Embed" 
    176156           i18n:attributes="value label_embed;" 
    177157           tal:attributes="tabindex tabindex/next" 
    178158           /> 
    179  
    180159   </tal:buttons> 
    181160  </tal:define> 
     161 </metal:fill> 
     162</metal:use> 
    182163</metal:define> 
    183164 
    184165 
    185  
    186166<!-- edit/view macros,  called from Main edit macros if chapter is not the one that is edited --> 
    187167 
    188 <metal:legend define-macro="legend_view_buttons"> 
    189  
    190         <legend> 
    191         <input class="context" 
    192             tabindex="" 
    193             type="submit" 
    194             name="form.button.editChapter" 
    195             value="Edit" i18n:domain="plone" 
    196             i18n:attributes="value label_edit;" 
    197             tal:attributes="tabindex tabindex/next; 
    198             disabled python:test(isLocked, 'disabled', None); 
    199             onClick python:'''javascript:document.getElementById('%s_edited').value=%s''' % (fieldName, index)"                    /> 
    200         <input class="context" 
    201             tabindex="" 
    202             type="submit" 
    203             name="form.button.moveChapterUp" 
    204             value="Move up" i18n:domain="lemill" 
    205             i18n:attributes="value label_move_up;" 
    206             tal:attributes="tabindex tabindex/next; 
    207             disabled python:test(isLocked or index &lt; granularity, 'disabled', None); 
    208             onClick python:'''javascript:document.getElementById('%s_deleted').value=%s''' % (fieldName, index)" 
    209             /> 
    210         <input class="context" 
    211             tabindex="" 
    212             type="submit" 
    213             name="form.button.moveChapterDown" 
    214             value="Move down" i18n:domain="lemill" 
    215             i18n:attributes="value label_move_down;" 
    216             tal:attributes="tabindex tabindex/next; 
    217             disabled python:test(isLocked or index==len(value)-granularity, 'disabled', None); 
    218             onClick python:'''javascript:document.getElementById('%s_deleted').value=%s''' % (fieldName, index)" 
    219             /> 
    220  
    221         <input class="context deleteButtonPosition" 
    222             tabindex="" 
    223             type="submit" 
    224             name="form.button.delChapter" 
    225             value="Delete" i18n:domain="plone" 
    226             i18n:attributes="value;" 
    227             tal:attributes="tabindex tabindex/next; 
    228             disabled python:test(isLocked, 'disabled', None); 
    229             onClick python:'''javascript:document.getElementById('%s_deleted').value=%s''' % (fieldName, index)" 
    230             /> 
    231     </legend> 
    232  
    233 </metal:legend> 
    234  
    235168<metal:edit_view_text_block define-macro="edit_view_text_block"> 
    236     <div tal:replace="structure cleaned_chapter" /> 
     169<div tal:replace="structure cleaned_chapter/text" /> 
    237170</metal:edit_view_text_block> 
    238171 
    239172<tal:edit_view_media_piece metal:define-macro="edit_view_media_piece">     
    240     <tal:defs define="UID python:field.isUid(chapter); 
    241     piece python:field.getObjectByUID(context,UID); 
    242     deleted piece/isDeleted | python:True"> 
    243     <tal:we_got_piece condition="python:piece and not deleted"> 
    244         <tal:audio tal:condition="piece/isAudio"> 
    245             <div style="height:20px" class="visualClear">&nbsp;</div> 
    246            <metal:block metal:use-macro="here/macros_audioplayer/macros/audioplayer" /&g