source: trunk/ExerciseMaterial.py @ 2410

Revision 2410, 12.7 KB checked in by anonymous, 11 years ago (diff)

Portlets are much more efficient and simpler now and work independent of Plone's portlet system. The logic is in Resources/getPortletDetails, portlets itself just either show or not show their parts.

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
22
23from config import PROJECTNAME, MODIFY_CONTENT, VIEW, to_unicode
24from FieldsWidgets import ChapterField, ChapterWidget, StringWidget
25from SharedMetadata import *
26from Material import Material
27from permissions import MODIFY_CONTENT
28from random import shuffle
29from Products.LeMill import LeMillMessageFactory as _
30
31import re
32
33schema = BaseSchema + Schema((
34   ChapterField('bodyText',
35        accessor="getBodyText",
36        edit_accessor = 'getRawBodyText',
37        mutator = "setBodyText",
38        index='ZCTextIndex:schema',
39        index_method = 'getOnlyText',
40        searchable = True,
41        deleteEmptyChapters = False,
42        allowable_content_types = ['text/html',],
43        allow_file_upload = False,
44        default_output_type = 'text/x-html-captioned',
45        default_content_type = 'text/html',
46        default=[('','guidelines'),],
47        widget=ExerciseWidget(label = "Body text",
48            label_msgid = "label_bodytext",
49            i18n_domain = "lemill",
50            ),
51    ),
52))
53
54schema = schema + tags + language_schema + group_sharing + subject_area_schema + target_group_schema + license_schema + coverImage + lemill_metadata_mods + no_description + author_schema + deletionReason + version_schema + translation_schema + latest_edit_schema + score + state
55
56schema = schema.copy()
57schema.moveField('rights', pos='bottom')
58schema.moveField('language', after='bodyText')
59schema['title'].required = True
60
61fill_in_the_blanks=re.compile(r"""(?P<filler>({.*?})+)""",  re.IGNORECASE)
62
63class ExerciseMaterial(Material):
64    """Exercise page"""
65
66    schema = schema
67
68    meta_type = "ExerciseMaterial"
69    archetype_name = "ExerciseMaterial"
70
71    security = ClassSecurityInfo()
72    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        dump= '\n'.join([x[0] for x in values if x[1] not in ['media_piece', 'multiple_choices']])
82        return dump
83
84    def getOnlyRawText(self):
85        field=self.getField('bodyText')
86        values = field.getRaw(self)
87        dump= '\n'.join([x[0] for x in values if x[1] not in ['media_piece', 'multiple_choices']])
88        return dump
89
90
91    def isCorrectAnswer(self, chapter, answer_index):
92        """ given a chapter having a multiple choice question, is this answer in correct answers? """
93        return answer_index < len(chapter[1])
94       
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')
102
103    def getAllAnswers(self, chapter):
104        """ Will return the list of the shuffled combined correct and incorrect answers """
105        # Chapter should have the structure: [question,correct_list,incorrect_list]
106        all_answers = chapter[1] + chapter[2]
107        extended_answers = []
108        for numerated_answer in enumerate(all_answers):
109            extended_answers.append(numerated_answer)
110        shuffle(extended_answers)
111        return extended_answers
112
113    def get_values_from_fitbs(self, chapter_index):
114        """ find words in {}:s and return them as dict """
115       
116        text=self.getBodyText()
117        text=text[chapter_index][0]
118        matches=fill_in_the_blanks.findall(text)
119        results={}
120        index=0
121        for m in matches:
122            answer=m[0]
123            answer=answer.strip(' {}')
124            answer=answer.split('}{')
125            results['exercise_%s_answer_%s' % (chapter_index, index)]=answer
126            index=index+1
127        return results
128           
129    def replace_blanks_with_input_tag(self, chapter_index, answers=True, readonly=True):
130        """ find words in { } and replace them with input boxes """
131        replacement="""<input type="text" value="%s"%s name="exercise_%s_answer_%s" id="exercise_%s_answer_%s" />"""
132        self.iterator=0
133       
134        def rep(match):
135            answer_index=self.iterator
136            if answers:
137                value=match.group('filler').strip(' {}')
138                value=value.replace('}{','/')
139            else:
140                value=""
141            self.iterator+=1
142            return replacement % (value, readonly, chapter_index, answer_index, chapter_index, answer_index)
143
144        if readonly:
145            readonly=' readonly="1"'
146        else:
147            readonly=''           
148        text=self.getBodyText()
149        text=text[chapter_index][0]
150        text=fill_in_the_blanks.sub(rep, text)
151        del self.iterator
152        return text
153
154    def replace_blanks_with_your_answers(self, text, your_answerdict, i):
155        """ find braces {text} and add own answers before them in brackets [mytext]{text} """
156        self.iterator=0
157       
158        def rep(match):
159            value=match.group('filler')           
160            your_answer=your_answerdict.get('exercise_%s_answer_%s' % (i, self.iterator), '')
161            self.iterator+=1
162            return '[%s]%s' % (your_answer, value)
163        text=fill_in_the_blanks.sub(rep, text)
164        del self.iterator
165        return text
166
167
168
169    def sendAnswers(self, REQUEST):
170        """ Send e-mail to a teacher """
171        putils = getToolByName(self,'plone_utils')
172        letool = getToolByName(self,'lemill_tool')
173        mhost = putils.getMailHost()
174        mbody = u''
175        students_name = REQUEST.get('your_name','')
176        students_email = REQUEST.get('students_email','')
177        teachers_email = REQUEST.get('teachers_email','')
178
179        if not students_email or (teachers_email and not students_name):
180            msg = _(u"You have not provided enough information to send an e-mail.")
181            putils.addPortalMessage(msg, type='warn')
182            return REQUEST.RESPONSE.redirect(self.absolute_url())
183
184        if not self.lemill_tool.validateCaptcha(REQUEST.get('captcha')):
185            msg = _(u"Invalid answer for humanity test.")
186            putils.addPortalMessage(msg, type='warn')
187            return REQUEST.RESPONSE.redirect(self.absolute_url())
188       
189        exercise_body = self.getBodyText()
190
191
192        # Now we should compose the message body       
193        if teachers_email:
194            mbody += _(u"""
195Hello,
196
197%(name)s has completed the LeMill exercise '%(exercise_title)s' at %(exercise_url)s.
198""") % {'name':to_unicode(students_name), 'exercise_title':to_unicode(self.Title()), 'exercise_url':to_unicode(self.absolute_url())}
199        else:
200            mbody += _(u"""
201Hello %(name)s,
202
203You have completed the LeMill exercise '%(exercise_title)s' at %(exercise_url)s.
204""") % {'name':to_unicode(students_name), 'exercise_title':to_unicode(self.Title()), 'exercise_url':to_unicode(self.absolute_url())}
205
206        mbody += _(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.")
207        # Message body is composed chapter by chapter
208        i=0
209        for (chapter_text, chapter_type) in exercise_body:
210            if chapter_type=='multiple_choices':
211                answered_right = 0
212                answered_wrong = 0
213                question = chapter_text[0]
214                correct_answers = chapter_text[1]
215                answers_total = correct_answers+chapter_text[2]
216                my_answers=[]
217                # Correct answers have small index number
218                for a in range(len(correct_answers)):
219                    answer_value = REQUEST.get('exercise_%s_checkbox_%s' % (i,a),'')
220                    if answer_value:
221                        my_answers.append(answers_total[a])
222                        answered_right+=1
223                    else:
224                        answered_wrong+=1
225                # These are all wrong answers
226                for a in range(len(correct_answers),len(answers_total)):
227                    answer_value = REQUEST.get('exercise_%s_checkbox_%s' % (i,a),'')
228                    if answer_value:
229                        my_answers.append(answers_total[a])
230                        answered_wrong+=1
231                    else:
232                        answered_right+=1
233                percentage=int((float(answered_right)/(answered_wrong+answered_right))*100)
234                mbody += _(u"""
235
236Your answer to question '%(question)s' was:
237%(my_answers)s
238The correct answer is:
239%(correct_answers)s
240You got %(percentage)s%% correct!
241""") % {'question':to_unicode(question), 'my_answers':to_unicode(', '.join(my_answers)), 'correct_answers':to_unicode(', '.join(correct_answers)), 'percentage':percentage}
242
243            elif chapter_type == 'fill_in_the_blanks':
244                correct_answer_dict=self.get_values_from_fitbs(i)
245                correct_answers_n=0
246                my_answer_dict={}
247                all_answers_n=len(correct_answer_dict.keys())
248                for it in range(all_answers_n):
249                    my_answer=REQUEST.get('exercise_%s_answer_%s' % (i,it),'')
250                    correct_answer=[to_unicode(c.strip()) for c in correct_answer_dict['exercise_%s_answer_%s' % (i,it)]]
251                    if to_unicode(my_answer.strip()) in correct_answer:
252                        correct_answers_n+=1
253                    my_answer_dict['exercise_%s_answer_%s' % (i,it)]=my_answer
254                exercise_text=self.replace_blanks_with_your_answers(chapter_text, my_answer_dict, i)
255                exercise_text=letool.html_to_text(exercise_text)
256                mbody += _(u"""
257Answer entered by student is in the square brackets. The correct answer is in the logical brackets.
258In the following fill-in-the-blanks exercise your answers and correct answers were:
259%(exercise_text)s
260
261You got %(correct_answers_n)s/%(all_answers_n)s correct!
262""") % {'exercise_text':to_unicode(exercise_text), 'correct_answers_n':correct_answers_n, 'all_answers_n':all_answers_n}           
263
264            elif chapter_type == 'open_ended':
265                students_answer = REQUEST.get('exercise_%s_answer' % i, '')
266                mbody += _(u"""
267Your answer to question '%(question)s' was:
268%(answer)s
269
270Your teacher will give you feedback on your answer.
271""") % {'question':to_unicode(chapter_text), 'answer':to_unicode(students_answer)}
272            i+=1 # <--- remember to increase index
273           
274        mbody += _(u"\n\nBest regards, LeMill")
275        mbody = mbody.encode('utf-8')
276
277        if teachers_email:
278            message_from = "%s <%s>" % (students_name, students_email)
279            message_subject = "LeMill exercise '%s' by %s" % (self.Title(),students_name)
280            try:
281                mhost.secureSend(mbody, mto=teachers_email, mfrom=message_from, subject=message_subject, mcc=students_email, charset='utf-8')
282                msg = _(u"The e-mail has been sent to you and a teacher.")
283                putils.addPortalMessage(msg)
284                return REQUEST.RESPONSE.redirect(self.absolute_url())
285            except:
286                msg = _(u"The e-mail could not be sent.")
287                putils.addPortalMessage(msg, type='warn')
288                return REQUEST.RESPONSE.redirect(self.absolute_url())
289        else:
290            message_from = "%s <%s>" % ('LeMill', students_email)
291            message_subject = "LeMill exercise '%s' feedback" % (self.Title())
292            try:
293                mhost.secureSend(mbody, mto=students_email, mfrom=message_from, subject=message_subject, charset='utf-8')
294                msg = _(u"The e-mail has been sent to you.")
295                putils.addPortalMessage(msg)
296                return REQUEST.RESPONSE.redirect(self.absolute_url())
297            except:
298                msg = _(u"The e-mail could not be sent.")
299                putils.addPortalMessage(msg, type='warn')
300                return REQUEST.RESPONSE.redirect(self.absolute_url())
301
302
303registerType(ExerciseMaterial, PROJECTNAME)
Note: See TracBrowser for help on using the repository browser.