source: trunk/ExerciseMaterial.py @ 3119

Revision 3119, 13.5 KB checked in by jukka, 9 years ago (diff)

Working on sending emails from exercises.

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        # Check if this is a choice or multiple choice form
145        cor=0
146        multiple=False
147        for q,c in choices:
148            if c:
149                cor+=1
150        if cor!=1:
151            multiple=True
152       
153        if multiple:
154            for i, choice in enumerate(choices):
155                a=form.get('exercise_%s_checkbox_%s' % (index, i), '')
156                if a:
157                    results.append('[x]')
158                    if not choice[1]:
159                        results.append(' <- [ ]\t')
160                        wrongs+=1
161                    else:
162                        results.append('\t\t')
163                        corrects+=1
164                else:
165                    results.append('[ ]')
166                    if choice[1]:
167                        results.append(' <- [x]\t')
168                        wrongs+=1
169                    else:
170                        results.append('\t\t')
171                        corrects+=1               
172                results.append(to_unicode(choice[0]))
173                results.append('\n')
174        else:
175            a=int(form.get('exercise_%s_radio' % index, '-1'))         
176            for i, choice in enumerate(choices):
177                if a==i:
178                    results.append('[x]')
179                    if choice[1]:
180                        corrects=1
181                    else:
182                        wrongs=1                       
183                    results.append('\t\t')
184                else:
185                    results.append('[ ]')
186                    if choice[1]:
187                        results.append(' <- [x]\t')
188                        wrongs=1
189                    else:
190                        results.append('\t\t')
191                results.append(to_unicode(choice[0]))
192                results.append('\n')
193        results.append('\n')
194        return u''.join(results), corrects, wrongs               
195
196       
197    def checkOpenEndedFromForm(self, form, question, index):
198        """ Reports back the given answer """
199        results=[question,'\n\n']
200        a=form.get('exercise_%s_open_ended' % index, '')
201        results.append(a)
202        results.append('\n\n')
203        return u''.join(results)                           
204
205    def checkExercise(self):
206        """ Builds a text dump about evaluating the exercise, used for web feedback and email """
207        lt=getToolByName(self, 'lemill_tool')
208        form=self.REQUEST.form
209        corrects=0
210        wrongs=0
211        results=[to_unicode(self.Title()),'\n','-'*len(self.Title()),'\n\n']
212        task=0
213        for index, chapter in enumerate(self.getBodyText()):
214            text=lt.stripHTML(to_unicode(chapter['text']))
215            cs=ws=0
216            s=''
217            if chapter['type']=='fill_in_the_blanks':
218                s, cs, ws=self.checkFillInTheBlankAnswersFromForm(form, text,index)
219            elif chapter['type']=='open_ended':
220                s=self.checkOpenEndedFromForm(form, text, index)
221            elif chapter['type']=='multiple_choices':
222                s, cs, ws=self.checkMultipleChoiceFromForm(form, text, chapter['choices'], index)
223            if s:
224                task+=1
225                corrects+=cs
226                wrongs+=ws
227                results.append('%s. ' % task)
228                results.append(s)
229        if corrects or wrongs:
230            results.append('-----------------------------------------\n')
231            results.append('                %s / %s   = %2d%%\n' % (corrects, corrects+wrongs, ((corrects*1.0)/(corrects+wrongs))*100))
232        return u''.join(results)
233
234
235    def sendAnswers(self, REQUEST):
236        """ Send e-mail to a teacher """
237        msg=self._sendAnswers(REQUEST)
238        lt=getToolByName(self, 'lemill_tool')
239        lt.addPortalMessage(msg[0])
240        return self.feedback_view()
241
242    def _sendAnswers(self, REQUEST):
243        mhost=self.MailHost
244        body=[]
245        students_name = to_unicode(REQUEST.get('your_name',''))
246        students_email = REQUEST.get('students_email','')
247        teachers_email = REQUEST.get('teachers_email','')
248        captcha_c=REQUEST.get('recaptcha_challenge_field','')
249        captcha_r=REQUEST.get('recaptcha_response_field','')
250        captcha=self.lemill_tool.validateCaptcha(captcha_c, captcha_r)       
251        if not students_email or (teachers_email and not students_name):
252            msg = self.translate("text_mail_feedback_no_information", "You have not provided enough information to send an e-mail.",domain='lemill')
253            return (msg, 'warn')
254        if not captcha:
255            msg = self.translate("Invalid answer for humanity test.",domain='lemill')
256            return (msg, 'warn')
257        # Build message body
258        if teachers_email:
259            body.append(self.translate(u"""Hello,
260%(name)s has completed the LeMill exercise '%(exercise_title)s' at %(exercise_url)s.
261""",domain='lemill') % {'name':students_name, 'exercise_title':self.Title(), 'exercise_url':self.absolute_url()})
262        else:
263            body.append(self.translate(u"""Hello %(name)s,
264You have completed the LeMill exercise '%(exercise_title)s' at %(exercise_url)s.
265""",domain='lemill') % {'name':students_name, 'exercise_title':self.Title(), 'exercise_url':self.absolute_url()})
266        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'))
267        body.append('\n\n')
268        body.append(self.checkExercise())
269        body.append('\n\n')
270        body.append(self.translate("text_mail_feedback_message_body_regards", "Best regards, LeMill", domain='lemill'))                     
271        body=u''.join(body)
272        # Other email fields
273        if students_name:
274            msubject=self.translate("LeMill exercise '%s' by %s", domain='lemill') % (self.Title(), students_name)
275        else:
276            msubject=self.translate("LeMill exercise '%s' feedback", domain='lemill') % self.Title()
277        if teachers_email:
278            mto=teachers_email
279            mcc=students_email
280        else:
281            mto=students_email
282            mcc=''
283        # Send
284        try:
285            mhost.secureSend(body, mto, "LeMill <no-reply@lemill.net>", subject=msubject, mcc=mcc, charset='utf-8')
286            msg = self.translate("text_mail_feedback_message_sent", "The e-mail has been sent.",domain='lemill')
287            return (msg, 'mess')
288        except:
289            msg = self.translate("The e-mail could not be sent.",domain='lemill')
290            return (msg, 'warn')
291
292    def prepareForPDF(self):
293        """ Replaces form fields with dotted lines """       
294        text=Material.prepareForPDF(self)
295        # Getting rid of submit for exercises
296        submit_pattern = re.compile('<fieldset class="visualNoPrint">.*?</fieldset>', re.MULTILINE | re.DOTALL | re.IGNORECASE)
297        text = re.sub(submit_pattern, '', text)
298        # Replace text inputs with some dots
299        input_text_pattern = re.compile('<input type="text" value="" name="exercise_.*?/>',re.DOTALL | re.IGNORECASE | re.MULTILINE)
300        text = re.sub(input_text_pattern, '.......... ', text)
301        # Replace textarea with some lines of dots
302        textarea_pattern = re.compile('<textarea.*?</textarea>', re.DOTALL | re.IGNORECASE | re.MULTILINE)
303        text = re.sub(textarea_pattern, '<br />'.join(('.'*80, '.'*80)), text)
304        return text
305
306    def fixBrokenChoice(self):
307        """ If choice -type is mapped wrong, fix it """
308        old_values=self.getRawBodyText()
309        new_values=[]
310        fixed=False
311        for vald in old_values:
312            if vald['type']=='choice' and isinstance(vald['text'], list):
313                 # [text, [correct_answers], [wrong_answers]]
314                 broken_text=vald['text']
315                 vald['text']=broken_text[0]
316                 choices=[(broken_text[1][0], 1)]
317                 for item in broken_text[2]:
318                    choices.append((item, 0))
319                 vald['choices']=choices
320                 vald['type']='multiple_choices'
321                 fixed=True
322            new_values.append(vald)
323        if fixed:
324            self.setBodyText(new_values)
325
326
327registerType(ExerciseMaterial, PROJECTNAME)
Note: See TracBrowser for help on using the repository browser.