source: trunk/ExerciseMaterial.py @ 3075

Revision 3075, 13.1 KB checked in by jukka, 9 years ago (diff)

Fixed lesson plans and school projects

Line 
1# Copyright 2006 by the LeMill Team (see AUTHORS)
2#
3# This file is part of LeMill.
4#
5# LeMill is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# LeMill is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with LeMill; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1
18
19from AccessControl import ClassSecurityInfo, getSecurityManager
20from Products.Archetypes.public import *
21from Products.CMFCore.utils import getToolByName
22from config import PROJECTNAME, MODIFY_CONTENT, VIEW, to_unicode
23from FieldsWidgets import ChapterField, ExerciseWidget
24from Schemata import no_description, material_schema, community_editing_schema, draft_schema
25from Material import Material
26from permissions import MODIFY_CONTENT
27from random import shuffle
28from Products.LeMill import LeMillMessageFactory as _
29import re
30
31exercise_schema = Schema((
32   ChapterField('bodyText',
33        accessor="getBodyText",
34        edit_accessor = 'getRawBodyText',
35        mutator = "setBodyText",
36        index='ZCTextIndex',
37        index_method = 'getOnlyText',
38        copied_in_translation=True,
39        searchable = True,
40        deleteEmptyChapters = False,
41        allowable_content_types = ['text/html',],
42        allow_file_upload = False,
43        default_output_type = 'text/x-html-captioned',
44        default_content_type = 'text/html',
45        default=[{'type':'text_block', 'text':''}],
46        widget=ExerciseWidget(label = "Body text",
47            label_msgid = "label_bodytext",
48            i18n_domain = "lemill",
49            ),
50    ),
51))
52
53schema = material_schema + community_editing_schema + no_description + draft_schema + exercise_schema
54schema = schema.copy()
55schema.moveField('rights', pos='bottom')
56schema.moveField('language', after='bodyText')
57schema['title'].required = True
58schema.moveField('hideDrafts', before='rights')
59schema.moveField('editingMode', before='hideDrafts')
60
61blanks= re.compile("(\{.*?\})")
62
63class ExerciseMaterial(Material):
64    """Exercise page"""
65    schema = schema
66    meta_type = "ExerciseMaterial"
67    archetype_name = "ExerciseMaterial"
68    default_location = 'content/exercises'
69    security = ClassSecurityInfo()
70    security.declareObjectPublic()
71    aliases = {
72        '(Default)' : 'fullscreen_view',
73        'view'      : 'base_view',
74        'edit'      : 'base_edit',
75        'feedback'  : 'feedback_view',
76    }
77
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=to_unicode(piece.strip('{} '))
85                if previous_was_blank:
86                    answers[-1].append(answer)
87                else:
88                    answers.append([answer])
89            else:
90                pieces.append(to_unicode(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=[]
97        if readonly:
98            readonly=' readonly="1"'
99        else:
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=u"""<input type="text" value="%s"%s name="exercise_%s_answer_%s" id="exercise_%s_answer_%s" />""" % (to_unicode(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_checkbox_%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(to_unicode(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_checkbox' % 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)               
200       
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()})
264        else:
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')
292            return (msg, 'warn')
293
294    def prepareForPDF(self):
295        """ Replaces form fields with dotted lines """       
296        text=Material.prepareForPDF(self)
297        # Getting rid of submit for exercises
298        submit_pattern = re.compile('<fieldset class="visualNoPrint">.*?</fieldset>', re.MULTILINE | re.DOTALL | re.IGNORECASE)
299        text = re.sub(submit_pattern, '', text)
300        # Replace text inputs with some dots
301        input_text_pattern = re.compile('<input type="text" value="" name="exercise_.*?/>',re.DOTALL | re.IGNORECASE | re.MULTILINE)
302        text = re.sub(input_text_pattern, '.......... ', text)
303        # Replace textarea with some lines of dots
304        textarea_pattern = re.compile('<textarea.*?</textarea>', re.DOTALL | re.IGNORECASE | re.MULTILINE)
305        text = re.sub(textarea_pattern, '<br />'.join(('.'*80, '.'*80)), text)
306        return text
307
308registerType(ExerciseMaterial, PROJECTNAME)
Note: See TracBrowser for help on using the repository browser.