def build_assess_form(q, errorModels=(), bottom='', title='Assessing your answer'): 'return HTML for standard form for round 2' doc = webui.Document(title) if getattr(q, 'showAnswer', False) and getattr(q, 'explanation', False): doc.add_text('Answer: ' + q.explanation, 'B') doc.add_text('<HR>\n') doc.add_text( '''How does your answer compare with the right answer? If your answer was different, please briefly explain below how your reasoning differed.''', 'B') form = webui.Form('submit') form.append(webui.Input('qid', 'hidden', str(q.id))) form.append(webui.Input('stage', 'hidden', 'assess')) options = (('correct', 'Essentially the same.'), ('close', 'Close.'), ('different', 'Different.')) form.append(webui.RadioSelection('assessment', options)) if errorModels: form.append( "<br>\n<b>Did you make any of the following common errors?</b><br>\n" ) form.append( webui.CheckboxSelection('errors', list(enumerate(errorModels)))) form.append('''<br>\nIf you made an error not listed above, please indicate how your reasoning differed from the right idea. Otherwise, you may leave this blank, or ask any question about this problem that still puzzles you:<br>\n''') form.append(webui.Textarea('differences')) form.append('<br>\n') doc.append(form) doc.add_text(bottom) return str(doc)
def build_quizmode_form(formtitle='Switch to Quiz Mode?', explanation='''Quiz Mode presents all questions on a single form, so students answer them all and then submit the form once. This is designed for giving the students a test rather than the usual mode of walking them through one exercise at a time.''', title='Quiz', instructions='''Please answer all of the following questions. You must answer all questions. When you have answered all questions, click Go to submit your answers. Note that your submitted answers are final; you cannot resubmit answers again.''' ): doc = webui.Document(formtitle) doc.add_text(explanation) form = webui.Form('quizmode') form.append('<br>\nQuiz Title:\n') form.append(webui.Input('title', value='Quiz', size=50)) form.append('<br>\nInstructions for the students:<br>\n') form.append(webui.Textarea('instructions', value=instructions)) form.append('<br>\nWill this quiz be graded?<br>\n') options = (('yes', 'Graded'), ('', 'Ungraded')) form.append(webui.RadioSelection('graded', options)) form.append('<br>\n') doc.append(form) return str(doc)
def build_reconsider_form(qid, bottom='', title='Reconsidering your answer'): 'return HTML for standard form for round 2' doc = webui.Document(title) doc.add_text('''<B>Instructions</B>: As soon as your partner is ready, please take turns explaining why you think your answer is right, approximately one minute each. Then answer the following questions:<BR> ''') form = webui.Form('submit') form.append(webui.Input('qid', 'hidden', str(qid))) form.append(webui.Input('stage', 'hidden', 'reconsider')) d = dict( unchanged='I still prefer my original answer.', switched= "I've decided my partner's answer is better (enter his/her name below)." ) form.append(webui.RadioSelection('status', d.items(), selected='unchanged')) add_confidence_choice(form) form.append( "<br>\nYour partner's username (only needed if you prefer their answer):" ) form.append(webui.Input('partner')) form.append('<br>\n') doc.append(form) doc.add_text(bottom) return str(doc)
def start_admin(self, starttimer=0, showresp=''): doc = webui.Document('Socraticqs Admin') doc.add_text(self.title, 'H1') doc.add_text(self.text, 'BIG') doc.add_text('<HR>\n') if starttimer: # start the timer self.starttime = time.time() if hasattr(self, 'starttime'): # show timer, progress stats elapsed = int(time.time() - self.starttime) doc.add_text('Time since start: %d:%02d' % (elapsed / 60, elapsed % 60)) doc.add_text(' (updates every %d sec)' % self.refresh) doc.add_text('<BR>\n') t = webui.Table('Student Answers So Far', ( 'Just guessing', 'Not quite sure', 'Pretty sure', '(not yet)', )) doc.append(t) if showresp: doc.add_text('<BR>\n(<A HREF="qadmin">hide answers</A>)<BR>\n') else: doc.add_text( '<BR>\n(<A HREF="qadmin?showresp=1">show answers</A>)<BR>\n' ) counts = [0, 0, 0] for r in self.responses.values(): if showresp: doc.add_text(str(r), 'LI') counts[r.confidence] += 1 counts.append(len(self.courseDB.logins) - len(self.responses)) t.append(counts) doc.add_text('''<BR><B>Instructions</B>: when you feel enough students have responded (totally up to you), tell the students what stage to proceed to. E.g. you could ask them to discuss their answer with their neighbor (and if you wish, tell them to click the DISCUSS link to report whether this changed their minds). Or you could proceed directly to the ASSESS stage to present the solution and have the students self-assess. Note: you may use the navigation bar below to jump forward to another stage or question at any time.''') doc.head.append( '<meta http-equiv="Refresh" content="%d; url=qadmin?showresp=%s">\n' % (self.refresh, showresp)) else: # show instructions, GO button doc.add_text('''<B>Instructions</B>: present the question to the students. When you tell them to start, click the Go button to begin the timer. For a concept test, typically give them a minute to think about the question, and a minute or two to enter an answer.''') form = webui.Form('qadmin') form.append(webui.Input('starttimer', 'hidden', '1')) doc.append(form) doc.add_text(self.server.admin_nav()) return str(doc)
def assess_admin(self, showresp=''): if not getattr(self, 'showAnswer', False): self.showAnswer = True self._viewHTML['assess'] = \ forms.build_assess_form(self, self.errorModels, self._navHTML) doc = webui.Document('Socraticqs Admin') doc.add_text(self.title + ' Answer', 'H1') if hasattr(self, 'correctAnswer'): doc.add_text(str(self.correctAnswer), 'BIG') doc.add_text('<HR>\n') doc.add_text(self.explanation, 'B') doc.add_text('<HR>\n') if hasattr(self, 'starttime'): # show timer, progress stats elapsed = int(time.time() - self.starttime) doc.add_text('Time since start: %d:%02d' % (elapsed / 60, elapsed % 60)) doc.add_text(' (updates every %d sec)' % self.refresh) doc.add_text('<BR>\n') t = webui.Table('Self-Assessments So Far', ( 'Different', 'Close', 'Correct', '(not yet)', )) doc.append(t) if showresp: doc.add_text( '<BR>\n(<A HREF="qassess">hide self-assessments</A>)<BR>\n' ) else: doc.add_text( '<BR>\n(<A HREF="qassess?showresp=1">show self-assessments</A>)<BR>\n' ) counts = {} for r in self.responses.values(): if showresp and getattr(r, 'criticisms', False): doc.add_text(r.criticisms, 'LI') try: counts[r.reasons] = counts.get(r.reasons, 0) + 1 except AttributeError: pass t.append((counts.get('different', 0), counts.get('close', 0), counts.get('correct', 0), len(self.responses) - sum(counts.values()))) doc.add_text('''<BR><B>Instructions</B>: present the answer to the students, and ask them to click ASSESS to enter their self-assessment. Note: you may click START below to jump forward to another question at any time.''') doc.head.append( '<meta http-equiv="Refresh" content="%d; url=qassess?showresp=%s">\n' % (self.refresh, showresp)) doc.add_text(self.server.admin_nav()) return str(doc)
def prototype_form(self, offset=0, maxview=None, title='Categorize Responses'): offset = int(offset) unclustered = self.count_unclustered() if unclustered == 0: return self.cluster_report() doc = webui.Document(title) doc.add_text('''<B>Instructions</B>: if you wish, you can choose individual responses as distinct categories of answers, and then ask the students to assign themselves to these categories. However, this is purely <B>optional</B>. Click here to <A HREF="prototype_form">UPDATE</A> for the latest results.''') if self.categories: # not empty doc.add_text('%d Categories' % len(self.categories), 'h1') for r in self.categories: if r == self.correctAnswer: doc.add_text('<B>correct</B>: ' + str(r), 'LI') else: doc.add_text(str(r), 'LI') doc.add_text('%d Uncategorized Responses' % unclustered, 'h1') doc.add_text('''Choose one or more responses as new, distinct categories of student answers:<br> ''') l = list(self.iter_unclustered())[offset:] if not maxview: try: maxview = self.maxview except AttributeError: maxview = 10 maxview = int(maxview) if maxview and len(l) > maxview: l = l[:maxview] form = webui.Form('add_prototypes') for r in l: form.append( webui.RadioSelection('resp_' + str(r.uid), (('add', str(r)), ))) doc.append(form) if offset > 0: doc.add_text( '<A HREF="prototype_form?offset=%d&maxview=%d">[Previous %d]</A>\n' % (max(0, offset - maxview), maxview, maxview)) if maxview and unclustered > offset + maxview: doc.add_text( '<A HREF="prototype_form?offset=%d&maxview=%d">[Next %d]</A>\n' % (offset + maxview, maxview, maxview)) doc.add_text('''<br>If you want to "declare victory", click here to proceed to the <A HREF="cluster_report">cluster report</A>.''') doc.add_text(self.server.admin_nav()) return str(doc)
def build_vote_form(self, form=None, title='Vote for the best answer', text='''<h1>Vote</h1> Which of the following answers do you think is correct? <br>'''): doc = webui.Document(title) doc.add_text(text) if form is None: form = self.get_choice_form() doc.append(form) doc.add_text(self._navHTML) return str(doc)
def analysis(self, title='Final Results'): if self.responses: f = 100. / len(self.responses) else: # avoid division by zero error f = 1. def perc(d, k): return '%1.0f%%' % (d.get(k, 0) * f) doc = webui.Document(title) doc.add_text('''<B>Instructions</B>: This table shows the fraction of students that got the correct answer, or gave no response (NR). Uncategorized responses are not shown. Click here to <A HREF="analysis">UPDATE</A> for the latest results.''') d1, d2, d3 = self.count_rounds() t = webui.Table('%d Responses' % len(self.responses), ('answer', 'initial', 'revised', 'final')) for i, category in enumerate(self.list_categories()): a = letters[i] if category == self.correctAnswer: # bold the correct answer a = '<B>' + a + '</B> (correct)' t.append( (a, perc(d1, category), perc(d2, category), perc(d3, category))) t.append(('NR', '0%', perc(d2, None), perc(d3, None))) doc.append(t) doc.add_text('Reasons and Critiques', 'h1') for i, category in enumerate(self.categoriesSorted): doc.add_text('Answer ' + letters[i], 'h2') doc.add_text(str(category)) if self.categories[category]: doc.add_text('Reasons Given for this Answer', 'h3') for r in self.categories[category]: reasons = getattr(r, 'reasons', None) if reasons: doc.add_text(reasons, 'LI') l = [] for r in self.responses.values(): if getattr(r, 'critiqueTarget', None) == category \ and getattr(r, 'criticisms', None): l.append(r.criticisms) if l: doc.add_text('Critiques of this Answer', 'h3') for s in l: doc.add_text(s, 'LI') doc.add_text('<HR>\n') doc.add_text(self.server.admin_nav()) return str(doc)
def build_self_critique_form(self, title='Critique your original answer', text='''<h1>Critique</h1> Briefly state what you think was wrong with your original answer: <br>''', action='self_critique'): doc = webui.Document(title) doc.add_text(text) form = webui.Form('submit') form.append(webui.Input('qid', 'hidden', str(self.id))) form.append(webui.Input('stage', 'hidden', action)) form.append(webui.Textarea('criticisms')) form.append('<br>\n') doc.append(form) doc.add_text(self._navHTML) return str(doc)
def login_form( action='login', text='Please login to Socraticqs using your username and Student ID:<br>\n', registerText='''<br> If you have never logged in, click here to <A HREF="register_form">register</A>. '''): doc = webui.Document('Login') doc.add_text(text) form = webui.Form(action) form.append(webui.Data('username:'******'username', size=10)) form.append(webui.Data('<br>\nUID:')) form.append(webui.Input('uid', 'password', size=10)) doc.append(form) doc.add_text(registerText) return str(doc)
def build_cluster_form(self, title='Cluster Your Answer'): doc = webui.Document(title) doc.add_text('''Either choose the answer that basically matches your original answer, or choose <B>None of the Above</B><br> ''') form = webui.Form('submit') form.append(webui.Input('qid', 'hidden', str(self.id))) form.append(webui.Input('stage', 'hidden', 'cluster')) l = [] for i, r in enumerate(self.list_categories()): l.append((i, str(r))) l.append(('none', 'None of the above')) form.append(webui.RadioSelection('match', l)) form.append('<br>\n') doc.append(form) doc.add_text(self._navHTML) return str(doc)
def _admin_page(self): doc = webui.Document('Socraticqs Console') doc.add_text('%d students logged in.' % len(self.courseDB.logins)) doc.add_text('Concept Tests', 'h1') for i, q in enumerate(self.courseDB.questions): doc.add_text( '''<A HREF="start_question?q=%d" TITLE="Start the students on this question">%s</A>''' % (i, q.title), 'LI') doc.add_text('''<B>Instructions</B>: click on a question to start the students on that question. At any time you may use the navigation bar at the bottom of the page to go to whatever stage you wish.<BR> <BR> Or click here to switch to <A HREF="quiz_form">Quiz Mode</A>.''') doc.add_text(self.admin_nav()) return str(doc)
def register_form(action='register', text='''Please register by choosing a username, and entering your full name and Student ID:<br>\n''', loginText='''<br> If you have already registered, click here to <A HREF="login_form">login</A>. '''): doc = webui.Document('Register') doc.add_text(text) form = webui.Form(action) form.append(webui.Data('username:'******'username', size=10)) form.append(webui.Data('<br>\nFull Name (e.g. Joe Smith):')) form.append(webui.Input('fullname', size=20)) form.append(webui.Data('<br>\nUID:')) form.append(webui.Input('uid', 'password', size=10)) form.append(webui.Data('<br>\nRe-enter UID:')) form.append(webui.Input('uid2', 'password', size=10)) doc.append(form) doc.add_text(loginText) return str(doc)
def cluster_report(self): fmt = '%(answer)s<br><b>(%(tag)s answer chosen by %(n)d students)</b>' doc = webui.Document('Clustering Complete') doc.add_text( 'Done: %d responses in %d categories:' % (len(self.responses), len(self.categories)), 'h1') try: p = len(self.categories.get(self.correctAnswer, ())) * 100. \ / len(self.responses) except AttributeError: doc.add_text('Choose which answer is correct:') doc.append( self.get_choice_form('correct', False, 0, fmt, useSubmit=False)) doc.add_text('''<br>If none of these are correct, click here to add the <A HREF="add_correct">correct answer</A> given by the instructor.''') else: doc.add_text('%2.0f%% of students got the correct answer<hr>' % p) doc.append(self.get_choice_form('correct', False, 0, fmt)) doc.add_text(self._gotoVoteHTML) self.init_vote() doc.add_text(self.server.admin_nav()) return str(doc)
def __init__(self, questionID, title, text, explanation, nerror, *args, **kwargs): self.id = questionID self.title = title self.text = text self.explanation = explanation nerror = int(nerror) self.errorModels = args[:nerror] args = args[nerror:] # skip past error models try: self.rootPath = kwargs['rootPath'] del kwargs['rootPath'] except KeyError: self.rootPath = '' self.refresh = 15 self.categories = {} for attr in ('hasReasons', 'isClustered', 'noMatch', 'hasFinalVote', 'hasCritique'): setattr(self, attr, set()) # initialize answer counters doc = webui.Document(title) self.doc = doc doc.add_text(text) form = webui.Form('submit') form.append(webui.Input('qid', 'hidden', str(self.id))) form.append(webui.Input('stage', 'hidden', 'answer')) self.build_form(form, *args, **kwargs) try: self.correctAnswer.prototype = self.correctAnswer except AttributeError: pass doc.append(form) doc.add_text(self._navHTML) self.responses = {} d = {} for attr in self._stages: # initialize submission action dict d[attr] = getattr(self, attr) self.submitStages = d self._afterURL = self.get_url('assess') self._viewHTML = { 'answer': str(doc), 'reconsider': forms.build_reconsider_form(questionID, self._navHTML), 'assess': '''Your instructor has not yet started the ASSESS phase. When your instructor asks you to, please click here to <A HREF="%s">ASSESS</A> your answer.%s''' \ % (self.get_url('assess'), self._navHTML) } self._clusterFormHTML = \ '''No categories have yet been added. When your instructor asks you to, please click here to continue to <A HREF="%s">categorize your answer</A>.%s''' \ % (self.get_url('cluster'), self._navHTML) self._matchedHTML = \ '''Your answer already matches a category. When your instructor asks you to, please click here to continue to the <A HREF="%s">final vote</A>.%s''' \ % (self.get_url('vote'), self._navHTML) self._noResponseHTML = \ '''Sorry, you first need to submit an answer to this question, because I can find no record of your answer. Please click the START link below to continue.''' \ + self._navHTML