From f0b4ac3ab8183ff2af39595bffa87e29c2dc61cd Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 5 Nov 2012 14:11:29 -0500 Subject: [PATCH 001/234] add open ended input type and html template --- .idea/.name | 1 + .idea/encodings.xml | 5 + .../inspectionProfiles/profiles_settings.xml | 7 + .idea/misc.xml | 8 + .idea/mitx.iml | 9 + .idea/modules.xml | 9 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 7 + .idea/workspace.xml | 516 ++++++++++++++++++ common/lib/capa/capa/inputtypes.py | 49 ++ .../capa/capa/templates/openendedinput.html | 32 ++ 11 files changed, 648 insertions(+) create mode 100644 .idea/.name create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/mitx.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 common/lib/capa/capa/templates/openendedinput.html diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000000..36d4bf6d51 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +mitx \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000000..e206d70d85 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..c60c33bb47 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..e746fb7776 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + $APPLICATION_HOME_DIR$/lib/pycharm.jar!/resources/html5-schema/html5.rnc + + + + diff --git a/.idea/mitx.iml b/.idea/mitx.iml new file mode 100644 index 0000000000..2d50e7b1fc --- /dev/null +++ b/.idea/mitx.iml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000..7eaf1301cf --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000000..922003b843 --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..c80f2198b5 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000000..ef9844844f --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1351640399572 + 1351640399572 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index ec1cda83c7..70fe5dd6c8 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -733,3 +733,52 @@ class ChemicalEquationInput(InputTypeBase): return {'previewer': '/static/js/capa/chemical_equation_preview.js',} registry.register(ChemicalEquationInput) + +#----------------------------------------------------------------------------- + +class OpenEndedInput(InputTypeBase): + """ + A text area input for code--uses codemirror, does syntax highlighting, special tab handling, + etc. + """ + + template = "openendedinput.html" + tags = ['openendedinput'] + + # pulled out for testing + submitted_msg = ("Submitted. As soon as your submission is" + " graded, this message will be replaced with the grader's feedback.") + + @classmethod + def get_attributes(cls): + """ + Convert options to a convenient format. + """ + return [Attribute('rows', '30'), + Attribute('cols', '80'), + Attribute('hidden', ''), + ] + + def setup(self): + """ + Implement special logic: handle queueing state, and default input. + """ + # if no student input yet, then use the default input given by the problem + if not self.value: + self.value = self.xml.text + + # Check if problem has been queued + self.queue_len = 0 + # Flag indicating that the problem has been queued, 'msg' is length of queue + if self.status == 'incomplete': + self.status = 'queued' + self.queue_len = self.msg + self.msg = self.submitted_msg + + def _extra_context(self): + """Defined queue_len, add it """ + return {'queue_len': self.queue_len,} + +registry.register(OpenEndedInput) + +#----------------------------------------------------------------------------- \ No newline at end of file diff --git a/common/lib/capa/capa/templates/openendedinput.html b/common/lib/capa/capa/templates/openendedinput.html new file mode 100644 index 0000000000..697bff8082 --- /dev/null +++ b/common/lib/capa/capa/templates/openendedinput.html @@ -0,0 +1,32 @@ +
+ + +
+ % if status == 'unsubmitted': + Unanswered + % elif status == 'correct': + Correct + % elif status == 'incorrect': + Incorrect + % elif status == 'queued': + Queued + + % endif + + % if hidden: +
+ % endif + +

${status}

+
+ + + +
+ ${msg|n} +
+
From 329ea7ab7200db8662767758b95cfd9aa3378a6a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 5 Nov 2012 14:12:22 -0500 Subject: [PATCH 002/234] removed .idea files --- .gitmodules | 0 .idea/.name | 1 - .idea/encodings.xml | 5 - .../inspectionProfiles/profiles_settings.xml | 7 - .idea/misc.xml | 8 - .idea/mitx.iml | 9 - .idea/modules.xml | 9 - .idea/scopes/scope_settings.xml | 5 - .idea/vcs.xml | 7 - .idea/workspace.xml | 516 ------------------ lms/static/admin/css/ie.css | 63 --- 11 files changed, 630 deletions(-) delete mode 100644 .gitmodules delete mode 100644 .idea/.name delete mode 100644 .idea/encodings.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/mitx.iml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/scopes/scope_settings.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/workspace.xml delete mode 100644 lms/static/admin/css/ie.css diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 36d4bf6d51..0000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -mitx \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e206d70d85..0000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index c60c33bb47..0000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index e746fb7776..0000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - $APPLICATION_HOME_DIR$/lib/pycharm.jar!/resources/html5-schema/html5.rnc - - - - diff --git a/.idea/mitx.iml b/.idea/mitx.iml deleted file mode 100644 index 2d50e7b1fc..0000000000 --- a/.idea/mitx.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 7eaf1301cf..0000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b843..0000000000 --- a/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index c80f2198b5..0000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index ef9844844f..0000000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,516 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1351640399572 - 1351640399572 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lms/static/admin/css/ie.css b/lms/static/admin/css/ie.css deleted file mode 100644 index fd00f7f204..0000000000 --- a/lms/static/admin/css/ie.css +++ /dev/null @@ -1,63 +0,0 @@ -/* IE 6 & 7 */ - -/* Proper fixed width for dashboard in IE6 */ - -.dashboard #content { - *width: 768px; -} - -.dashboard #content-main { - *width: 535px; -} - -/* IE 6 ONLY */ - -/* Keep header from flowing off the page */ - -#container { - _position: static; -} - -/* Put the right sidebars back on the page */ - -.colMS #content-related { - _margin-right: 0; - _margin-left: 10px; - _position: static; -} - -/* Put the left sidebars back on the page */ - -.colSM #content-related { - _margin-right: 10px; - _margin-left: -115px; - _position: static; -} - -.form-row { - _height: 1%; -} - -/* Fix right margin for changelist filters in IE6 */ - -#changelist-filter ul { - _margin-right: -10px; -} - -/* IE ignores min-height, but treats height as if it were min-height */ - -.change-list .filtered { - _height: 400px; -} - -/* IE doesn't know alpha transparency in PNGs */ - -.inline-deletelink { - background: transparent url(../img/inline-delete-8bit.png) no-repeat; -} - -/* IE7 doesn't support inline-block */ -.change-list ul.toplinks li { - zoom: 1; - *display: inline; -} \ No newline at end of file From de6e913d983f2f1e7cccf5694f318352b55dc7b1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 5 Nov 2012 14:51:51 -0500 Subject: [PATCH 003/234] copy code response input type and modify a bit --- common/lib/capa/capa/responsetypes.py | 204 +++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 20e7c43577..53c9637373 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1814,6 +1814,207 @@ class ImageResponse(LoncapaResponse): return (dict([(ie.get('id'), ie.get('rectangle')) for ie in self.ielements]), dict([(ie.get('id'), ie.get('regions')) for ie in self.ielements])) #----------------------------------------------------------------------------- + +class OpenEndedResponse(LoncapaResponse): + """ + Grade student open ended responses using an external queueing server, called 'xqueue' + + Expects 'xqueue' dict in ModuleSystem with the following keys that are needed by OpenEndedResponse: + system.xqueue = { 'interface': XqueueInterface object, + 'callback_url': Per-StudentModule callback URL + where results are posted (string), + 'default_queuename': Default queuename to submit request (string) + } + + External requests are only submitted for student submission grading + (i.e. and not for getting reference answers) + """ + + response_tag = 'openendedresponse' + allowed_inputfields = ['openendedinput'] + max_inputfields = 1 + + def setup_response(self): + ''' + Configure OpenEndedResponse from XML. + ''' + xml = self.xml + # TODO: XML can override external resource (grader/queue) URL + self.url = xml.get('url', None) + self.queue_name = xml.get('queuename', self.system.xqueue['default_queuename']) + + # VS[compat]: + # Check if XML uses the ExternalResponse format or the generic OpenEndedResponse format + oeparam = self.xml.find('openendedparam') + self._parse_openendedresponse_xml(oeparam) + + def _parse_openendedresponse_xml(self,oeparam): + ''' + Parse OpenEndedResponse XML: + self.initial_display + self.answer (an answer to display to the student in the LMS) + self.payload + ''' + # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload + grader_payload = oeparam.find('grader_payload') + grader_payload = grader_payload.text if grader_payload is not None else '' + self.payload = {'grader_payload': grader_payload} + + answer_display = oeparam.find('answer_display') + if answer_display is not None: + self.answer = answer_display.text + else: + self.answer = 'No answer provided.' + + initial_display = oeparam.find('initial_display') + if initial_display is not None: + self.initial_display = initial_display.text + else: + self.initial_display = '' + + def get_score(self, student_answers): + try: + # Note that submission can be a file + submission = student_answers[self.answer_id] + except Exception as err: + log.error('Error in OpenEndedResponse %s: cannot get student answer for %s;' + ' student_answers=%s' % + (err, self.answer_id, convert_files_to_filenames(student_answers))) + raise Exception(err) + + # Prepare xqueue request + #------------------------------------------------------------ + + qinterface = self.system.xqueue['interface'] + qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) + + anonymous_student_id = self.system.anonymous_student_id + + # Generate header + queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + + anonymous_student_id + + self.answer_id) + xheader = xqueue_interface.make_xheader(lms_callback_url=self.system.xqueue['callback_url'], + lms_key=queuekey, + queue_name=self.queue_name) + + self.context.update({'submission': submission}) + + contents = self.payload.copy() + + # Metadata related to the student submission revealed to the external grader + student_info = {'anonymous_student_id': anonymous_student_id, + 'submission_time': qtime, + } + contents.update({'student_info': json.dumps(student_info)}) + + # Submit request. When successful, 'msg' is the prior length of the queue + contents.update({'student_response': submission}) + (error, msg) = qinterface.send_to_queue(header=xheader, + body=json.dumps(contents)) + + # State associated with the queueing request + queuestate = {'key': queuekey, + 'time': qtime,} + + cmap = CorrectMap() + if error: + cmap.set(self.answer_id, queuestate=None, + msg='Unable to deliver your submission to grader. (Reason: %s.)' + ' Please try again later.' % msg) + else: + # Queueing mechanism flags: + # 1) Backend: Non-null CorrectMap['queuestate'] indicates that + # the problem has been queued + # 2) Frontend: correctness='incomplete' eventually trickles down + # through inputtypes.textbox and .filesubmission to inform the + # browser to poll the LMS + cmap.set(self.answer_id, queuestate=queuestate, correctness='incomplete', msg=msg) + + return cmap + + def update_score(self, score_msg, oldcmap, queuekey): + + (valid_score_msg, correct, points, msg) = self._parse_score_msg(score_msg) + if not valid_score_msg: + oldcmap.set(self.answer_id, + msg='Invalid grader reply. Please contact the course staff.') + return oldcmap + + correctness = 'correct' if correct else 'incorrect' + + # TODO: Find out how this is used elsewhere, if any + self.context['correct'] = correctness + + # Replace 'oldcmap' with new grading results if queuekey matches. If queuekey + # does not match, we keep waiting for the score_msg whose key actually matches + if oldcmap.is_right_queuekey(self.answer_id, queuekey): + # Sanity check on returned points + if points < 0: + points = 0 + elif points > self.maxpoints[self.answer_id]: + points = self.maxpoints[self.answer_id] + # Queuestate is consumed + oldcmap.set(self.answer_id, npoints=points, correctness=correctness, + msg=msg.replace(' ', ' '), queuestate=None) + else: + log.debug('OpenEndedResponse: queuekey %s does not match for answer_id=%s.' % + (queuekey, self.answer_id)) + + return oldcmap + + def get_answers(self): + anshtml = '
%s
' % self.answer + return {self.answer_id: anshtml} + + def get_initial_display(self): + return {self.answer_id: self.initial_display} + + def _parse_score_msg(self, score_msg): + """ + Grader reply is a JSON-dump of the following dict + { 'correct': True/False, + 'score': Numeric value (floating point is okay) to assign to answer + 'msg': grader_msg } + + Returns (valid_score_msg, correct, score, msg): + valid_score_msg: Flag indicating valid score_msg format (Boolean) + correct: Correctness of submission (Boolean) + score: Points to be assigned (numeric, can be float) + msg: Message from grader to display to student (string) + """ + fail = (False, False, 0, '') + try: + score_result = json.loads(score_msg) + except (TypeError, ValueError): + log.error("External grader message should be a JSON-serialized dict." + " Received score_msg = %s" % score_msg) + return fail + if not isinstance(score_result, dict): + log.error("External grader message should be a JSON-serialized dict." + " Received score_result = %s" % score_result) + return fail + for tag in ['correct', 'score', 'msg']: + if tag not in score_result: + log.error("External grader message is missing one or more required" + " tags: 'correct', 'score', 'msg'") + return fail + + # Next, we need to check that the contents of the external grader message + # is safe for the LMS. + # 1) Make sure that the message is valid XML (proper opening/closing tags) + # 2) TODO: Is the message actually HTML? + msg = score_result['msg'] + try: + etree.fromstring(msg) + except etree.XMLSyntaxError as err: + log.error("Unable to parse external grader message as valid" + " XML: score_msg['msg']=%s" % msg) + return fail + + return (True, score_result['correct'], score_result['score'], msg) + +#----------------------------------------------------------------------------- # TEMPORARY: List of all response subclasses # FIXME: To be replaced by auto-registration @@ -1830,4 +2031,5 @@ __all__ = [CodeResponse, ChoiceResponse, MultipleChoiceResponse, TrueFalseResponse, - JavascriptResponse] + JavascriptResponse, + OpenEndedResponse] From 8e87d49228c20514f83ddb661034eebb4da240f6 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 5 Nov 2012 15:30:34 -0500 Subject: [PATCH 004/234] setup grader type parameter to be paarsed from xml --- common/lib/capa/capa/responsetypes.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 53c9637373..d5b0c86ec1 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1852,26 +1852,27 @@ class OpenEndedResponse(LoncapaResponse): ''' Parse OpenEndedResponse XML: self.initial_display - self.answer (an answer to display to the student in the LMS) self.payload + self.grader_type - type of grader to use. One of 'peer','ml','turk' + ''' # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' self.payload = {'grader_payload': grader_payload} - answer_display = oeparam.find('answer_display') - if answer_display is not None: - self.answer = answer_display.text - else: - self.answer = 'No answer provided.' - initial_display = oeparam.find('initial_display') if initial_display is not None: self.initial_display = initial_display.text else: self.initial_display = '' + grader_type = oeparam.find('initial_display') + if grader_type is not None: + self.grader_type = grader_type.text + else: + self.grader_type='ml' + def get_score(self, student_answers): try: # Note that submission can be a file @@ -1910,6 +1911,7 @@ class OpenEndedResponse(LoncapaResponse): # Submit request. When successful, 'msg' is the prior length of the queue contents.update({'student_response': submission}) + contents.update({'grader_type'} : self.grader_type) (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) From 79941271d7341a0adde11f8c708c2987bdda61b8 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 5 Nov 2012 15:50:18 -0500 Subject: [PATCH 005/234] fix bracket in wrong spot --- common/lib/capa/capa/responsetypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index d5b0c86ec1..e18c894452 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1911,7 +1911,7 @@ class OpenEndedResponse(LoncapaResponse): # Submit request. When successful, 'msg' is the prior length of the queue contents.update({'student_response': submission}) - contents.update({'grader_type'} : self.grader_type) + contents.update({'grader_type' : self.grader_type}) (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) From 5c7e6415be911a3007d527a952fe354f66847777 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 5 Nov 2012 18:10:29 -0500 Subject: [PATCH 006/234] Modify capa types --- common/lib/capa/capa/responsetypes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index e18c894452..6fd975aa45 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1867,12 +1867,14 @@ class OpenEndedResponse(LoncapaResponse): else: self.initial_display = '' - grader_type = oeparam.find('initial_display') + grader_type = oeparam.find('grader_type') if grader_type is not None: self.grader_type = grader_type.text else: self.grader_type='ml' + self.answer="None available." + def get_score(self, student_answers): try: # Note that submission can be a file @@ -1912,6 +1914,7 @@ class OpenEndedResponse(LoncapaResponse): # Submit request. When successful, 'msg' is the prior length of the queue contents.update({'student_response': submission}) contents.update({'grader_type' : self.grader_type}) + log.debug(contents) (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) From bf361af3b37c351933142a71d496912e76f99920 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 6 Nov 2012 20:43:38 -0500 Subject: [PATCH 007/234] Added debug code, then removed it --- common/lib/capa/capa/responsetypes.py | 5 +---- common/lib/capa/capa/xqueue_interface.py | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 6fd975aa45..d12e451b7a 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1843,8 +1843,6 @@ class OpenEndedResponse(LoncapaResponse): self.url = xml.get('url', None) self.queue_name = xml.get('queuename', self.system.xqueue['default_queuename']) - # VS[compat]: - # Check if XML uses the ExternalResponse format or the generic OpenEndedResponse format oeparam = self.xml.find('openendedparam') self._parse_openendedresponse_xml(oeparam) @@ -1914,7 +1912,7 @@ class OpenEndedResponse(LoncapaResponse): # Submit request. When successful, 'msg' is the prior length of the queue contents.update({'student_response': submission}) contents.update({'grader_type' : self.grader_type}) - log.debug(contents) + (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) @@ -1939,7 +1937,6 @@ class OpenEndedResponse(LoncapaResponse): return cmap def update_score(self, score_msg, oldcmap, queuekey): - (valid_score_msg, correct, points, msg) = self._parse_score_msg(score_msg) if not valid_score_msg: oldcmap.set(self.answer_id, diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py index 0214488cce..f145cad23c 100644 --- a/common/lib/capa/capa/xqueue_interface.py +++ b/common/lib/capa/capa/xqueue_interface.py @@ -49,6 +49,7 @@ def parse_xreply(xreply): return_code = xreply['return_code'] content = xreply['content'] + return (return_code, content) From e2d19d1a4083f57e587d51889635b95fdb82e02e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 7 Nov 2012 10:21:56 -0500 Subject: [PATCH 008/234] Add openendedparam to list of tags to remove from output html --- common/lib/capa/capa/capa_problem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index db42fb698a..53ddab00f3 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -53,7 +53,7 @@ response_tag_dict = dict([(x.response_tag, x) for x in responsetypes.__all__]) solution_tags = ['solution'] # these get captured as student responses -response_properties = ["codeparam", "responseparam", "answer"] +response_properties = ["codeparam", "responseparam", "answer", "openendedparam"] # special problem tags which should be turned into innocuous HTML html_transforms = {'problem': {'tag': 'div'}, @@ -72,7 +72,7 @@ global_context = {'random': random, 'miller': chem.miller} # These should be removed from HTML output, including all subelements -html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup"] +html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup", "openendedparam"] log = logging.getLogger('mitx.' + __name__) From ce7444780bf5e9528fa3244957ed80b9cbfe7b00 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 7 Nov 2012 11:53:01 -0500 Subject: [PATCH 009/234] Add in feedback, work on css to display open ended feedback --- common/lib/capa/capa/responsetypes.py | 10 +++++++--- common/lib/xmodule/xmodule/css/capa/display.scss | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index d12e451b7a..4eaaa1607f 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1937,6 +1937,7 @@ class OpenEndedResponse(LoncapaResponse): return cmap def update_score(self, score_msg, oldcmap, queuekey): + log.debug(score_msg) (valid_score_msg, correct, points, msg) = self._parse_score_msg(score_msg) if not valid_score_msg: oldcmap.set(self.answer_id, @@ -1996,17 +1997,20 @@ class OpenEndedResponse(LoncapaResponse): log.error("External grader message should be a JSON-serialized dict." " Received score_result = %s" % score_result) return fail - for tag in ['correct', 'score', 'msg']: + for tag in ['correct', 'score', 'msg', 'feedback']: if tag not in score_result: log.error("External grader message is missing one or more required" - " tags: 'correct', 'score', 'msg'") + " tags: 'correct', 'score', 'msg', 'feedback") return fail + #Extract feedback from score_result + feedback = score_result['feedback'] # Next, we need to check that the contents of the external grader message # is safe for the LMS. # 1) Make sure that the message is valid XML (proper opening/closing tags) # 2) TODO: Is the message actually HTML? msg = score_result['msg'] + try: etree.fromstring(msg) except etree.XMLSyntaxError as err: @@ -2014,7 +2018,7 @@ class OpenEndedResponse(LoncapaResponse): " XML: score_msg['msg']=%s" % msg) return fail - return (True, score_result['correct'], score_result['score'], msg) + return (True, score_result['correct'], score_result['score'], feedback) #----------------------------------------------------------------------------- # TEMPORARY: List of all response subclasses diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index fd67a3804e..098100f297 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -685,6 +685,21 @@ section.problem { color: #B00; } } + + .markup-text{ + margin: 5px; + padding: 20px 0px 15px 50px; + border-top: 1px solid #DDD; + border-left: 20px solid #FAFAFA; + + .badspelling { + color: #BB0000; + } + + .badgrammar { + color: #BDA046; + } + } } } } From d59681e3aa4ddfd584648020ff6ef1c66aba4ddc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 7 Nov 2012 13:52:04 -0500 Subject: [PATCH 010/234] Alter css tag names, change documentation and answer display settings. --- common/lib/capa/capa/responsetypes.py | 19 ++++++++++--------- .../lib/xmodule/xmodule/css/capa/display.scss | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 4eaaa1607f..bdeea036f6 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1850,28 +1850,29 @@ class OpenEndedResponse(LoncapaResponse): ''' Parse OpenEndedResponse XML: self.initial_display - self.payload - self.grader_type - type of grader to use. One of 'peer','ml','turk' - + self.payload - dict containing keys -- + 'grader' : path to grader settings file, 'problem_id' : id of the problem + + self.answer - What to display when show answer is clicked ''' # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' self.payload = {'grader_payload': grader_payload} + #Parse initial display initial_display = oeparam.find('initial_display') if initial_display is not None: self.initial_display = initial_display.text else: self.initial_display = '' - grader_type = oeparam.find('grader_type') - if grader_type is not None: - self.grader_type = grader_type.text + #Parse answer display + answer_display = oeparam.find('answer_display') + if answer_display is not None: + self.answer= answer_display.text else: - self.grader_type='ml' - - self.answer="None available." + self.answer = "No answer available." def get_score(self, student_answers): try: diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 098100f297..58ba7b00ed 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -692,11 +692,11 @@ section.problem { border-top: 1px solid #DDD; border-left: 20px solid #FAFAFA; - .badspelling { + bs { color: #BB0000; } - .badgrammar { + bg { color: #BDA046; } } From a901757dd741dc82fcce39c7bf2ac3338390cec7 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 7 Nov 2012 13:53:45 -0500 Subject: [PATCH 011/234] Fix minor bug with removed grader type tag --- common/lib/capa/capa/responsetypes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index bdeea036f6..7101dccfd9 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1852,7 +1852,7 @@ class OpenEndedResponse(LoncapaResponse): self.initial_display self.payload - dict containing keys -- 'grader' : path to grader settings file, 'problem_id' : id of the problem - + self.answer - What to display when show answer is clicked ''' # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload @@ -1912,7 +1912,6 @@ class OpenEndedResponse(LoncapaResponse): # Submit request. When successful, 'msg' is the prior length of the queue contents.update({'student_response': submission}) - contents.update({'grader_type' : self.grader_type}) (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) From dba8a7a767a477336d06589386a5d22e007c1d63 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 7 Nov 2012 14:16:56 -0500 Subject: [PATCH 012/234] Add anonymous student id to grader payload --- common/lib/capa/capa/responsetypes.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 7101dccfd9..bef79e3a4b 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1843,6 +1843,7 @@ class OpenEndedResponse(LoncapaResponse): self.url = xml.get('url', None) self.queue_name = xml.get('queuename', self.system.xqueue['default_queuename']) + #Look for tag named openendedparam that encapsulates all grader settings oeparam = self.xml.find('openendedparam') self._parse_openendedresponse_xml(oeparam) @@ -1858,8 +1859,17 @@ class OpenEndedResponse(LoncapaResponse): # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' + + #Update grader payload with student id. If grader payload not json, error. + try: + grader_payload=json.loads(grader_payload) + grader_payload.update({'student_id' : self.system.anonymous_student_id}) + grader_payload=json.dumps(grader_payload) + except Exception as err: + log.error("Grader payload is not a json object!") self.payload = {'grader_payload': grader_payload} + #Parse initial display initial_display = oeparam.find('initial_display') if initial_display is not None: From 941ef528638315b2316e654e961b0882df921749 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 7 Nov 2012 15:54:49 -0500 Subject: [PATCH 013/234] Add docs, fix some logic --- common/lib/capa/capa/responsetypes.py | 28 +++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index bef79e3a4b..daaea90562 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1867,8 +1867,8 @@ class OpenEndedResponse(LoncapaResponse): grader_payload=json.dumps(grader_payload) except Exception as err: log.error("Grader payload is not a json object!") - self.payload = {'grader_payload': grader_payload} + self.payload = {'grader_payload': grader_payload} #Parse initial display initial_display = oeparam.find('initial_display') @@ -1882,16 +1882,15 @@ class OpenEndedResponse(LoncapaResponse): if answer_display is not None: self.answer= answer_display.text else: - self.answer = "No answer available." + self.answer = "No answer given." def get_score(self, student_answers): + try: - # Note that submission can be a file submission = student_answers[self.answer_id] except Exception as err: log.error('Error in OpenEndedResponse %s: cannot get student answer for %s;' - ' student_answers=%s' % - (err, self.answer_id, convert_files_to_filenames(student_answers))) + ' student_answers=%s', err, self.answer_id) raise Exception(err) # Prepare xqueue request @@ -1906,6 +1905,7 @@ class OpenEndedResponse(LoncapaResponse): queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + anonymous_student_id + self.answer_id) + xheader = xqueue_interface.make_xheader(lms_callback_url=self.system.xqueue['callback_url'], lms_key=queuekey, queue_name=self.queue_name) @@ -1918,11 +1918,11 @@ class OpenEndedResponse(LoncapaResponse): student_info = {'anonymous_student_id': anonymous_student_id, 'submission_time': qtime, } - contents.update({'student_info': json.dumps(student_info)}) + + #Update contents with student response and student info + contents.update({'student_info': json.dumps(student_info), 'student_response': submission}) # Submit request. When successful, 'msg' is the prior length of the queue - contents.update({'student_response': submission}) - (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) @@ -1988,7 +1988,9 @@ class OpenEndedResponse(LoncapaResponse): Grader reply is a JSON-dump of the following dict { 'correct': True/False, 'score': Numeric value (floating point is okay) to assign to answer - 'msg': grader_msg } + 'msg': grader_msg + 'feedback' : feedback from grader + } Returns (valid_score_msg, correct, score, msg): valid_score_msg: Flag indicating valid score_msg format (Boolean) @@ -2012,22 +2014,24 @@ class OpenEndedResponse(LoncapaResponse): log.error("External grader message is missing one or more required" " tags: 'correct', 'score', 'msg', 'feedback") return fail - #Extract feedback from score_result - feedback = score_result['feedback'] # Next, we need to check that the contents of the external grader message # is safe for the LMS. # 1) Make sure that the message is valid XML (proper opening/closing tags) # 2) TODO: Is the message actually HTML? msg = score_result['msg'] + feedback = score_result['feedback'] try: etree.fromstring(msg) + etree.fromstring(feedback) except etree.XMLSyntaxError as err: log.error("Unable to parse external grader message as valid" - " XML: score_msg['msg']=%s" % msg) + " Msg: score_msg['msg']=%r " + "\n Feedback : score_result['feedback'] = %r", msg, feedback) return fail + #Currently ignore msg and only return feedback (which takes the place of msg) return (True, score_result['correct'], score_result['score'], feedback) #----------------------------------------------------------------------------- From 06a00a982ae301ff9dde498100c5344c2378546d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 13 Nov 2012 11:56:14 -0500 Subject: [PATCH 014/234] Send location to external grader --- common/lib/capa/capa/responsetypes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index daaea90562..3193643318 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1863,7 +1863,8 @@ class OpenEndedResponse(LoncapaResponse): #Update grader payload with student id. If grader payload not json, error. try: grader_payload=json.loads(grader_payload) - grader_payload.update({'student_id' : self.system.anonymous_student_id}) + location=self.system.ajax_url.split("://")[1] + grader_payload.update({'location' : location}) grader_payload=json.dumps(grader_payload) except Exception as err: log.error("Grader payload is not a json object!") From 9abd80203fba176c654876bceb2be991e9042571 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 14 Nov 2012 10:24:19 -0500 Subject: [PATCH 015/234] Pass course id in payload --- common/lib/capa/capa/responsetypes.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 3193643318..dc120f7f47 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1864,7 +1864,11 @@ class OpenEndedResponse(LoncapaResponse): try: grader_payload=json.loads(grader_payload) location=self.system.ajax_url.split("://")[1] - grader_payload.update({'location' : location}) + org,course,type,name=location.split("/") + grader_payload.update({ + 'location' : location, + 'course_id' : "{0}/{1}".format(org,course) + }) grader_payload=json.dumps(grader_payload) except Exception as err: log.error("Grader payload is not a json object!") From 14403103a440cca4635b51b31e6d0bf7cf3c33e6 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 15 Nov 2012 14:00:45 -0500 Subject: [PATCH 016/234] Remove msg key from xqueue passback dict --- common/lib/capa/capa/responsetypes.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index dc120f7f47..0a5471d47f 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2001,7 +2001,6 @@ class OpenEndedResponse(LoncapaResponse): valid_score_msg: Flag indicating valid score_msg format (Boolean) correct: Correctness of submission (Boolean) score: Points to be assigned (numeric, can be float) - msg: Message from grader to display to student (string) """ fail = (False, False, 0, '') try: @@ -2014,26 +2013,23 @@ class OpenEndedResponse(LoncapaResponse): log.error("External grader message should be a JSON-serialized dict." " Received score_result = %s" % score_result) return fail - for tag in ['correct', 'score', 'msg', 'feedback']: + for tag in ['correct', 'score','feedback']: if tag not in score_result: log.error("External grader message is missing one or more required" - " tags: 'correct', 'score', 'msg', 'feedback") + " tags: 'correct', 'score', 'feedback") return fail # Next, we need to check that the contents of the external grader message # is safe for the LMS. # 1) Make sure that the message is valid XML (proper opening/closing tags) # 2) TODO: Is the message actually HTML? - msg = score_result['msg'] feedback = score_result['feedback'] try: - etree.fromstring(msg) etree.fromstring(feedback) except etree.XMLSyntaxError as err: log.error("Unable to parse external grader message as valid" - " Msg: score_msg['msg']=%r " - "\n Feedback : score_result['feedback'] = %r", msg, feedback) + "\n Feedback : score_result['feedback'] = %r",feedback) return fail #Currently ignore msg and only return feedback (which takes the place of msg) From 0c69c466583f17429d0b2cc4243cf00df15b6f2c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 15 Nov 2012 15:55:00 -0500 Subject: [PATCH 017/234] Remove need for msg --- common/lib/capa/capa/responsetypes.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 0a5471d47f..6cf04458b6 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1959,6 +1959,7 @@ class OpenEndedResponse(LoncapaResponse): msg='Invalid grader reply. Please contact the course staff.') return oldcmap + correctness = 'correct' if correct else 'incorrect' # TODO: Find out how this is used elsewhere, if any @@ -2025,6 +2026,10 @@ class OpenEndedResponse(LoncapaResponse): # 2) TODO: Is the message actually HTML? feedback = score_result['feedback'] + correct=False + if score_result['correct']=="True": + correct=True + try: etree.fromstring(feedback) except etree.XMLSyntaxError as err: @@ -2033,7 +2038,7 @@ class OpenEndedResponse(LoncapaResponse): return fail #Currently ignore msg and only return feedback (which takes the place of msg) - return (True, score_result['correct'], score_result['score'], feedback) + return (True, correct, score_result['score'], feedback) #----------------------------------------------------------------------------- # TEMPORARY: List of all response subclasses From e17db85d34b5db91b5f3fc34c93754e3b0bbaa6a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 15 Nov 2012 16:11:44 -0500 Subject: [PATCH 018/234] Add in max score attribute for proper instructor scoring --- common/lib/capa/capa/responsetypes.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 6cf04458b6..887aaa9752 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1889,6 +1889,18 @@ class OpenEndedResponse(LoncapaResponse): else: self.answer = "No answer given." + #Parse max_score + top_score = oeparam.find('max_score') + if top_score is not None: + try: + self.max_score= int(top_score.text) + except: + self.top_score=1 + else: + self.max_score = 1 + + log.debug(self.max_score) + def get_score(self, student_answers): try: @@ -1925,7 +1937,11 @@ class OpenEndedResponse(LoncapaResponse): } #Update contents with student response and student info - contents.update({'student_info': json.dumps(student_info), 'student_response': submission}) + contents.update({ + 'student_info': json.dumps(student_info), + 'student_response': submission, + 'max_score' : self.max_score + }) # Submit request. When successful, 'msg' is the prior length of the queue (error, msg) = qinterface.send_to_queue(header=xheader, From 565f502cb17707d64c1c5c86eed3218020464db4 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 20 Nov 2012 09:28:10 -0500 Subject: [PATCH 019/234] Add in prompt tag to openended response and parse/pass along properly --- common/lib/capa/capa/responsetypes.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 887aaa9752..1fa93eac62 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1845,9 +1845,10 @@ class OpenEndedResponse(LoncapaResponse): #Look for tag named openendedparam that encapsulates all grader settings oeparam = self.xml.find('openendedparam') - self._parse_openendedresponse_xml(oeparam) + prompt=self.xml.find('prompt') + self._parse_openendedresponse_xml(oeparam,prompt) - def _parse_openendedresponse_xml(self,oeparam): + def _parse_openendedresponse_xml(self,oeparam,prompt): ''' Parse OpenEndedResponse XML: self.initial_display @@ -1857,6 +1858,16 @@ class OpenEndedResponse(LoncapaResponse): self.answer - What to display when show answer is clicked ''' # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload + + #Modify code from stringify_children in xmodule. Didn't import directly in order to avoid capa depending + #on xmodule (seems to be avoided in code) + prompt_parts=[prompt.text] + [prompt_parts.append((etree.tostring(p, with_tail=True))) for p in prompt.getchildren()] + prompt_string=' '.join(prompt_parts) + + #Strip html tags from prompt. This may need to be removed in order to display prompt to instructors properly. + prompt_string=re.sub('<[^<]+?>', '', prompt_string) + grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' @@ -1867,7 +1878,8 @@ class OpenEndedResponse(LoncapaResponse): org,course,type,name=location.split("/") grader_payload.update({ 'location' : location, - 'course_id' : "{0}/{1}".format(org,course) + 'course_id' : "{0}/{1}".format(org,course), + 'prompt' : prompt_string }) grader_payload=json.dumps(grader_payload) except Exception as err: From d71445f9d89caf9f3f84396b02a82b8cfebdd8b1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 20 Nov 2012 13:52:58 -0500 Subject: [PATCH 020/234] Add rubric field to open ended response --- common/lib/capa/capa/responsetypes.py | 32 ++++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 1fa93eac62..14b3c97886 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1846,9 +1846,25 @@ class OpenEndedResponse(LoncapaResponse): #Look for tag named openendedparam that encapsulates all grader settings oeparam = self.xml.find('openendedparam') prompt=self.xml.find('prompt') + rubric=self.xml.find('rubric') self._parse_openendedresponse_xml(oeparam,prompt) - def _parse_openendedresponse_xml(self,oeparam,prompt): + def stringify_children(self,node,strip_tags=True): + """ + Modify code from stringify_children in xmodule. Didn't import directly in order to avoid capa depending + on xmodule (seems to be avoided in code) + """ + parts=[node.text] + [parts.append((etree.tostring(p, with_tail=True))) for p in node.getchildren()] + node_string=' '.join(parts) + + #Strip html tags from prompt. This may need to be removed in order to display prompt to instructors properly. + if strip_tags: + node_string=re.sub('<[^<]+?>', '', node_string) + + return node_string + + def _parse_openendedresponse_xml(self,oeparam,prompt,rubric): ''' Parse OpenEndedResponse XML: self.initial_display @@ -1858,15 +1874,8 @@ class OpenEndedResponse(LoncapaResponse): self.answer - What to display when show answer is clicked ''' # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload - - #Modify code from stringify_children in xmodule. Didn't import directly in order to avoid capa depending - #on xmodule (seems to be avoided in code) - prompt_parts=[prompt.text] - [prompt_parts.append((etree.tostring(p, with_tail=True))) for p in prompt.getchildren()] - prompt_string=' '.join(prompt_parts) - - #Strip html tags from prompt. This may need to be removed in order to display prompt to instructors properly. - prompt_string=re.sub('<[^<]+?>', '', prompt_string) + prompt_string=self.stringify_children(prompt) + rubric_string=self.stringify_children(rubric) grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' @@ -1879,7 +1888,8 @@ class OpenEndedResponse(LoncapaResponse): grader_payload.update({ 'location' : location, 'course_id' : "{0}/{1}".format(org,course), - 'prompt' : prompt_string + 'prompt' : prompt_string, + 'rubric' : rubric_string, }) grader_payload=json.dumps(grader_payload) except Exception as err: From 1e194f0e1aa0cb159eccafedee7b8a638caef4c0 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 20 Nov 2012 13:57:46 -0500 Subject: [PATCH 021/234] Fix rubric code, make sure it is removed properly. --- common/lib/capa/capa/capa_problem.py | 2 +- common/lib/capa/capa/responsetypes.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index 53ddab00f3..2eaa0e4286 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -72,7 +72,7 @@ global_context = {'random': random, 'miller': chem.miller} # These should be removed from HTML output, including all subelements -html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup", "openendedparam"] +html_problem_semantics = ["codeparam", "responseparam", "answer", "script", "hintgroup", "openendedparam","openendedrubric"] log = logging.getLogger('mitx.' + __name__) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 14b3c97886..82ed4d2ff7 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1846,8 +1846,8 @@ class OpenEndedResponse(LoncapaResponse): #Look for tag named openendedparam that encapsulates all grader settings oeparam = self.xml.find('openendedparam') prompt=self.xml.find('prompt') - rubric=self.xml.find('rubric') - self._parse_openendedresponse_xml(oeparam,prompt) + rubric=self.xml.find('openendedrubric') + self._parse_openendedresponse_xml(oeparam,prompt,rubric) def stringify_children(self,node,strip_tags=True): """ From 422ecb5b9d1e22b4e0ae7ff95255a8fb62041904 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 20 Nov 2012 18:51:56 -0500 Subject: [PATCH 022/234] define correctness in the response type --- common/lib/capa/capa/responsetypes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 82ed4d2ff7..6a9ac98171 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2052,7 +2052,7 @@ class OpenEndedResponse(LoncapaResponse): log.error("External grader message should be a JSON-serialized dict." " Received score_result = %s" % score_result) return fail - for tag in ['correct', 'score','feedback']: + for tag in ['score','feedback']: if tag not in score_result: log.error("External grader message is missing one or more required" " tags: 'correct', 'score', 'feedback") @@ -2064,8 +2064,10 @@ class OpenEndedResponse(LoncapaResponse): # 2) TODO: Is the message actually HTML? feedback = score_result['feedback'] - correct=False - if score_result['correct']=="True": + score_ratio=int(score_result['score'])/self.max_score + + correct=FALSE + if score_ratio>=.66: correct=True try: From 1a22d3a15fb942b3dd9c0675b91c98f41d1087ef Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 20 Nov 2012 19:04:46 -0500 Subject: [PATCH 023/234] Lowercase False --- common/lib/capa/capa/responsetypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 6a9ac98171..31fb7b0f0f 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2066,7 +2066,7 @@ class OpenEndedResponse(LoncapaResponse): score_ratio=int(score_result['score'])/self.max_score - correct=FALSE + correct=False if score_ratio>=.66: correct=True From c2bf0689b658f6a87a1b68b1ea82e64a6ce76e22 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 20 Nov 2012 19:24:39 -0500 Subject: [PATCH 024/234] Remove correct from expected tags --- common/lib/capa/capa/responsetypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 31fb7b0f0f..ba9f03549e 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2055,7 +2055,7 @@ class OpenEndedResponse(LoncapaResponse): for tag in ['score','feedback']: if tag not in score_result: log.error("External grader message is missing one or more required" - " tags: 'correct', 'score', 'feedback") + " tags: 'score', 'feedback") return fail # Next, we need to check that the contents of the external grader message From 6b5125c4dfa1d143d2b66dfd54e94b474c003773 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 13 Nov 2012 18:07:59 -0500 Subject: [PATCH 025/234] fix typos, logger configs --- common/lib/xmodule/xmodule/seq_module.py | 2 +- lms/djangoapps/courseware/tabs.py | 2 +- lms/djangoapps/instructor/views.py | 2 +- lms/urls.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index b625646e66..817ed9ab2e 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -10,7 +10,7 @@ from xmodule.progress import Progress from xmodule.exceptions import NotFoundError from pkg_resources import resource_string -log = logging.getLogger("mitx.common.lib.seq_module") +log = logging.getLogger(__name__) # HACK: This shouldn't be hard-coded to two types # OBSOLETE: This obsoletes 'type' diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 980fedb947..4eaef20089 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -36,7 +36,7 @@ CourseTab = namedtuple('CourseTab', 'name link is_active') # wrong. (e.g. "is there a 'name' field?). Validators can assume # that the type field is valid. # -# - a function that takes a config, a user, and a course, and active_page and +# - a function that takes a config, a user, and a course, an active_page and # return a list of CourseTabs. (e.g. "return a CourseTab with specified # name, link to courseware, and is_active=True/False"). The function can # assume that it is only called with configs of the appropriate type that diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index f985cc43a0..fec7536151 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -27,7 +27,7 @@ from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundErr from xmodule.modulestore.search import path_to_location import track.views -log = logging.getLogger("mitx.courseware") +log = logging.getLogger(__name__) template_imports = {'urllib': urllib} diff --git a/lms/urls.py b/lms/urls.py index 529396c20e..62842ec5fe 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -161,7 +161,7 @@ if settings.COURSEWARE_ENABLED: # input types system so that previews can be context-specific. # Unfortunately, we don't have time to think through the right way to do # that (and implement it), and it's not a terrible thing to provide a - # generic chemican-equation rendering service. + # generic chemical-equation rendering service. url(r'^preview/chemcalc', 'courseware.module_render.preview_chemcalc', name='preview_chemcalc'), From f5d9e963cc897e19ceead213e7a6dedc0d9dc9af Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 13 Nov 2012 18:46:21 -0500 Subject: [PATCH 026/234] Initial wiring of a staff grading tab. - no actual functionality, but have a tab that renders static html via a view --- lms/djangoapps/courseware/tabs.py | 9 ++++++++ lms/djangoapps/instructor/grading.py | 25 +++++++++++++++++++++ lms/djangoapps/instructor/views.py | 21 +++++++++++++++++ lms/templates/instructor/staff_grading.html | 20 +++++++++++++++++ lms/urls.py | 2 ++ 5 files changed, 77 insertions(+) create mode 100644 lms/djangoapps/instructor/grading.py create mode 100644 lms/templates/instructor/staff_grading.html diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 4eaef20089..45b4e1821c 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -97,6 +97,14 @@ def _textbooks(tab, user, course, active_page): for index, textbook in enumerate(course.textbooks)] return [] + +def _staff_grading(tab, user, course, active_page): + if has_access(user, course, 'staff'): + link = reverse('staff_grading', args=[course.id]) + return [CourseTab('Staff grading', link, active_page == "staff_grading")] + return [] + + #### Validators @@ -132,6 +140,7 @@ VALID_TAB_TYPES = { 'textbooks': TabImpl(null_validator, _textbooks), 'progress': TabImpl(need_name, _progress), 'static_tab': TabImpl(key_checker(['name', 'url_slug']), _static_tab), + 'staff_grading': TabImpl(null_validator, _staff_grading), } diff --git a/lms/djangoapps/instructor/grading.py b/lms/djangoapps/instructor/grading.py new file mode 100644 index 0000000000..7a48b25a49 --- /dev/null +++ b/lms/djangoapps/instructor/grading.py @@ -0,0 +1,25 @@ +""" +LMS part of instructor grading: + +- views + ajax handling +- calls the instructor grading service +""" + +import json +import logging + +log = logging.getLogger(__name__) + + +class StaffGrading(object): + """ + Wrap up functionality for staff grading of submissions--interface exposes get_html, ajax views. + """ + def __init__(self, course): + self.course = course + + def get_html(self): + return "Instructor grading!" + # context = {} + # return render_to_string('courseware/instructor_grading_view.html', context) + diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index fec7536151..8a33f1d60b 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -27,6 +27,9 @@ from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundErr from xmodule.modulestore.search import path_to_location import track.views +from .grading import StaffGrading + + log = logging.getLogger(__name__) template_imports = {'urllib': urllib} @@ -409,6 +412,24 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True, return datatable + +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +def staff_grading(request, course_id): + """ + Show the instructor grading interface. + """ + course = get_course_with_access(request.user, course_id, 'staff') + + grading = StaffGrading(course) + + return render_to_response('instructor/staff_grading.html', { + 'view_html': grading.get_html(), + 'course': course, + 'course_id': course_id, + # Checked above + 'staff_access': True, }) + + @cache_control(no_cache=True, no_store=True, must_revalidate=True) def gradebook(request, course_id): """ diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html new file mode 100644 index 0000000000..b00fd935aa --- /dev/null +++ b/lms/templates/instructor/staff_grading.html @@ -0,0 +1,20 @@ +<%inherit file="/main.html" /> +<%block name="bodyclass">${course.css_class} +<%namespace name='static' file='/static_content.html'/> + +<%block name="headextra"> + <%static:css group='course'/> + + +<%block name="title">${course.number} Staff Grading + +<%include file="/courseware/course_navigation.html" args="active_page='staff_grading'" /> + +<%block name="js_extra"> + + +
+
+ ${view_html} +
+
diff --git a/lms/urls.py b/lms/urls.py index 62842ec5fe..7c1ccb6bde 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -230,6 +230,8 @@ if settings.COURSEWARE_ENABLED: 'instructor.views.grade_summary', name='grade_summary'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/enroll_students$', 'instructor.views.enroll_students', name='enroll_students'), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading$', + 'instructor.views.staff_grading', name='staff_grading'), ) # discussion forums live within courseware, so courseware must be enabled first From 7d1d135c1638d752ad5a79db2a1fa4a2ba9ed32e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 13 Nov 2012 18:46:38 -0500 Subject: [PATCH 027/234] Wire in some initial js. --- lms/envs/common.py | 15 +++++++++++++-- .../coffee/src/staff_grading/staff_grading.coffee | 5 +++++ lms/templates/instructor/staff_grading.html | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 lms/static/coffee/src/staff_grading/staff_grading.coffee diff --git a/lms/envs/common.py b/lms/envs/common.py index dd9013bcb3..008de7ac84 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -406,6 +406,9 @@ main_vendor_js = [ discussion_js = sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/discussion/**/*.coffee')) +staff_grading_js = sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/staff_grading/**/*.coffee')) + + # Load javascript from all of the available xmodules, and # prep it for use in pipeline js from xmodule.x_module import XModuleDescriptor @@ -468,7 +471,8 @@ with open(module_styles_path, 'w') as module_styles: PIPELINE_JS = { 'application': { - # Application will contain all paths not in courseware_only_js + # Application will contain all paths not in courseware_only_js or + # discussion_js or staff_grading_js 'source_filenames': [ pth.replace(COMMON_ROOT / 'static/', '') for pth @@ -476,7 +480,9 @@ PIPELINE_JS = { ] + [ pth.replace(PROJECT_ROOT / 'static/', '') for pth in sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee'))\ - if pth not in courseware_only_js and pth not in discussion_js + if (pth not in courseware_only_js and + pth not in discussion_js and + pth not in staff_grading_js) ] + [ 'js/form.ext.js', 'js/my_courses_dropdown.js', @@ -505,7 +511,12 @@ PIPELINE_JS = { 'discussion' : { 'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in discussion_js], 'output_filename': 'js/discussion.js' + }, + 'staff_grading' : { + 'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in staff_grading_js], + 'output_filename': 'js/staff_grading.js' } + } PIPELINE_DISABLE_WRAPPER = True diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee new file mode 100644 index 0000000000..06c84a3867 --- /dev/null +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -0,0 +1,5 @@ +class @StaffGrading + constructor: -> + alert('hi!') + + \ No newline at end of file diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index b00fd935aa..d9d183c161 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -11,6 +11,7 @@ <%include file="/courseware/course_navigation.html" args="active_page='staff_grading'" /> <%block name="js_extra"> + <%static:js group='staff_grading'/>
From c7ab37bdabd0ea961837e20f158a3574f0d64bde Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 14 Nov 2012 14:32:43 -0500 Subject: [PATCH 028/234] initial js--called at page load --- .../coffee/src/staff_grading/staff_grading.coffee | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 06c84a3867..445b32b444 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -1,5 +1,11 @@ -class @StaffGrading +# wrap everything in a class in case we want to use inside xmodules later +class StaffGrading constructor: -> alert('hi!') - \ No newline at end of file + load: -> + alert('loading') + +# for now, just create an instance and load it... +grading = new StaffGrading +$(document).ready(grading.load) From aa786d138d8976057a005d4683139780c87aa42e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 14 Nov 2012 20:46:02 -0500 Subject: [PATCH 029/234] move computers--adding test file --- .../src/staff_grading/staff_grading.coffee | 11 ++++++--- .../src/staff_grading/test_grading.html | 24 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 lms/static/coffee/src/staff_grading/test_grading.html diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 445b32b444..fbaf1cbfa8 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -1,11 +1,16 @@ # wrap everything in a class in case we want to use inside xmodules later class StaffGrading constructor: -> - alert('hi!') + @submission_container = $('.submission-container') + @rubric_container = $('.rubric-container') + @submit_button = $('.submit-button') + @mock_backend = true + @load() + + load: -> alert('loading') # for now, just create an instance and load it... -grading = new StaffGrading -$(document).ready(grading.load) +$(document).ready(() -> new StaffGrading) diff --git a/lms/static/coffee/src/staff_grading/test_grading.html b/lms/static/coffee/src/staff_grading/test_grading.html new file mode 100644 index 0000000000..dd960b03ac --- /dev/null +++ b/lms/static/coffee/src/staff_grading/test_grading.html @@ -0,0 +1,24 @@ + + + + + + + + +

Staff grading

+ +
+ +
+ +
+
+ +
+ +
+ + + + From 3d0ef7580710aeceb3b86aeaef2040c0bbc53986 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 14 Nov 2012 21:57:11 -0500 Subject: [PATCH 030/234] Basic js implementation, with mocked backend --- .../src/staff_grading/staff_grading.coffee | 121 ++++++++++++++++-- .../src/staff_grading/test_grading.html | 10 +- 2 files changed, 118 insertions(+), 13 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index fbaf1cbfa8..4d754adfab 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -1,16 +1,121 @@ # wrap everything in a class in case we want to use inside xmodules later + +get_random_int: (min, max) -> + return Math.floor(Math.random() * (max - min + 1)) + min + +# states +state_grading = "have_data" +state_no_data = "no_data" +state_error = "error" + class StaffGrading - constructor: -> + constructor: (mock_backend) -> + @el = $('.staff-grading') + @ajax_url = @el.data('ajax_url') + + @error_container = $('.error-container') @submission_container = $('.submission-container') @rubric_container = $('.rubric-container') - @submit_button = $('.submit-button') - @mock_backend = true + @button = $('.submit-button') + @button.click @clicked + @state = state_no_data + + @mock_backend = mock_backend + if @mock_backend + @mock_cnt = 0 + + @get_next_submission() + + mock: (cmd, data) -> + # Return a mock response to cmd and data + @mock_cnt++ + if cmd == 'get_next' + response = + 'success': true + 'submission': 'submission! ' + @mock_cnt + 'rubric': 'A rubric!' + @mock_cnt + else if cmd == 'save_grade' + response = + 'success': true + 'submission': 'another submission! ' + @mock_cnt + 'rubric': 'A rubric!' + @mock_cnt + else + response = + 'success': false + 'error': 'Unknown command ' + cmd + return response + + + set_button_text: (text) -> + @button.prop('value', text) + + _post: (cmd, data, callback) -> + if @mock_backend + callback(@mock(cmd, data)) + else + # TODO: replace with postWithPrefix when that's loaded + $.post(@ajax_url + cmd, data, callback) + + + ajax_callback: (response) => + if response.success + if response.submission + @data_loaded(response.submission, response.rubric) + else + @no_more() + else + @error(response.error) + + get_next_submission: () -> + @_post('get_next', {}, @ajax_callback) + + submit_and_get_next: () -> + data = {eval: '123'} + @_post('save_grade', data, @ajax_callback) + + error: (msg) -> + @error_container.html(msg) + @state = state_error + @update() + + data_loaded: (submission, rubric) -> + @submission_container.html(submission) + @rubric_container.html(rubric) + @state = state_grading + @update() + + no_more: () -> + @submission_container.html(submission) + @rubric_container.html(rubric) + @state = state_no_data + @update() + + update: () -> + # make button state and actions right + if @state == state_error + @set_button_text('Try loading again') + else if @state == state_grading + @set_button_text('Submit') + else if @state == state_no_data + @set_button_text('Re-check for submissions') + else + @error('System got into invalid state ' + @state) + + clicked: (event) => + event.preventDefault() + if @state == state_error + @error_container.html('') + @get_next_submission() + else if @state == state_grading + @submit_and_get_next() + else if @state == state_no_data + @get_next_submission() + else + @error('System got into invalid state ' + @state) + - @load() - - load: -> - alert('loading') # for now, just create an instance and load it... -$(document).ready(() -> new StaffGrading) +mock_backend = true +$(document).ready(() -> new StaffGrading(mock_backend)) diff --git a/lms/static/coffee/src/staff_grading/test_grading.html b/lms/static/coffee/src/staff_grading/test_grading.html index dd960b03ac..07891e1c33 100644 --- a/lms/static/coffee/src/staff_grading/test_grading.html +++ b/lms/static/coffee/src/staff_grading/test_grading.html @@ -6,19 +6,19 @@ +

Staff grading

-
+
-
+
-
-
+
- +
From 546096e8a0a7fa22af779fd007b9a9829d380bac Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 15 Nov 2012 10:23:24 -0500 Subject: [PATCH 031/234] Split out mock backend, fix out-of-data bug --- .../src/staff_grading/staff_grading.coffee | 98 +++++++++++-------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 4d754adfab..22416a5276 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -8,10 +8,57 @@ state_grading = "have_data" state_no_data = "no_data" state_error = "error" +class StaffGradingBackend + constructor: (ajax_url, mock_backend) -> + @ajax_url = ajax_url + @mock_backend = mock_backend + if @mock_backend + @mock_cnt = 0 + + mock: (cmd, data) -> + # Return a mock response to cmd and data + @mock_cnt++ + if cmd == 'get_next' + response = + success: true + submission: 'submission! ' + @mock_cnt + rubric: 'A rubric!' + @mock_cnt + + else if cmd == 'save_grade' + response = + success: true + submission: 'another submission! ' + @mock_cnt + rubric: 'A rubric!' + @mock_cnt + else + response = + success: false + error: 'Unknown command ' + cmd + + if @mock_cnt % 5 == 0 + response = + success: true + message: 'No more submissions' + + + if @mock_cnt % 7 == 0 + response = + success: false + error: 'An error for testing' + + return response + + + post: (cmd, data, callback) -> + if @mock_backend + callback(@mock(cmd, data)) + else + # TODO: replace with postWithPrefix when that's loaded + $.post(@ajax_url + cmd, data, callback) + + class StaffGrading - constructor: (mock_backend) -> - @el = $('.staff-grading') - @ajax_url = @el.data('ajax_url') + constructor: (backend) -> + @backend = backend @error_container = $('.error-container') @submission_container = $('.submission-container') @@ -20,42 +67,12 @@ class StaffGrading @button.click @clicked @state = state_no_data - @mock_backend = mock_backend - if @mock_backend - @mock_cnt = 0 - @get_next_submission() - mock: (cmd, data) -> - # Return a mock response to cmd and data - @mock_cnt++ - if cmd == 'get_next' - response = - 'success': true - 'submission': 'submission! ' + @mock_cnt - 'rubric': 'A rubric!' + @mock_cnt - else if cmd == 'save_grade' - response = - 'success': true - 'submission': 'another submission! ' + @mock_cnt - 'rubric': 'A rubric!' + @mock_cnt - else - response = - 'success': false - 'error': 'Unknown command ' + cmd - return response - set_button_text: (text) -> @button.prop('value', text) - _post: (cmd, data, callback) -> - if @mock_backend - callback(@mock(cmd, data)) - else - # TODO: replace with postWithPrefix when that's loaded - $.post(@ajax_url + cmd, data, callback) - ajax_callback: (response) => if response.success @@ -67,11 +84,11 @@ class StaffGrading @error(response.error) get_next_submission: () -> - @_post('get_next', {}, @ajax_callback) + @backend.post('get_next', {}, @ajax_callback) submit_and_get_next: () -> data = {eval: '123'} - @_post('save_grade', data, @ajax_callback) + @backend.post('save_grade', data, @ajax_callback) error: (msg) -> @error_container.html(msg) @@ -85,8 +102,8 @@ class StaffGrading @update() no_more: () -> - @submission_container.html(submission) - @rubric_container.html(rubric) + @submission_container.html('') + @rubric_container.html('') @state = state_no_data @update() @@ -117,5 +134,8 @@ class StaffGrading # for now, just create an instance and load it... -mock_backend = true -$(document).ready(() -> new StaffGrading(mock_backend)) +mock_backend = true +ajax_url = $('.staff-grading').data('ajax_url') +backend = new StaffGradingBackend(ajax_url, mock_backend) + +$(document).ready(() -> new StaffGrading(backend)) From b1d5273a2a10b828a137714d2e03a4e0fe4f9aea Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 15 Nov 2012 10:45:55 -0500 Subject: [PATCH 032/234] add messages, headers for sections --- .../src/staff_grading/staff_grading.coffee | 25 ++++++++++++++----- .../src/staff_grading/test_grading.html | 22 ++++++++++++---- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 22416a5276..e36e70151f 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -61,12 +61,18 @@ class StaffGrading @backend = backend @error_container = $('.error-container') + @message_container = $('.message-container') @submission_container = $('.submission-container') @rubric_container = $('.rubric-container') + @submission_wrapper = $('.submission-wrapper') + @rubric_wrapper = $('.rubric-wrapper') @button = $('.submit-button') @button.click @clicked @state = state_no_data + @submission_wrapper.hide() + @rubric_wrapper.hide() + @get_next_submission() @@ -102,26 +108,35 @@ class StaffGrading @update() no_more: () -> - @submission_container.html('') - @rubric_container.html('') @state = state_no_data @update() update: () -> - # make button state and actions right + # make button and div state match the state. Idempotent. if @state == state_error @set_button_text('Try loading again') + else if @state == state_grading + @submission_wrapper.show() + @rubric_wrapper.show() @set_button_text('Submit') + else if @state == state_no_data + @submission_wrapper.hide() + @rubric_wrapper.hide() + @message_container.html('Nothing to grade') @set_button_text('Re-check for submissions') + else @error('System got into invalid state ' + @state) clicked: (event) => event.preventDefault() + # always clear out errors and messages on transition... + @message_container.html('') + @error_container.html('') + if @state == state_error - @error_container.html('') @get_next_submission() else if @state == state_grading @submit_and_get_next() @@ -131,8 +146,6 @@ class StaffGrading @error('System got into invalid state ' + @state) - - # for now, just create an instance and load it... mock_backend = true ajax_url = $('.staff-grading').data('ajax_url') diff --git a/lms/static/coffee/src/staff_grading/test_grading.html b/lms/static/coffee/src/staff_grading/test_grading.html index 07891e1c33..780515a752 100644 --- a/lms/static/coffee/src/staff_grading/test_grading.html +++ b/lms/static/coffee/src/staff_grading/test_grading.html @@ -1,7 +1,7 @@ - + @@ -9,11 +9,23 @@

Staff grading

-
- -
+
+
-
+
+
+ +
+

Submission

+
+
+
+ +
+

Rubric

+
+
+
From e1fd6d73b3ebd80fb695d0d2d775506fdacf96e3 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 15 Nov 2012 13:35:53 -0500 Subject: [PATCH 033/234] refactor to be more clearly model-view --- .../src/staff_grading/staff_grading.coffee | 83 ++++++++++++------- .../src/staff_grading/test_grading.html | 1 + 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index e36e70151f..827f9fe56e 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -25,6 +25,7 @@ class StaffGradingBackend rubric: 'A rubric!' + @mock_cnt else if cmd == 'save_grade' + console.log("eval: #{data.score} pts, Feedback: #{data.feedback}") response = success: true submission: 'another submission! ' + @mock_cnt @@ -60,6 +61,7 @@ class StaffGrading constructor: (backend) -> @backend = backend + # all the jquery selectors @error_container = $('.error-container') @message_container = $('.message-container') @submission_container = $('.submission-container') @@ -67,75 +69,98 @@ class StaffGrading @submission_wrapper = $('.submission-wrapper') @rubric_wrapper = $('.rubric-wrapper') @button = $('.submit-button') - @button.click @clicked + + # model state @state = state_no_data + @submission = '' + @rubric = '' + @error_msg = '' + @message = '' - @submission_wrapper.hide() - @rubric_wrapper.hide() + @feedback = null + @score = null + # action handlers + @button.click @clicked + + # render intial state + @render_view() + + # send initial request automatically @get_next_submission() set_button_text: (text) -> @button.prop('value', text) - ajax_callback: (response) => - if response.success - if response.submission - @data_loaded(response.submission, response.rubric) - else - @no_more() + # always clear out errors and messages on transition. + @error_msg = '' + @message = '' + + if response.success + if response.submission + @data_loaded(response.submission, response.rubric) else - @error(response.error) - + @no_more() + else + @error(response.error) + + @render_view() + get_next_submission: () -> @backend.post('get_next', {}, @ajax_callback) submit_and_get_next: () -> - data = {eval: '123'} + data = {score: '1', feedback: 'Great!'} + @backend.post('save_grade', data, @ajax_callback) error: (msg) -> - @error_container.html(msg) + @error_msg = msg @state = state_error - @update() data_loaded: (submission, rubric) -> - @submission_container.html(submission) - @rubric_container.html(rubric) + @submission = submission + @rubric = rubric @state = state_grading - @update() no_more: () -> + @submission = null + @rubric = null + @message = 'Nothing to grade' @state = state_no_data - @update() - update: () -> - # make button and div state match the state. Idempotent. + render_view: () -> + # make the view elements match the state. Idempotent. + show_grading_elements = false + + @message_container.html(@message) + @error_container.html(@error_msg) + if @state == state_error @set_button_text('Try loading again') else if @state == state_grading - @submission_wrapper.show() - @rubric_wrapper.show() + @submission_container.html(@submission) + @rubric_container.html(@rubric) + show_grading_elements = true @set_button_text('Submit') else if @state == state_no_data - @submission_wrapper.hide() - @rubric_wrapper.hide() - @message_container.html('Nothing to grade') + @message_container.html(@message) @set_button_text('Re-check for submissions') else @error('System got into invalid state ' + @state) + @submission_wrapper.toggle(show_grading_elements) + @rubric_wrapper.toggle(show_grading_elements) + + clicked: (event) => event.preventDefault() - # always clear out errors and messages on transition... - @message_container.html('') - @error_container.html('') - + if @state == state_error @get_next_submission() else if @state == state_grading diff --git a/lms/static/coffee/src/staff_grading/test_grading.html b/lms/static/coffee/src/staff_grading/test_grading.html index 780515a752..8e69c90866 100644 --- a/lms/static/coffee/src/staff_grading/test_grading.html +++ b/lms/static/coffee/src/staff_grading/test_grading.html @@ -25,6 +25,7 @@

Rubric

+
From e910f60e08b2418fa118680ff785e5795f66e036 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 15 Nov 2012 14:27:46 -0500 Subject: [PATCH 034/234] wip --- .../src/staff_grading/staff_grading.coffee | 18 +++++++++++------- .../coffee/src/staff_grading/test_grading.html | 12 ++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 827f9fe56e..aded7464e7 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -22,7 +22,7 @@ class StaffGradingBackend response = success: true submission: 'submission! ' + @mock_cnt - rubric: 'A rubric!' + @mock_cnt + rubric: 'A rubric! ' + @mock_cnt else if cmd == 'save_grade' console.log("eval: #{data.score} pts, Feedback: #{data.feedback}") @@ -68,7 +68,8 @@ class StaffGrading @rubric_container = $('.rubric-container') @submission_wrapper = $('.submission-wrapper') @rubric_wrapper = $('.rubric-wrapper') - @button = $('.submit-button') + @feedback_area = $('.feedback-area') + @submit_button = $('.submit-button') # model state @state = state_no_data @@ -77,11 +78,12 @@ class StaffGrading @error_msg = '' @message = '' - @feedback = null @score = null # action handlers - @button.click @clicked + @submit_button.click @submit + @correct_button.click () => @score = 1 + @incorrect_button.click () => @score = 0 # render intial state @render_view() @@ -91,7 +93,7 @@ class StaffGrading set_button_text: (text) -> - @button.prop('value', text) + @submit_button.prop('value', text) ajax_callback: (response) => # always clear out errors and messages on transition. @@ -112,7 +114,7 @@ class StaffGrading @backend.post('get_next', {}, @ajax_callback) submit_and_get_next: () -> - data = {score: '1', feedback: 'Great!'} + data = {score: @score, feedback: @feedback_area.val()} @backend.post('save_grade', data, @ajax_callback) @@ -123,6 +125,8 @@ class StaffGrading data_loaded: (submission, rubric) -> @submission = submission @rubric = rubric + @feedback_area.val('') + @score = null @state = state_grading no_more: () -> @@ -158,7 +162,7 @@ class StaffGrading @rubric_wrapper.toggle(show_grading_elements) - clicked: (event) => + submit: (event) => event.preventDefault() if @state == state_error diff --git a/lms/static/coffee/src/staff_grading/test_grading.html b/lms/static/coffee/src/staff_grading/test_grading.html index 8e69c90866..b417aa41d9 100644 --- a/lms/static/coffee/src/staff_grading/test_grading.html +++ b/lms/static/coffee/src/staff_grading/test_grading.html @@ -26,6 +26,18 @@
+
+ +

+ + + + + +

+
+
From 5836438454225cc8c2297d43e490001f7784ce57 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 15 Nov 2012 19:23:07 -0500 Subject: [PATCH 035/234] hook up radio buttons, hide submit button till after grading --- .../src/staff_grading/staff_grading.coffee | 34 +++++++++++++++---- .../src/staff_grading/test_grading.html | 6 ++-- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index aded7464e7..b252e7a365 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -4,7 +4,8 @@ get_random_int: (min, max) -> return Math.floor(Math.random() * (max - min + 1)) + min # states -state_grading = "have_data" +state_grading = "grading" +state_graded = "graded" state_no_data = "no_data" state_error = "error" @@ -82,8 +83,10 @@ class StaffGrading # action handlers @submit_button.click @submit - @correct_button.click () => @score = 1 - @incorrect_button.click () => @score = 0 + # TODO: hook up an event to the input changing, which updates + # @score (instead of the individual hacks) + $('#correct-radio').click @graded_callback + $('#incorrect-radio').click @graded_callback # render intial state @render_view() @@ -92,8 +95,13 @@ class StaffGrading @get_next_submission() - set_button_text: (text) -> - @submit_button.prop('value', text) + set_button_text: (text) => + @submit_button.attr('value', text) + + graded_callback: (event) => + @score = event.target.value + @state = state_graded + @render_view() ajax_callback: (response) => # always clear out errors and messages on transition. @@ -138,6 +146,7 @@ class StaffGrading render_view: () -> # make the view elements match the state. Idempotent. show_grading_elements = false + show_submit_button = true @message_container.html(@message) @error_container.html(@error_msg) @@ -149,6 +158,16 @@ class StaffGrading @submission_container.html(@submission) @rubric_container.html(@rubric) show_grading_elements = true + + # no submit button until user picks grade. + show_submit_button = false + + # TODO: clean up with proper input-related logic + $('#correct-radio')[0].checked = false + $('#incorrect-radio')[0].checked = false + + else if @state == state_graded + show_grading_elements = true @set_button_text('Submit') else if @state == state_no_data @@ -158,6 +177,7 @@ class StaffGrading else @error('System got into invalid state ' + @state) + @submit_button.toggle(show_submit_button) @submission_wrapper.toggle(show_grading_elements) @rubric_wrapper.toggle(show_grading_elements) @@ -167,12 +187,12 @@ class StaffGrading if @state == state_error @get_next_submission() - else if @state == state_grading + else if @state == state_graded @submit_and_get_next() else if @state == state_no_data @get_next_submission() else - @error('System got into invalid state ' + @state) + @error('System got into invalid state for submission: ' + @state) # for now, just create an instance and load it... diff --git a/lms/static/coffee/src/staff_grading/test_grading.html b/lms/static/coffee/src/staff_grading/test_grading.html index b417aa41d9..8a1cde1fd4 100644 --- a/lms/static/coffee/src/staff_grading/test_grading.html +++ b/lms/static/coffee/src/staff_grading/test_grading.html @@ -1,7 +1,8 @@ - + + @@ -32,8 +33,7 @@

- - +

From 9d8d6655c685597ada8885e28a889a92d4fe288e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 09:10:02 -0500 Subject: [PATCH 036/234] add docstring for expect_json decorator --- common/djangoapps/util/json_request.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/djangoapps/util/json_request.py b/common/djangoapps/util/json_request.py index 169a7e3fb4..9458bff858 100644 --- a/common/djangoapps/util/json_request.py +++ b/common/djangoapps/util/json_request.py @@ -4,6 +4,11 @@ import json def expect_json(view_function): + """ + View decorator for simplifying handing of requests that expect json. If the request's + CONTENT_TYPE is application/json, parses the json dict from request.body, and updates + request.POST with the contents. + """ @wraps(view_function) def expect_json_with_cloned_request(request, *args, **kwargs): # cdodge: fix postback errors in CMS. The POST 'content-type' header can include additional information From ed6a8f68ac7c422dfd198ea1a582d186d4a3da53 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 10:04:56 -0500 Subject: [PATCH 037/234] starting to stub out the backend staff grading service [wip] --- .../instructor/staff_grading_service.py | 57 +++++++++++++++++++ lms/urls.py | 4 ++ 2 files changed, 61 insertions(+) create mode 100644 lms/djangoapps/instructor/staff_grading_service.py diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py new file mode 100644 index 0000000000..d4a4d82e63 --- /dev/null +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -0,0 +1,57 @@ +""" +This module provides views that proxy to the staff grading backend service. +""" + +import json +import requests +import sys + +from django.http import Http404 +from django.http import HttpResponse + + +from util.json_request import expect_json + +class GradingServiceError(Exception): + pass + +class StaffGradingService(object): + """ + Interface to staff grading backend. + """ + def __init__(self, url): + self.url = url + # TODO: add auth + self.session = requests.session() + + def get_next(course_id): + """ + Get the next thing to grade. Returns json, or raises GradingServiceError + if there's a problem. + """ + try: + r = self.session.get(url + 'get_next') + except requests.exceptions.ConnectionError as err: + # reraise as promised GradingServiceError, but preserve stacktrace. + raise GradingServiceError, str(err), sys.exc_info()[2] + + return r.text + + +#@login_required +def get_next(request, course_id): + """ + """ + d = {'success': False} + return HttpResponse(json.dumps(d)) + + +#@login_required +@expect_json +def save_grade(request, course_id): + """ + + """ + d = {'success': False} + return HttpResponse(json.dumps(d)) + diff --git a/lms/urls.py b/lms/urls.py index 7c1ccb6bde..0b2def9135 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -232,6 +232,10 @@ if settings.COURSEWARE_ENABLED: 'instructor.views.enroll_students', name='enroll_students'), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading$', 'instructor.views.staff_grading', name='staff_grading'), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading/get_next$', + 'instructor.staff_grading_service.get_next', name='staff_grading_get_next'), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/staff_grading/save_grade$', + 'instructor.staff_grading_service.save_grade', name='staff_grading_save_grade'), ) # discussion forums live within courseware, so courseware must be enabled first From 4f359ea59412c7f1aba2b25912075dbcc8f61779 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 13:38:21 -0500 Subject: [PATCH 038/234] pass through message when no more submissions. --- lms/static/coffee/src/staff_grading/staff_grading.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index b252e7a365..e410a3c41d 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -112,7 +112,7 @@ class StaffGrading if response.submission @data_loaded(response.submission, response.rubric) else - @no_more() + @no_more(response.message) else @error(response.error) @@ -137,10 +137,10 @@ class StaffGrading @score = null @state = state_grading - no_more: () -> + no_more: (message) -> @submission = null @rubric = null - @message = 'Nothing to grade' + @message = message @state = state_no_data render_view: () -> From 5bf39fef962c1e94e1f616074d31cc57a710e784 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 15:23:05 -0500 Subject: [PATCH 039/234] Cherry pick the test-cleanup parts of e2826cb. - look up test courses by id, not name --- .../xmodule/xmodule/modulestore/__init__.py | 13 ++++++++++ lms/djangoapps/courseware/tests/tests.py | 19 ++++---------- lms/djangoapps/instructor/tests.py | 25 ++++++++++--------- lms/djangoapps/instructor/views.py | 4 +-- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py index fa8cf8d3d7..5b94add68f 100644 --- a/common/lib/xmodule/xmodule/modulestore/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/__init__.py @@ -339,6 +339,12 @@ class ModuleStore(object): ''' raise NotImplementedError + def get_course(self, course_id): + ''' + Look for a specific course id. Returns the course descriptor, or None if not found. + ''' + raise NotImplementedError + def get_parent_locations(self, location): '''Find all locations that are the parents of this location. Needed for path_to_location(). @@ -399,3 +405,10 @@ class ModuleStoreBase(ModuleStore): errorlog = self._get_errorlog(location) return errorlog.errors + + def get_course(self, course_id): + """Default impl--linear search through course list""" + for c in self.get_courses(): + if c.id == course_id: + return c + return None diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 8239eadfd9..1eebf0f408 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -288,14 +288,10 @@ class TestNavigation(PageLoader): def setUp(self): xmodule.modulestore.django._MODULESTORES = {} - courses = modulestore().get_courses() - def find_course(course_id): - """Assumes the course is present""" - return [c for c in courses if c.id==course_id][0] - - self.full = find_course("edX/full/6.002_Spring_2012") - self.toy = find_course("edX/toy/2012_Fall") + # Assume courses are there + self.full = modulestore().get_course("edX/full/6.002_Spring_2012") + self.toy = modulestore().get_course("edX/toy/2012_Fall") # Create two accounts self.student = 'view@test.com' @@ -346,14 +342,9 @@ class TestViewAuth(PageLoader): def setUp(self): xmodule.modulestore.django._MODULESTORES = {} - courses = modulestore().get_courses() - def find_course(course_id): - """Assumes the course is present""" - return [c for c in courses if c.id==course_id][0] - - self.full = find_course("edX/full/6.002_Spring_2012") - self.toy = find_course("edX/toy/2012_Fall") + self.full = modulestore().get_course("edX/full/6.002_Spring_2012") + self.toy = modulestore().get_course("edX/toy/2012_Fall") # Create two accounts self.student = 'view@test.com' diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests.py index 532c0c3f68..55e63c8dc2 100644 --- a/lms/djangoapps/instructor/tests.py +++ b/lms/djangoapps/instructor/tests.py @@ -33,12 +33,8 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): xmodule.modulestore.django._MODULESTORES = {} courses = modulestore().get_courses() - def find_course(name): - """Assumes the course is present""" - return [c for c in courses if c.location.course==name][0] - - self.full = find_course("full") - self.toy = find_course("toy") + self.full = modulestore().get_course("edX/full/6.002_Spring_2012") + self.toy = modulestore().get_course("edX/toy/2012_Fall") # Create two accounts self.student = 'view@test.com' @@ -49,9 +45,12 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): self.activate_user(self.student) self.activate_user(self.instructor) - group_name = _course_staff_group_name(self.toy.location) - g = Group.objects.create(name=group_name) - g.user_set.add(ct.user(self.instructor)) + def make_instructor(course): + group_name = _course_staff_group_name(course.location) + g = Group.objects.create(name=group_name) + g.user_set.add(ct.user(self.instructor)) + + make_instructor(self.toy) self.logout() self.login(self.instructor, self.password) @@ -67,9 +66,9 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): self.assertEqual(response['Content-Type'],'text/csv',msg) - cdisp = response['Content-Disposition'].replace('TT_2012','2012') # jenkins course_id is TT_2012_Fall instead of 2012_Fall? - msg += "cdisp = '{0}'\n".format(cdisp) - self.assertEqual(cdisp,'attachment; filename=grades_edX/toy/2012_Fall.csv',msg) + cdisp = response['Content-Disposition'] + msg += "Content-Disposition = '%s'\n" % cdisp + self.assertEqual(cdisp, 'attachment; filename=grades_{0}.csv'.format(course.id), msg) body = response.content.replace('\r','') msg += "body = '{0}'\n".format(body) @@ -77,6 +76,8 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): expected_body = '''"ID","Username","Full Name","edX email","External email","HW 01","HW 02","HW 03","HW 04","HW 05","HW 06","HW 07","HW 08","HW 09","HW 10","HW 11","HW 12","HW Avg","Lab 01","Lab 02","Lab 03","Lab 04","Lab 05","Lab 06","Lab 07","Lab 08","Lab 09","Lab 10","Lab 11","Lab 12","Lab Avg","Midterm","Final" "2","u2","Fred Weasley","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0.0","0.0" ''' + # All the not-actually-in-the-course hw and labs come from the + # default grading policy string in graders.py self.assertEqual(body, expected_body, msg) FORUM_ROLES = [ FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA ] diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index 8a33f1d60b..b877a7236d 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -90,7 +90,7 @@ def instructor_dashboard(request, course_id): try: group = Group.objects.get(name=staffgrp) except Group.DoesNotExist: - group = Group(name=staffgrp) # create the group + group = Group(name=staffgrp) # create the group group.save() return group @@ -380,7 +380,7 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True, enrolled_students = User.objects.filter(courseenrollment__course_id=course_id).prefetch_related("groups").order_by('username') header = ['ID', 'Username', 'Full Name', 'edX email', 'External email'] - if get_grades: + if get_grades and enrolled_students.count() > 0: # just to construct the header gradeset = grades.grade(enrolled_students[0], request, course, keep_raw_scores=get_raw_scores) # log.debug('student {0} gradeset {1}'.format(enrolled_students[0], gradeset)) From 4b8708c2e4278bae5f005f212c79983f0a8f5a88 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 15:24:11 -0500 Subject: [PATCH 040/234] move a comment --- lms/djangoapps/instructor/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests.py index 55e63c8dc2..4f8ac140b0 100644 --- a/lms/djangoapps/instructor/tests.py +++ b/lms/djangoapps/instructor/tests.py @@ -73,11 +73,11 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): body = response.content.replace('\r','') msg += "body = '{0}'\n".format(body) + # All the not-actually-in-the-course hw and labs come from the + # default grading policy string in graders.py expected_body = '''"ID","Username","Full Name","edX email","External email","HW 01","HW 02","HW 03","HW 04","HW 05","HW 06","HW 07","HW 08","HW 09","HW 10","HW 11","HW 12","HW Avg","Lab 01","Lab 02","Lab 03","Lab 04","Lab 05","Lab 06","Lab 07","Lab 08","Lab 09","Lab 10","Lab 11","Lab 12","Lab Avg","Midterm","Final" "2","u2","Fred Weasley","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0.0","0.0" ''' - # All the not-actually-in-the-course hw and labs come from the - # default grading policy string in graders.py self.assertEqual(body, expected_body, msg) FORUM_ROLES = [ FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA ] From 055aeae0b6b75bf31c733a0f618a24f319f92c74 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 15:47:49 -0500 Subject: [PATCH 041/234] fix comment in access.py --- lms/djangoapps/courseware/access.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index 00b4c763b3..ba9b8a3bc0 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -34,7 +34,8 @@ def has_access(user, obj, action): user: a Django user object. May be anonymous. - obj: The object to check access for. For now, a module or descriptor. + obj: The object to check access for. A module, descriptor, location, or + certain special strings (e.g. 'global') action: A string specifying the action that the client is trying to perform. From ff1192657015e67f0f057309842fa094d867a2e7 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 15:48:38 -0500 Subject: [PATCH 042/234] Initial impl and basic access tests for staff grading service --- lms/djangoapps/courseware/tests/tests.py | 14 +- .../instructor/staff_grading_service.py | 120 +++++++++++++++++- lms/djangoapps/instructor/tests.py | 54 ++++++-- lms/envs/common.py | 4 + 4 files changed, 175 insertions(+), 17 deletions(-) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 1eebf0f408..eb026e9c6b 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -221,8 +221,7 @@ class PageLoader(ActivateLoginTestCase): def check_for_get_code(self, code, url): """ - Check that we got the expected code. Hacks around our broken 404 - handling. + Check that we got the expected code when accessing url via GET. """ resp = self.client.get(url) self.assertEqual(resp.status_code, code, @@ -230,6 +229,17 @@ class PageLoader(ActivateLoginTestCase): .format(resp.status_code, url, code)) + def check_for_post_code(self, code, url, data={}): + """ + Check that we got the expected code when accessing url via POST. + """ + resp = self.client.post(url, data) + self.assertEqual(resp.status_code, code, + "got code {0} for url '{1}'. Expected code {2}" + .format(resp.status_code, url, code)) + + + def check_pages_load(self, course_name, data_dir, modstore): """Make all locations in course load""" print "Checking course {0} in {1}".format(course_name, data_dir) diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index d4a4d82e63..e4f82cd5e0 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -3,14 +3,20 @@ This module provides views that proxy to the staff grading backend service. """ import json +import logging import requests import sys +from django.conf import settings from django.http import Http404 from django.http import HttpResponse - +from courseware.access import has_access from util.json_request import expect_json +from xmodule.course_module import CourseDescriptor + +log = logging.getLogger("mitx.courseware") + class GradingServiceError(Exception): pass @@ -37,21 +43,121 @@ class StaffGradingService(object): return r.text + def save_grade(course_id, submission_id, score, feedback): + """ + Save a grade. + + TODO: what is data? + + Returns json, or raises GradingServiceError if there's a problem. + """ + try: + r = self.session.get(url + 'save_grade') + except requests.exceptions.ConnectionError as err: + # reraise as promised GradingServiceError, but preserve stacktrace. + raise GradingServiceError, str(err), sys.exc_info()[2] + + return r.text + +_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL) + + +def _err_response(msg): + """ + Return a HttpResponse with a json dump with success=False, and the given error message. + """ + return HttpResponse(json.dumps({'success': False, 'error': msg})) + + +def _check_access(user, course_id): + """ + Raise 404 if user doesn't have staff access to course_id + """ + course_location = CourseDescriptor.id_to_location(course_id) + if not has_access(user, course_location, 'staff'): + raise Http404 + + return + -#@login_required def get_next(request, course_id): """ + Get the next thing to grade for course_id. + + Returns a json dict with the following keys: + + 'success': bool + + 'submission_id': a unique identifier for the submission, to be passed back + with the grade. + + 'submission': the submission, rendered as read-only html for grading + + 'rubric': the rubric, also rendered as html. + + 'message': if there was no submission available, but nothing went wrong, + there will be a message field. + + 'error': if success is False, will have an error message with more info. """ - d = {'success': False} - return HttpResponse(json.dumps(d)) + _check_access(request.user, course_id) + + return HttpResponse(_get_next(course_id)) + + +def _get_next(course_id): + """ + Implementation of get_next (also called from save_grade) -- returns a json string + """ + + try: + return _service.get_next(course_id) + except GradingServiceError: + log.exception("Error from grading service") + return json.dumps({'success': False, 'error': 'Could not connect to grading service'}) -#@login_required @expect_json def save_grade(request, course_id): """ + Save the grade and feedback for a submission, and, if all goes well, return + the next thing to grade. + Expects the following POST parameters: + 'score': int + 'feedback': string + 'submission_id': int + + Returns the same thing as get_next, except that additional error messages + are possible if something goes wrong with saving the grade. """ - d = {'success': False} - return HttpResponse(json.dumps(d)) + _check_access(request.user, course_id) + + if request.method != 'POST': + raise Http404 + + required = ('score', 'feedback', 'submission_id') + for k in required: + if k not in request.POST.keys(): + return _err_response('Missing required key {0}'.format(k)) + + p = request.POST + + try: + result_json = _service.save_grade(course_id, p['submission_id'], p['score'], p['feedback']) + except GradingServiceError: + log.exception("Error saving grade") + return _err_response('Could not connect to grading service') + + try: + result = json.loads(result_json) + except ValueError: + return _err_response('Grading service returned mal-formatted data.') + + if not result.get('success', False): + log.warning('Got success=False from grading service. Response: %s', result_json) + return _err_response('Grading service failed') + + # Ok, save_grade seemed to work. Get the next submission to grade. + return HttpResponse(_get_next(course_id)) diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests.py index 4f8ac140b0..5f740b8ff9 100644 --- a/lms/djangoapps/instructor/tests.py +++ b/lms/djangoapps/instructor/tests.py @@ -31,7 +31,6 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): def setUp(self): xmodule.modulestore.django._MODULESTORES = {} - courses = modulestore().get_courses() self.full = modulestore().get_course("edX/full/6.002_Spring_2012") self.toy = modulestore().get_course("edX/toy/2012_Fall") @@ -79,6 +78,7 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): "2","u2","Fred Weasley","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0.0","0.0" ''' self.assertEqual(body, expected_body, msg) + FORUM_ROLES = [ FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA ] FORUM_ADMIN_ACTION_SUFFIX = { FORUM_ROLE_ADMINISTRATOR : 'admin', FORUM_ROLE_MODERATOR : 'moderator', FORUM_ROLE_COMMUNITY_TA : 'community TA'} @@ -90,22 +90,19 @@ def action_name(operation, rolename): else: return '{0} forum {1}'.format(operation, FORUM_ADMIN_ACTION_SUFFIX[rolename]) + @override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) class TestInstructorDashboardForumAdmin(ct.PageLoader): ''' Check for change in forum admin role memberships ''' - + def setUp(self): xmodule.modulestore.django._MODULESTORES = {} courses = modulestore().get_courses() - def find_course(name): - """Assumes the course is present""" - return [c for c in courses if c.location.course==name][0] - - self.full = find_course("full") - self.toy = find_course("toy") + self.full = modulestore().get_course("edX/full/6.002_Spring_2012") + self.toy = modulestore().get_course("edX/toy/2012_Fall") # Create two accounts self.student = 'view@test.com' @@ -124,6 +121,8 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): self.login(self.instructor, self.password) self.enroll(self.toy) + + def initialize_roles(self, course_id): self.admin_role = Role.objects.get_or_create(name=FORUM_ROLE_ADMINISTRATOR, course_id=course_id)[0] self.moderator_role = Role.objects.get_or_create(name=FORUM_ROLE_MODERATOR, course_id=course_id)[0] @@ -210,3 +209,42 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): added_roles.sort() roles = ', '.join(added_roles) self.assertTrue(response.content.find('{0}'.format(roles))>=0, 'not finding roles "{0}"'.format(roles)) + + +@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) +class TestStaffGradingService(ct.PageLoader): + ''' + Check that staff grading service proxy works. Basically just checking the + access control and error handling logic -- all the actual work is on the + backend. + ''' + + + + def setUp(self): + xmodule.modulestore.django._MODULESTORES = {} + + self.course_id = "edX/toy/2012_Fall" + self.toy = modulestore().get_course(self.course_id) + def make_instructor(course): + group_name = _course_staff_group_name(course.location) + g = Group.objects.create(name=group_name) + g.user_set.add(ct.user(self.instructor)) + + make_instructor(self.toy) + + self.logout() + + def test_access(self): + """ + Make sure only staff have access. + """ + self.login(self.student, self.password) + self.enroll(self.toy) + + # both get and post should return 404 + for view_name in ('staff_grading_get_next', 'staff_grading_save_grade'): + url = reverse(view_name, kwargs={'course_id': self.course_id}) + self.check_for_get_code(404, url) + self.check_for_post_code(404, url) + diff --git a/lms/envs/common.py b/lms/envs/common.py index 008de7ac84..7ddd2ffb9a 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -322,6 +322,10 @@ WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False WIKI_LINK_LIVE_LOOKUPS = False WIKI_LINK_DEFAULT_LEVEL = 2 +################################# Staff grading config ##################### + +STAFF_GRADING_BACKEND_URL = None + ################################# Jasmine ################################### JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' From d0e2b85e3c0b876263d3e91da41364219f9a6a0b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 16:56:12 -0500 Subject: [PATCH 043/234] Refactor testing code, hook up frontend. - now getting requests from js to server and back, with mocked service. --- lms/djangoapps/courseware/tests/tests.py | 102 +++++++++--------- .../instructor/staff_grading_service.py | 31 ++++-- lms/djangoapps/instructor/tests.py | 58 ++++++++-- lms/djangoapps/instructor/views.py | 5 + .../src/staff_grading/staff_grading.coffee | 16 ++- lms/templates/instructor/staff_grading.html | 43 +++++++- 6 files changed, 187 insertions(+), 68 deletions(-) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index eb026e9c6b..480a119b48 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -222,24 +222,28 @@ class PageLoader(ActivateLoginTestCase): def check_for_get_code(self, code, url): """ Check that we got the expected code when accessing url via GET. + Returns the response. """ resp = self.client.get(url) self.assertEqual(resp.status_code, code, "got code {0} for url '{1}'. Expected code {2}" .format(resp.status_code, url, code)) + return resp def check_for_post_code(self, code, url, data={}): """ Check that we got the expected code when accessing url via POST. + Returns the response. """ resp = self.client.post(url, data) self.assertEqual(resp.status_code, code, "got code {0} for url '{1}'. Expected code {2}" .format(resp.status_code, url, code)) + return resp + - def check_pages_load(self, course_name, data_dir, modstore): """Make all locations in course load""" print "Checking course {0} in {1}".format(course_name, data_dir) @@ -661,46 +665,46 @@ class TestCourseGrader(PageLoader): return [c for c in courses if c.id==course_id][0] self.graded_course = find_course("edX/graded/2012_Fall") - + # create a test student self.student = 'view@test.com' self.password = 'foo' self.create_account('u1', self.student, self.password) self.activate_user(self.student) self.enroll(self.graded_course) - + self.student_user = user(self.student) - + self.factory = RequestFactory() - + def get_grade_summary(self): student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( self.graded_course.id, self.student_user, self.graded_course) - - fake_request = self.factory.get(reverse('progress', - kwargs={'course_id': self.graded_course.id})) - - return grades.grade(self.student_user, fake_request, - self.graded_course, student_module_cache) - - def get_homework_scores(self): - return self.get_grade_summary()['totaled_scores']['Homework'] - - def get_progress_summary(self): - student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( - self.graded_course.id, self.student_user, self.graded_course) - + fake_request = self.factory.get(reverse('progress', kwargs={'course_id': self.graded_course.id})) - progress_summary = grades.progress_summary(self.student_user, fake_request, + return grades.grade(self.student_user, fake_request, + self.graded_course, student_module_cache) + + def get_homework_scores(self): + return self.get_grade_summary()['totaled_scores']['Homework'] + + def get_progress_summary(self): + student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( + self.graded_course.id, self.student_user, self.graded_course) + + fake_request = self.factory.get(reverse('progress', + kwargs={'course_id': self.graded_course.id})) + + progress_summary = grades.progress_summary(self.student_user, fake_request, self.graded_course, student_module_cache) return progress_summary - + def check_grade_percent(self, percent): grade_summary = self.get_grade_summary() - self.assertEqual(percent, grade_summary['percent']) - + self.assertEqual(grade_summary['percent'], percent) + def submit_question_answer(self, problem_url_name, responses): """ The field names of a problem are hard to determine. This method only works @@ -710,96 +714,96 @@ class TestCourseGrader(PageLoader): input_i4x-edX-graded-problem-H1P3_2_2 """ problem_location = "i4x://edX/graded/problem/{0}".format(problem_url_name) - - modx_url = reverse('modx_dispatch', + + modx_url = reverse('modx_dispatch', kwargs={ 'course_id' : self.graded_course.id, 'location' : problem_location, 'dispatch' : 'problem_check', } ) - + resp = self.client.post(modx_url, { 'input_i4x-edX-graded-problem-{0}_2_1'.format(problem_url_name): responses[0], 'input_i4x-edX-graded-problem-{0}_2_2'.format(problem_url_name): responses[1], }) print "modx_url" , modx_url, "responses" , responses print "resp" , resp - + return resp - + def problem_location(self, problem_url_name): return "i4x://edX/graded/problem/{0}".format(problem_url_name) - + def reset_question_answer(self, problem_url_name): problem_location = self.problem_location(problem_url_name) - - modx_url = reverse('modx_dispatch', + + modx_url = reverse('modx_dispatch', kwargs={ 'course_id' : self.graded_course.id, 'location' : problem_location, 'dispatch' : 'problem_reset', } ) - + resp = self.client.post(modx_url) - return resp - + return resp + def test_get_graded(self): #### Check that the grader shows we have 0% in the course self.check_grade_percent(0) - + #### Submit the answers to a few problems as ajax calls def earned_hw_scores(): """Global scores, each Score is a Problem Set""" return [s.earned for s in self.get_homework_scores()] - + def score_for_hw(hw_url_name): hw_section = [section for section in self.get_progress_summary()[0]['sections'] if section.get('url_name') == hw_url_name][0] return [s.earned for s in hw_section['scores']] - + # Only get half of the first problem correct self.submit_question_answer('H1P1', ['Correct', 'Incorrect']) self.check_grade_percent(0.06) self.assertEqual(earned_hw_scores(), [1.0, 0, 0]) # Order matters self.assertEqual(score_for_hw('Homework1'), [1.0, 0.0]) - + # Get both parts of the first problem correct self.reset_question_answer('H1P1') self.submit_question_answer('H1P1', ['Correct', 'Correct']) self.check_grade_percent(0.13) self.assertEqual(earned_hw_scores(), [2.0, 0, 0]) self.assertEqual(score_for_hw('Homework1'), [2.0, 0.0]) - + # This problem is shown in an ABTest self.submit_question_answer('H1P2', ['Correct', 'Correct']) self.check_grade_percent(0.25) self.assertEqual(earned_hw_scores(), [4.0, 0.0, 0]) - self.assertEqual(score_for_hw('Homework1'), [2.0, 2.0]) - + self.assertEqual(score_for_hw('Homework1'), [2.0, 2.0]) + # This problem is hidden in an ABTest. Getting it correct doesn't change total grade self.submit_question_answer('H1P3', ['Correct', 'Correct']) self.check_grade_percent(0.25) self.assertEqual(score_for_hw('Homework1'), [2.0, 2.0]) - + # On the second homework, we only answer half of the questions. # Then it will be dropped when homework three becomes the higher percent # This problem is also weighted to be 4 points (instead of default of 2) - # If the problem was unweighted the percent would have been 0.38 so we + # If the problem was unweighted the percent would have been 0.38 so we # know it works. self.submit_question_answer('H2P1', ['Correct', 'Correct']) self.check_grade_percent(0.42) - self.assertEqual(earned_hw_scores(), [4.0, 4.0, 0]) - + self.assertEqual(earned_hw_scores(), [4.0, 4.0, 0]) + # Third homework self.submit_question_answer('H3P1', ['Correct', 'Correct']) self.check_grade_percent(0.42) # Score didn't change - self.assertEqual(earned_hw_scores(), [4.0, 4.0, 2.0]) - + self.assertEqual(earned_hw_scores(), [4.0, 4.0, 2.0]) + self.submit_question_answer('H3P2', ['Correct', 'Correct']) self.check_grade_percent(0.5) # Now homework2 dropped. Score changes - self.assertEqual(earned_hw_scores(), [4.0, 4.0, 4.0]) - + self.assertEqual(earned_hw_scores(), [4.0, 4.0, 4.0]) + # Now we answer the final question (worth half of the grade) self.submit_question_answer('FinalQuestion', ['Correct', 'Correct']) self.check_grade_percent(1.0) # Hooray! We got 100% diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index e4f82cd5e0..2a2a7a3552 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -21,6 +21,25 @@ log = logging.getLogger("mitx.courseware") class GradingServiceError(Exception): pass + +class MockStaffGradingService(object): + """ + A simple mockup of a staff grading service, testing. + """ + def __init__(self): + self.cnt = 0 + + def get_next(self, course_id): + self.cnt += 1 + return json.dumps({'success': True, + 'submission_id': self.cnt, + 'submission': 'Test submission {cnt}'.format(cnt=self.cnt), + 'rubric': 'A rubric'}) + + def save_grade(self, course_id, submission_id, score, feedback): + return self.get_next(course_id) + + class StaffGradingService(object): """ Interface to staff grading backend. @@ -30,20 +49,20 @@ class StaffGradingService(object): # TODO: add auth self.session = requests.session() - def get_next(course_id): + def get_next(self, course_id): """ Get the next thing to grade. Returns json, or raises GradingServiceError if there's a problem. """ try: - r = self.session.get(url + 'get_next') + r = self.session.get(self.url + 'get_next') except requests.exceptions.ConnectionError as err: # reraise as promised GradingServiceError, but preserve stacktrace. raise GradingServiceError, str(err), sys.exc_info()[2] return r.text - def save_grade(course_id, submission_id, score, feedback): + def save_grade(self, course_id, submission_id, score, feedback): """ Save a grade. @@ -52,15 +71,15 @@ class StaffGradingService(object): Returns json, or raises GradingServiceError if there's a problem. """ try: - r = self.session.get(url + 'save_grade') + r = self.session.get(self.url + 'save_grade') except requests.exceptions.ConnectionError as err: # reraise as promised GradingServiceError, but preserve stacktrace. raise GradingServiceError, str(err), sys.exc_info()[2] return r.text -_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL) - +#_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL) +_service = MockStaffGradingService() def _err_response(msg): """ diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests.py index 5f740b8ff9..28a56ad9de 100644 --- a/lms/djangoapps/instructor/tests.py +++ b/lms/djangoapps/instructor/tests.py @@ -8,15 +8,24 @@ Notes for running by hand: django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/instructor """ +import courseware.tests.tests as ct + +import json + +from nose import SkipTest +from mock import patch, Mock + from override_settings import override_settings -from django.contrib.auth.models import \ - Group # Need access to internal func to put users in the right group +# Need access to internal func to put users in the right group +from django.contrib.auth.models import Group + from django.core.urlresolvers import reverse from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, \ FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_STUDENT from django_comment_client.utils import has_forum_access +from instructor import staff_grading_service from courseware.access import _course_staff_group_name import courseware.tests.tests as ct from xmodule.modulestore.django import modulestore @@ -79,7 +88,7 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): ''' self.assertEqual(body, expected_body, msg) - + FORUM_ROLES = [ FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA ] FORUM_ADMIN_ACTION_SUFFIX = { FORUM_ROLE_ADMINISTRATOR : 'admin', FORUM_ROLE_MODERATOR : 'moderator', FORUM_ROLE_COMMUNITY_TA : 'community TA'} FORUM_ADMIN_USER = { FORUM_ROLE_ADMINISTRATOR : 'forumadmin', FORUM_ROLE_MODERATOR : 'forummoderator', FORUM_ROLE_COMMUNITY_TA : 'forummoderator'} @@ -91,6 +100,8 @@ def action_name(operation, rolename): return '{0} forum {1}'.format(operation, FORUM_ADMIN_ACTION_SUFFIX[rolename]) +_mock_service = staff_grading_service.MockStaffGradingService() + @override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) class TestInstructorDashboardForumAdmin(ct.PageLoader): ''' @@ -101,8 +112,15 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): xmodule.modulestore.django._MODULESTORES = {} courses = modulestore().get_courses() +<<<<<<< HEAD self.full = modulestore().get_course("edX/full/6.002_Spring_2012") self.toy = modulestore().get_course("edX/toy/2012_Fall") +======= + + + self.course_id = "edX/toy/2012_Fall" + self.toy = modulestore().get_course(self.course_id) +>>>>>>> Refactor testing code, hook up frontend. # Create two accounts self.student = 'view@test.com' @@ -122,7 +140,7 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): self.enroll(self.toy) - + def initialize_roles(self, course_id): self.admin_role = Role.objects.get_or_create(name=FORUM_ROLE_ADMINISTRATOR, course_id=course_id)[0] self.moderator_role = Role.objects.get_or_create(name=FORUM_ROLE_MODERATOR, course_id=course_id)[0] @@ -220,7 +238,7 @@ class TestStaffGradingService(ct.PageLoader): ''' - + def setUp(self): xmodule.modulestore.django._MODULESTORES = {} @@ -240,7 +258,6 @@ class TestStaffGradingService(ct.PageLoader): Make sure only staff have access. """ self.login(self.student, self.password) - self.enroll(self.toy) # both get and post should return 404 for view_name in ('staff_grading_get_next', 'staff_grading_save_grade'): @@ -248,3 +265,32 @@ class TestStaffGradingService(ct.PageLoader): self.check_for_get_code(404, url) self.check_for_post_code(404, url) +<<<<<<< HEAD +======= + + @patch.object(staff_grading_service, '_service', _mock_service) + def test_get_next(self): + self.login(self.instructor, self.password) + + url = reverse('staff_grading_get_next', kwargs={'course_id': self.course_id}) + + r = self.check_for_get_code(200, url) + d = json.loads(r.content) + self.assertTrue(d['success']) + self.assertEquals(d['submission_id'], _mock_service.cnt) + + + @patch.object(staff_grading_service, '_service', _mock_service) + def test_save_grade(self): + self.login(self.instructor, self.password) + + url = reverse('staff_grading_save_grade', kwargs={'course_id': self.course_id}) + + data = {'score': '12', 'feedback': 'great!', 'submission_id': '123'} + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertTrue(d['success'], str(d)) + self.assertEquals(d['submission_id'], _mock_service.cnt) + + +>>>>>>> Refactor testing code, hook up frontend. diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index b877a7236d..389a64721a 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -422,10 +422,15 @@ def staff_grading(request, course_id): grading = StaffGrading(course) + ajax_url = reverse('staff_grading', kwargs={'course_id': course_id}) + if not ajax_url.endswith('/'): + ajax_url += '/' + return render_to_response('instructor/staff_grading.html', { 'view_html': grading.get_html(), 'course': course, 'course_id': course_id, + 'ajax_url': ajax_url, # Checked above 'staff_access': True, }) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index e410a3c41d..7440277d98 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -24,6 +24,7 @@ class StaffGradingBackend success: true submission: 'submission! ' + @mock_cnt rubric: 'A rubric! ' + @mock_cnt + submission_id: @mock_cnt else if cmd == 'save_grade' console.log("eval: #{data.score} pts, Feedback: #{data.feedback}") @@ -31,6 +32,7 @@ class StaffGradingBackend success: true submission: 'another submission! ' + @mock_cnt rubric: 'A rubric!' + @mock_cnt + submission_id: @mock_cnt else response = success: false @@ -74,6 +76,7 @@ class StaffGrading # model state @state = state_no_data + @submission_id = null @submission = '' @rubric = '' @error_msg = '' @@ -110,7 +113,7 @@ class StaffGrading if response.success if response.submission - @data_loaded(response.submission, response.rubric) + @data_loaded(response.submission, response.rubric, response.submission_id) else @no_more(response.message) else @@ -122,7 +125,10 @@ class StaffGrading @backend.post('get_next', {}, @ajax_callback) submit_and_get_next: () -> - data = {score: @score, feedback: @feedback_area.val()} + data = + score: @score + feedback: @feedback_area.val() + submission_id: @submission_id @backend.post('save_grade', data, @ajax_callback) @@ -130,9 +136,10 @@ class StaffGrading @error_msg = msg @state = state_error - data_loaded: (submission, rubric) -> + data_loaded: (submission, rubric, submission_id) -> @submission = submission @rubric = rubric + @submission_id = submission_id @feedback_area.val('') @score = null @state = state_grading @@ -140,6 +147,7 @@ class StaffGrading no_more: (message) -> @submission = null @rubric = null + @submission_id = null @message = message @state = state_no_data @@ -196,7 +204,7 @@ class StaffGrading # for now, just create an instance and load it... -mock_backend = true +mock_backend = false ajax_url = $('.staff-grading').data('ajax_url') backend = new StaffGradingBackend(ajax_url, mock_backend) diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index d9d183c161..d8e834d4f6 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -15,7 +15,44 @@
-
- ${view_html} -
+ +
+

Staff grading

+ +
+
+ +
+
+ +
+

Submission

+
+
+
+ +
+

Rubric

+
+
+ +
+ +

+ + + + +

+
+ +
+ +
+ +
+ +
+
From a584f06bac1d03ca558491a40d9cadd9a5bf0ccf Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 18:00:48 -0500 Subject: [PATCH 044/234] Add support for varying max_score. --- .../instructor/staff_grading_service.py | 26 +++++++--- lms/djangoapps/instructor/tests.py | 16 ++---- .../src/staff_grading/staff_grading.coffee | 50 +++++++++++++------ .../src/staff_grading/test_grading.html | 6 +-- 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index 2a2a7a3552..11f6189547 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -34,9 +34,10 @@ class MockStaffGradingService(object): return json.dumps({'success': True, 'submission_id': self.cnt, 'submission': 'Test submission {cnt}'.format(cnt=self.cnt), + 'max_score': 2 + self.cnt % 3, 'rubric': 'A rubric'}) - def save_grade(self, course_id, submission_id, score, feedback): + def save_grade(self, course_id, grader_id, submission_id, score, feedback): return self.get_next(course_id) @@ -62,16 +63,23 @@ class StaffGradingService(object): return r.text - def save_grade(self, course_id, submission_id, score, feedback): + def save_grade(self, course_id, grader_id, submission_id, score, feedback): """ - Save a grade. + Save a score and feedback for a submission. - TODO: what is data? + Returns json dict with keys + 'success': bool + 'error': error msg, if something went wrong. - Returns json, or raises GradingServiceError if there's a problem. + Raises GradingServiceError if there's a problem connecting. """ try: - r = self.session.get(self.url + 'save_grade') + data = {'course_id': course_id, + 'submission_id': submission_id, + 'score': score, + 'feedback': feedback, + 'grader_id': grader} + r = self.session.post(self.url + 'save_grade') except requests.exceptions.ConnectionError as err: # reraise as promised GradingServiceError, but preserve stacktrace. raise GradingServiceError, str(err), sys.exc_info()[2] @@ -163,7 +171,11 @@ def save_grade(request, course_id): p = request.POST try: - result_json = _service.save_grade(course_id, p['submission_id'], p['score'], p['feedback']) + result_json = _service.save_grade(course_id, + request.user.id, + p['submission_id'], + p['score'], + p['feedback']) except GradingServiceError: log.exception("Error saving grade") return _err_response('Could not connect to grading service') diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests.py index 28a56ad9de..87be93128c 100644 --- a/lms/djangoapps/instructor/tests.py +++ b/lms/djangoapps/instructor/tests.py @@ -112,15 +112,9 @@ class TestInstructorDashboardForumAdmin(ct.PageLoader): xmodule.modulestore.django._MODULESTORES = {} courses = modulestore().get_courses() -<<<<<<< HEAD - self.full = modulestore().get_course("edX/full/6.002_Spring_2012") - self.toy = modulestore().get_course("edX/toy/2012_Fall") -======= - self.course_id = "edX/toy/2012_Fall" self.toy = modulestore().get_course(self.course_id) ->>>>>>> Refactor testing code, hook up frontend. # Create two accounts self.student = 'view@test.com' @@ -236,9 +230,6 @@ class TestStaffGradingService(ct.PageLoader): access control and error handling logic -- all the actual work is on the backend. ''' - - - def setUp(self): xmodule.modulestore.django._MODULESTORES = {} @@ -265,8 +256,6 @@ class TestStaffGradingService(ct.PageLoader): self.check_for_get_code(404, url) self.check_for_post_code(404, url) -<<<<<<< HEAD -======= @patch.object(staff_grading_service, '_service', _mock_service) def test_get_next(self): @@ -286,11 +275,12 @@ class TestStaffGradingService(ct.PageLoader): url = reverse('staff_grading_save_grade', kwargs={'course_id': self.course_id}) - data = {'score': '12', 'feedback': 'great!', 'submission_id': '123'} + data = {'score': '12', + 'feedback': 'great!', + 'submission_id': '123'} r = self.check_for_post_code(200, url, data) d = json.loads(r.content) self.assertTrue(d['success'], str(d)) self.assertEquals(d['submission_id'], _mock_service.cnt) ->>>>>>> Refactor testing code, hook up frontend. diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 7440277d98..b36b9e33e7 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -18,21 +18,19 @@ class StaffGradingBackend mock: (cmd, data) -> # Return a mock response to cmd and data - @mock_cnt++ if cmd == 'get_next' + @mock_cnt++ response = success: true submission: 'submission! ' + @mock_cnt rubric: 'A rubric! ' + @mock_cnt submission_id: @mock_cnt + max_score: 2 + @mock_cnt % 3 else if cmd == 'save_grade' console.log("eval: #{data.score} pts, Feedback: #{data.feedback}") response = - success: true - submission: 'another submission! ' + @mock_cnt - rubric: 'A rubric!' + @mock_cnt - submission_id: @mock_cnt + @mock('get_next', {}) else response = success: false @@ -72,6 +70,7 @@ class StaffGrading @submission_wrapper = $('.submission-wrapper') @rubric_wrapper = $('.rubric-wrapper') @feedback_area = $('.feedback-area') + @score_selection_container = $('.score-selection-container') @submit_button = $('.submit-button') # model state @@ -81,15 +80,12 @@ class StaffGrading @rubric = '' @error_msg = '' @message = '' + @max_score = 0 @score = null # action handlers @submit_button.click @submit - # TODO: hook up an event to the input changing, which updates - # @score (instead of the individual hacks) - $('#correct-radio').click @graded_callback - $('#incorrect-radio').click @graded_callback # render intial state @render_view() @@ -98,6 +94,24 @@ class StaffGrading @get_next_submission() + setup_score_selection: => + # first, get rid of all the old inputs, if any. + @score_selection_container.html('') + + # Now create new labels and inputs for each possible score. + for score in [1..@max_score] + id = 'score-' + score + label = """""" + + input = """ + + """ + @score_selection_container.append(label + input) + + # And now hook up an event handler again + $("input[name='score-selection']").change @graded_callback + + set_button_text: (text) => @submit_button.attr('value', text) @@ -113,7 +127,7 @@ class StaffGrading if response.success if response.submission - @data_loaded(response.submission, response.rubric, response.submission_id) + @data_loaded(response.submission, response.rubric, response.submission_id, response.max_score) else @no_more(response.message) else @@ -136,11 +150,12 @@ class StaffGrading @error_msg = msg @state = state_error - data_loaded: (submission, rubric, submission_id) -> + data_loaded: (submission, rubric, submission_id, max_score) -> @submission = submission @rubric = rubric @submission_id = submission_id @feedback_area.val('') + @max_score = max_score @score = null @state = state_grading @@ -149,6 +164,8 @@ class StaffGrading @rubric = null @submission_id = null @message = message + @score = null + @max_score = 0 @state = state_no_data render_view: () -> @@ -157,6 +174,9 @@ class StaffGrading show_submit_button = true @message_container.html(@message) + if @backend.mock_backend + @message_container.append("

NOTE: Mocking backend.

") + @error_container.html(@error_msg) if @state == state_error @@ -170,10 +190,8 @@ class StaffGrading # no submit button until user picks grade. show_submit_button = false - # TODO: clean up with proper input-related logic - $('#correct-radio')[0].checked = false - $('#incorrect-radio')[0].checked = false - + @setup_score_selection() + else if @state == state_graded show_grading_elements = true @set_button_text('Submit') @@ -204,7 +222,7 @@ class StaffGrading # for now, just create an instance and load it... -mock_backend = false +mock_backend = true ajax_url = $('.staff-grading').data('ajax_url') backend = new StaffGradingBackend(ajax_url, mock_backend) diff --git a/lms/static/coffee/src/staff_grading/test_grading.html b/lms/static/coffee/src/staff_grading/test_grading.html index 8a1cde1fd4..9b84d0703b 100644 --- a/lms/static/coffee/src/staff_grading/test_grading.html +++ b/lms/static/coffee/src/staff_grading/test_grading.html @@ -30,11 +30,7 @@
-

- - - - +

From d1b433dba57dfe60e2872e33c23ba5841abe7c99 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 19 Nov 2012 18:08:07 -0500 Subject: [PATCH 045/234] add adaptive-max-score html to lms view --- lms/static/coffee/src/staff_grading/staff_grading.coffee | 4 ++-- lms/templates/instructor/staff_grading.html | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index b36b9e33e7..a3d5cf939b 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -105,7 +105,7 @@ class StaffGrading input = """ - """ + """ # " fix broken parsing in emacs @score_selection_container.append(label + input) # And now hook up an event handler again @@ -222,7 +222,7 @@ class StaffGrading # for now, just create an instance and load it... -mock_backend = true +mock_backend = false ajax_url = $('.staff-grading').data('ajax_url') backend = new StaffGradingBackend(ajax_url, mock_backend) diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index d8e834d4f6..dbf2e97dde 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -37,13 +37,10 @@
+ -

- - - - +

From d2cc8696eda59d42eb6eb9014ac09631db87dd24 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 20 Nov 2012 11:11:18 -0500 Subject: [PATCH 046/234] Add some initial css. --- .../src/staff_grading/staff_grading.coffee | 4 +-- lms/static/sass/course.scss | 1 + lms/static/sass/course/_staff_grading.scss | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 lms/static/sass/course/_staff_grading.scss diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index a3d5cf939b..ffdb28ccfd 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -96,7 +96,7 @@ class StaffGrading setup_score_selection: => # first, get rid of all the old inputs, if any. - @score_selection_container.html('') + @score_selection_container.html('Choose score: ') # Now create new labels and inputs for each possible score. for score in [1..@max_score] @@ -106,7 +106,7 @@ class StaffGrading input = """ """ # " fix broken parsing in emacs - @score_selection_container.append(label + input) + @score_selection_container.append(input + label) # And now hook up an event handler again $("input[name='score-selection']").change @graded_callback diff --git a/lms/static/sass/course.scss b/lms/static/sass/course.scss index acd735d25e..e900e589b2 100644 --- a/lms/static/sass/course.scss +++ b/lms/static/sass/course.scss @@ -43,6 +43,7 @@ @import "course/profile"; @import "course/gradebook"; @import "course/tabs"; +@import "course/staff_grading"; // instructor @import "course/instructor/instructor"; diff --git a/lms/static/sass/course/_staff_grading.scss b/lms/static/sass/course/_staff_grading.scss new file mode 100644 index 0000000000..4900f78bd0 --- /dev/null +++ b/lms/static/sass/course/_staff_grading.scss @@ -0,0 +1,29 @@ +div.staff-grading { + textarea.feedback-area { + height: 100px; + margin: 20px; + } + + div { + margin: 10px; + } + + label { + margin: 10px; + padding: 5px; + display: inline-block; + min-width: 50px; + background-color: #CCC; + text-size: 1.5em; + } + + /* Toggled State */ + input[type=radio]:checked + label { + background: #666; + color: white; + } + + input[name='score-selection'] { + display: none; + } +} From edd3277597644f7d7c509d2da18f135eb5ef9c5b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 20 Nov 2012 18:25:16 -0500 Subject: [PATCH 047/234] Tweaks to actually work with the backend. - specify mime type - right urls --- .../instructor/staff_grading_service.py | 39 ++++++++++++------- lms/envs/dev.py | 3 ++ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index 11f6189547..47f483e75f 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -8,8 +8,7 @@ import requests import sys from django.conf import settings -from django.http import Http404 -from django.http import HttpResponse +from django.http import HttpResponse, Http404 from courseware.access import has_access from util.json_request import expect_json @@ -29,7 +28,7 @@ class MockStaffGradingService(object): def __init__(self): self.cnt = 0 - def get_next(self, course_id): + def get_next(self, course_id, grader_id): self.cnt += 1 return json.dumps({'success': True, 'submission_id': self.cnt, @@ -47,16 +46,20 @@ class StaffGradingService(object): """ def __init__(self, url): self.url = url + self.get_next_url = url + '/get_next_submission/' + self.save_grade_url = url + '/save_grade/' # TODO: add auth self.session = requests.session() - def get_next(self, course_id): + def get_next(self, course_id, grader_id): """ Get the next thing to grade. Returns json, or raises GradingServiceError if there's a problem. """ try: - r = self.session.get(self.url + 'get_next') + r = self.session.get(self.get_next_url, + params={'course_id': course_id, + 'grader_id': grader_id}) except requests.exceptions.ConnectionError as err: # reraise as promised GradingServiceError, but preserve stacktrace. raise GradingServiceError, str(err), sys.exc_info()[2] @@ -78,22 +81,24 @@ class StaffGradingService(object): 'submission_id': submission_id, 'score': score, 'feedback': feedback, - 'grader_id': grader} - r = self.session.post(self.url + 'save_grade') + 'grader_id': grader_id} + + r = self.session.post(self.save_grade_url, data=data) except requests.exceptions.ConnectionError as err: # reraise as promised GradingServiceError, but preserve stacktrace. raise GradingServiceError, str(err), sys.exc_info()[2] return r.text -#_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL) -_service = MockStaffGradingService() +_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL) +#_service = MockStaffGradingService() def _err_response(msg): """ Return a HttpResponse with a json dump with success=False, and the given error message. """ - return HttpResponse(json.dumps({'success': False, 'error': msg})) + return HttpResponse(json.dumps({'success': False, 'error': msg}), + mimetype="application/json") def _check_access(user, course_id): @@ -129,16 +134,17 @@ def get_next(request, course_id): """ _check_access(request.user, course_id) - return HttpResponse(_get_next(course_id)) + return HttpResponse(_get_next(course_id, request.user.id), + mimetype="application/json") -def _get_next(course_id): +def _get_next(course_id, grader_id): """ Implementation of get_next (also called from save_grade) -- returns a json string """ try: - return _service.get_next(course_id) + return _service.get_next(course_id, grader_id) except GradingServiceError: log.exception("Error from grading service") return json.dumps({'success': False, 'error': 'Could not connect to grading service'}) @@ -168,11 +174,12 @@ def save_grade(request, course_id): if k not in request.POST.keys(): return _err_response('Missing required key {0}'.format(k)) + grader_id = request.user.id p = request.POST try: result_json = _service.save_grade(course_id, - request.user.id, + grader_id, p['submission_id'], p['score'], p['feedback']) @@ -183,6 +190,7 @@ def save_grade(request, course_id): try: result = json.loads(result_json) except ValueError: + log.exception("save_grade returned broken json: %s", result_json) return _err_response('Grading service returned mal-formatted data.') if not result.get('success', False): @@ -190,5 +198,6 @@ def save_grade(request, course_id): return _err_response('Grading service failed') # Ok, save_grade seemed to work. Get the next submission to grade. - return HttpResponse(_get_next(course_id)) + return HttpResponse(_get_next(course_id, grader_id), + mimetype="application/json") diff --git a/lms/envs/dev.py b/lms/envs/dev.py index bf72284425..57aeeb7bd9 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -102,6 +102,9 @@ SUBDOMAIN_BRANDING = { COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE" +################################# Staff grading config ##################### + +STAFF_GRADING_BACKEND_URL = "http://127.0.0.1:3033/staff_grading" ################################ LMS Migration ################################# From 26f033b79ffe9cda25f870396f6cb645310fbab0 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 20 Nov 2012 18:25:25 -0500 Subject: [PATCH 048/234] allow score 0 --- lms/static/coffee/src/staff_grading/staff_grading.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index ffdb28ccfd..5edc5e5c35 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -99,7 +99,7 @@ class StaffGrading @score_selection_container.html('Choose score: ') # Now create new labels and inputs for each possible score. - for score in [1..@max_score] + for score in [0..@max_score] id = 'score-' + score label = """""" From b48b389a565e6dd938a0009be338bcba6d6f9632 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 26 Nov 2012 16:40:25 -0500 Subject: [PATCH 049/234] implement login into staff grading service. --- .../instructor/staff_grading_service.py | 71 ++++++++++++++++--- lms/envs/common.py | 2 + lms/envs/dev.py | 2 + 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index 47f483e75f..5bff10bcda 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -5,6 +5,7 @@ This module provides views that proxy to the staff grading backend service. import json import logging import requests +from requests.exceptions import RequestException, ConnectionError, HTTPError import sys from django.conf import settings @@ -44,23 +45,70 @@ class StaffGradingService(object): """ Interface to staff grading backend. """ - def __init__(self, url): + def __init__(self, url, username, password): + self.username = username + self.password = password self.url = url + + self.login_url = url + '/login/' self.get_next_url = url + '/get_next_submission/' self.save_grade_url = url + '/save_grade/' + # TODO: add auth self.session = requests.session() + + def _login(self): + """ + Log into the staff grading service. + + Raises requests.exceptions.HTTPError if something goes wrong. + + Returns the decoded json dict of the response. + """ + response = self.session.post(self.login_url, + {'username': self.username, + 'password': self.password,}) + + response.raise_for_status() + + return response.json + + + def _try_with_login(self, operation): + """ + Call operation(), which should return a requests response object. If + the response status code is 302, call _login() and try the operation + again. NOTE: use requests.get(..., allow_redirects=False) to have + requests not auto-follow redirects. + + Returns the result of operation(). Does not catch exceptions. + """ + response = operation() + if response.status_code == 302: + # redirect means we aren't logged in + r = self._login() + if r and not r.get('success'): + log.warning("Couldn't log into staff_grading backend. Response: %s", + r) + # try again + return operation() + + return response + + def get_next(self, course_id, grader_id): """ Get the next thing to grade. Returns json, or raises GradingServiceError if there's a problem. """ + op = lambda: self.session.get(self.get_next_url, + allow_redirects=False, + params={'course_id': course_id, + 'grader_id': grader_id}) try: - r = self.session.get(self.get_next_url, - params={'course_id': course_id, - 'grader_id': grader_id}) - except requests.exceptions.ConnectionError as err: + r = self._try_with_login(op) + except (RequestException, ConnectionError, HTTPError) as err: # reraise as promised GradingServiceError, but preserve stacktrace. raise GradingServiceError, str(err), sys.exc_info()[2] @@ -82,15 +130,20 @@ class StaffGradingService(object): 'score': score, 'feedback': feedback, 'grader_id': grader_id} - - r = self.session.post(self.save_grade_url, data=data) - except requests.exceptions.ConnectionError as err: + + op = lambda: self.session.post(self.save_grade_url, data=data, + allow_redirects=False) + r = self._try_with_login(op) + except (RequestException, ConnectionError, HTTPError) as err: # reraise as promised GradingServiceError, but preserve stacktrace. raise GradingServiceError, str(err), sys.exc_info()[2] return r.text -_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL) +_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL, + settings.STAFF_GRADING_BACKEND_USERNAME, + settings.STAFF_GRADING_BACKEND_PASSWORD, + ) #_service = MockStaffGradingService() def _err_response(msg): diff --git a/lms/envs/common.py b/lms/envs/common.py index 7ddd2ffb9a..005d333e09 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -325,6 +325,8 @@ WIKI_LINK_DEFAULT_LEVEL = 2 ################################# Staff grading config ##################### STAFF_GRADING_BACKEND_URL = None +STAFF_GRADING_BACKEND_USERNAME = None +STAFF_GRADING_BACKEND_PASSWORD = None ################################# Jasmine ################################### JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' diff --git a/lms/envs/dev.py b/lms/envs/dev.py index 57aeeb7bd9..f3cc3e4c63 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -105,6 +105,8 @@ COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE" ################################# Staff grading config ##################### STAFF_GRADING_BACKEND_URL = "http://127.0.0.1:3033/staff_grading" +STAFF_GRADING_BACKEND_USERNAME = "lms" +STAFF_GRADING_BACKEND_PASSWORD = "abcd" ################################ LMS Migration ################################# From d28cd4f42910852e3242091901301b5f20bf9225 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 26 Nov 2012 17:09:52 -0500 Subject: [PATCH 050/234] use a dict for backend config. Load it on aws. --- .../instructor/staff_grading_service.py | 19 ++++++++----------- lms/envs/aws.py | 3 +++ lms/envs/common.py | 4 +--- lms/envs/dev.py | 11 ++++++----- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index 5bff10bcda..05c131ed56 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -45,14 +45,14 @@ class StaffGradingService(object): """ Interface to staff grading backend. """ - def __init__(self, url, username, password): - self.username = username - self.password = password - self.url = url + def __init__(self, config): + self.username = config['username'] + self.password = config['password'] + self.url = config['url'] - self.login_url = url + '/login/' - self.get_next_url = url + '/get_next_submission/' - self.save_grade_url = url + '/save_grade/' + self.login_url = self.url + '/login/' + self.get_next_url = self.url + '/get_next_submission/' + self.save_grade_url = self.url + '/save_grade/' # TODO: add auth self.session = requests.session() @@ -140,10 +140,7 @@ class StaffGradingService(object): return r.text -_service = StaffGradingService(settings.STAFF_GRADING_BACKEND_URL, - settings.STAFF_GRADING_BACKEND_USERNAME, - settings.STAFF_GRADING_BACKEND_PASSWORD, - ) +_service = StaffGradingService(settings.STAFF_GRADING_INTERFACE) #_service = MockStaffGradingService() def _err_response(msg): diff --git a/lms/envs/aws.py b/lms/envs/aws.py index b58bc5602b..d1abce8a6d 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -76,5 +76,8 @@ DATABASES = AUTH_TOKENS['DATABASES'] XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE'] +STAFF_GRADING_BACKEND = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE') + + PEARSON_TEST_USER = "pearsontest" PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD") diff --git a/lms/envs/common.py b/lms/envs/common.py index 005d333e09..3d26cb54c9 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -324,9 +324,7 @@ WIKI_LINK_DEFAULT_LEVEL = 2 ################################# Staff grading config ##################### -STAFF_GRADING_BACKEND_URL = None -STAFF_GRADING_BACKEND_USERNAME = None -STAFF_GRADING_BACKEND_PASSWORD = None +STAFF_GRADING_INTERFACE = None ################################# Jasmine ################################### JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' diff --git a/lms/envs/dev.py b/lms/envs/dev.py index f3cc3e4c63..0ad42f67d3 100644 --- a/lms/envs/dev.py +++ b/lms/envs/dev.py @@ -39,7 +39,7 @@ DATABASES = { } CACHES = { - # This is the cache used for most things. + # This is the cache used for most things. # In staging/prod envs, the sessions also live here. 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -104,10 +104,11 @@ COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE" ################################# Staff grading config ##################### -STAFF_GRADING_BACKEND_URL = "http://127.0.0.1:3033/staff_grading" -STAFF_GRADING_BACKEND_USERNAME = "lms" -STAFF_GRADING_BACKEND_PASSWORD = "abcd" - +STAFF_GRADING_INTERFACE = { + 'url': 'http://127.0.0.1:3033/staff_grading', + 'username': 'lms', + 'password': 'abcd', + } ################################ LMS Migration ################################# MITX_FEATURES['ENABLE_LMS_MIGRATION'] = True From 835f18795afe56f3fd4e6b96935f705d103f0ad2 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 30 Nov 2012 10:27:34 -0500 Subject: [PATCH 051/234] Make tests pass again --- .../instructor/staff_grading_service.py | 30 +++++++++++++++---- lms/djangoapps/instructor/tests.py | 16 +++++++--- lms/envs/common.py | 3 ++ lms/envs/test.py | 6 +++- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index 05c131ed56..c070bd6835 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -38,7 +38,7 @@ class MockStaffGradingService(object): 'rubric': 'A rubric'}) def save_grade(self, course_id, grader_id, submission_id, score, feedback): - return self.get_next(course_id) + return self.get_next(course_id, grader_id) class StaffGradingService(object): @@ -140,8 +140,28 @@ class StaffGradingService(object): return r.text -_service = StaffGradingService(settings.STAFF_GRADING_INTERFACE) -#_service = MockStaffGradingService() +# don't initialize until grading_service() is called--means that just +# importing this file doesn't create objects that may not have the right config +_service = None + +def grading_service(): + """ + Return a staff grading service instance--if settings.MOCK_STAFF_GRADING is True, + returns a mock one, otherwise a real one. + + Caches the result, so changing the setting after the first call to this + function will have no effect. + """ + global _service + if _service is not None: + return _service + + if settings.MOCK_STAFF_GRADING: + _service = MockStaffGradingService() + else: + _service = StaffGradingService(settings.STAFF_GRADING_INTERFACE) + + return _service def _err_response(msg): """ @@ -194,7 +214,7 @@ def _get_next(course_id, grader_id): """ try: - return _service.get_next(course_id, grader_id) + return grading_service().get_next(course_id, grader_id) except GradingServiceError: log.exception("Error from grading service") return json.dumps({'success': False, 'error': 'Could not connect to grading service'}) @@ -228,7 +248,7 @@ def save_grade(request, course_id): p = request.POST try: - result_json = _service.save_grade(course_id, + result_json = grading_service().save_grade(course_id, grader_id, p['submission_id'], p['score'], diff --git a/lms/djangoapps/instructor/tests.py b/lms/djangoapps/instructor/tests.py index 87be93128c..c47eb170fc 100644 --- a/lms/djangoapps/instructor/tests.py +++ b/lms/djangoapps/instructor/tests.py @@ -233,6 +233,14 @@ class TestStaffGradingService(ct.PageLoader): def setUp(self): xmodule.modulestore.django._MODULESTORES = {} + self.student = 'view@test.com' + self.instructor = 'view2@test.com' + self.password = 'foo' + self.create_account('u1', self.student, self.password) + self.create_account('u2', self.instructor, self.password) + self.activate_user(self.student) + self.activate_user(self.instructor) + self.course_id = "edX/toy/2012_Fall" self.toy = modulestore().get_course(self.course_id) def make_instructor(course): @@ -242,6 +250,8 @@ class TestStaffGradingService(ct.PageLoader): make_instructor(self.toy) + self.mock_service = staff_grading_service.grading_service() + self.logout() def test_access(self): @@ -257,7 +267,6 @@ class TestStaffGradingService(ct.PageLoader): self.check_for_post_code(404, url) - @patch.object(staff_grading_service, '_service', _mock_service) def test_get_next(self): self.login(self.instructor, self.password) @@ -266,10 +275,9 @@ class TestStaffGradingService(ct.PageLoader): r = self.check_for_get_code(200, url) d = json.loads(r.content) self.assertTrue(d['success']) - self.assertEquals(d['submission_id'], _mock_service.cnt) + self.assertEquals(d['submission_id'], self.mock_service.cnt) - @patch.object(staff_grading_service, '_service', _mock_service) def test_save_grade(self): self.login(self.instructor, self.password) @@ -281,6 +289,6 @@ class TestStaffGradingService(ct.PageLoader): r = self.check_for_post_code(200, url, data) d = json.loads(r.content) self.assertTrue(d['success'], str(d)) - self.assertEquals(d['submission_id'], _mock_service.cnt) + self.assertEquals(d['submission_id'], self.mock_service.cnt) diff --git a/lms/envs/common.py b/lms/envs/common.py index 3d26cb54c9..79d0bb78f9 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -325,6 +325,9 @@ WIKI_LINK_DEFAULT_LEVEL = 2 ################################# Staff grading config ##################### STAFF_GRADING_INTERFACE = None +# Used for testing, debugging +MOCK_STAFF_GRADING = False + ################################# Jasmine ################################### JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' diff --git a/lms/envs/test.py b/lms/envs/test.py index e815efdf4e..ef2a343db4 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -65,6 +65,10 @@ XQUEUE_INTERFACE = { } XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds + +# Don't rely on a real staff grading backend +MOCK_STAFF_GRADING = True + # TODO (cpennington): We need to figure out how envs/test.py can inject things # into common.py so that we don't have to repeat this sort of thing STATICFILES_DIRS = [ @@ -99,7 +103,7 @@ DATABASES = { } CACHES = { - # This is the cache used for most things. + # This is the cache used for most things. # In staging/prod envs, the sessions also live here. 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', From 052807d7f60a877e6924d1240c8f481a23803ba5 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 30 Nov 2012 11:30:02 -0500 Subject: [PATCH 052/234] Change the unique student id calculation to match the id we send to xqueue - can't change the xqueue one because there's data that uses it - we checked, and no one has sent out survey links that use the old sha1() format doing this because we'll need the LMS to be able to send the anon student id to backend services e.g. for peer grading, and passing two different anon student ids to xmodule (one for xqueue, one for other uses) is just asking for confusion. --- common/djangoapps/student/models.py | 15 +++++++-------- lms/djangoapps/courseware/module_render.py | 10 ++-------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 5975853a21..2f5bc3ac04 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -36,7 +36,7 @@ file and check it in at the same time as your model changes. To do that, 3. Add the migration file created in mitx/common/djangoapps/student/migrations/ """ from datetime import datetime -from hashlib import sha1 +import hashlib import json import logging import uuid @@ -197,14 +197,13 @@ def unique_id_for_user(user): """ Return a unique id for a user, suitable for inserting into e.g. personalized survey links. - - Currently happens to be implemented as a sha1 hash of the username - (and thus assumes that usernames don't change). """ - # Using the user id as the salt because it's sort of random, and is already - # in the db. - salt = str(user.id) - return sha1(salt + user.username).hexdigest() + # include the secret key as a salt, and to make the ids unique accross + # different LMS installs. + h = hashlib.md5() + h.update(settings.SECRET_KEY) + h.update(str(user.id)) + return h.hexdigest() ## TODO: Should be renamed to generic UserGroup, and possibly diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index 67927c0ee7..eb7b41b1e9 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -1,4 +1,3 @@ -import hashlib import json import logging import pyparsing @@ -20,6 +19,7 @@ from mitxmako.shortcuts import render_to_string from models import StudentModule, StudentModuleCache from psychometrics.psychoanalyze import make_psychometrics_data_update_handler from static_replace import replace_urls +from student.models import unique_id_for_user from xmodule.errortracker import exc_info_to_str from xmodule.exceptions import NotFoundError from xmodule.modulestore import Location @@ -152,12 +152,6 @@ def _get_module(user, request, location, student_module_cache, course_id, positi if not has_access(user, descriptor, 'load'): return None - # Anonymized student identifier - h = hashlib.md5() - h.update(settings.SECRET_KEY) - h.update(str(user.id)) - anonymous_student_id = h.hexdigest() - # Only check the cache if this module can possibly have state instance_module = None shared_module = None @@ -230,7 +224,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi # by the replace_static_urls code below replace_urls=replace_urls, node_path=settings.NODE_PATH, - anonymous_student_id=anonymous_student_id, + anonymous_student_id=unique_id_for_user(user), ) # pass position specified in URL to module through ModuleSystem system.set('position', position) From 51e148a7a7413176afd8661d2a8defa25bd729c4 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 11:42:24 -0500 Subject: [PATCH 053/234] Add in support for rendering feedback within lms --- common/lib/capa/capa/responsetypes.py | 46 ++++++++++++++++++++++++-- lms/templates/open_ended_error.html | 12 +++++++ lms/templates/open_ended_feedback.html | 16 +++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 lms/templates/open_ended_error.html create mode 100644 lms/templates/open_ended_feedback.html diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index ba9f03549e..8bdfb4f550 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -24,6 +24,7 @@ import os import subprocess import xml.sax.saxutils as saxutils from shapely.geometry import Point, MultiPoint +from django.template.loader import render_to_string # specific library imports from calc import evaluator, UndefinedVariable @@ -1965,6 +1966,8 @@ class OpenEndedResponse(LoncapaResponse): 'max_score' : self.max_score }) + log.debug(xheader) + log.debug(contents) # Submit request. When successful, 'msg' is the prior length of the queue (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) @@ -2027,6 +2030,40 @@ class OpenEndedResponse(LoncapaResponse): def get_initial_display(self): return {self.answer_id: self.initial_display} + def _format_feedback(self, response_items): + """ + Input: + Dictionary called feedback. Must contain keys seen below. + Output: + Return success/fail, error message or feedback template + """ + tags=['feedback_items','score','grader_type'] + + for tag in tags: + if tag not in response_items: + return False, "Grader response missing required feedback key!" + + if 'errors' in response_items['feedback_items']: + return True, render_to_string("open_ended_error.html", {'errors' : response_items['feedback_items']['errors']}) + + + feedback_item_start='
' + feedback_item_end='
' + feedback_long="" + for k,v in response_items: + feedback_long+=feedback_item_start.format(feedback_key=k) + feedback_long+=v + feedback_long+=feedback_item_end + + feedback_template=render_to_string("open_ended_feedback.html",{ + 'grader_type' : response_items['grader_type'], + 'score' : response_items['score'], + 'feedback_long' : feedback_long, + }) + + return True, feedback_template + + def _parse_score_msg(self, score_msg): """ Grader reply is a JSON-dump of the following dict @@ -2052,7 +2089,7 @@ class OpenEndedResponse(LoncapaResponse): log.error("External grader message should be a JSON-serialized dict." " Received score_result = %s" % score_result) return fail - for tag in ['score','feedback']: + for tag in ['score','feedback', 'grader_type']: if tag not in score_result: log.error("External grader message is missing one or more required" " tags: 'score', 'feedback") @@ -2062,7 +2099,12 @@ class OpenEndedResponse(LoncapaResponse): # is safe for the LMS. # 1) Make sure that the message is valid XML (proper opening/closing tags) # 2) TODO: Is the message actually HTML? - feedback = score_result['feedback'] + + feedback = self._format_feedback({ + 'score' : score_result['score'], + 'feedback_items' : score_result['feedback'], + 'grader_type' : score_result['grader_type'], + }) score_ratio=int(score_result['score'])/self.max_score diff --git a/lms/templates/open_ended_error.html b/lms/templates/open_ended_error.html new file mode 100644 index 0000000000..c91beb88b0 --- /dev/null +++ b/lms/templates/open_ended_error.html @@ -0,0 +1,12 @@ +
+
+
+ There was an error with your submission. Please contact course staff. +
+
+
+
+ {errors} +
+
+
\ No newline at end of file diff --git a/lms/templates/open_ended_feedback.html b/lms/templates/open_ended_feedback.html new file mode 100644 index 0000000000..39de3ad1c3 --- /dev/null +++ b/lms/templates/open_ended_feedback.html @@ -0,0 +1,16 @@ +
+
Feedback
+
+
+

Score: ${score}

+ % if grader_type=="ML" +

Number of potential problem areas identified: ${problem_areas}

+ % endif +
+
+
+
+ ${feedback_long} +
+
+
\ No newline at end of file From 15c5072984b5e1bceed2ff1eace82380c433e00f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 11:47:12 -0500 Subject: [PATCH 054/234] Work on template render code --- common/lib/capa/capa/responsetypes.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 8bdfb4f550..c587034931 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2037,20 +2037,15 @@ class OpenEndedResponse(LoncapaResponse): Output: Return success/fail, error message or feedback template """ - tags=['feedback_items','score','grader_type'] - for tag in tags: - if tag not in response_items: - return False, "Grader response missing required feedback key!" - - if 'errors' in response_items['feedback_items']: - return True, render_to_string("open_ended_error.html", {'errors' : response_items['feedback_items']['errors']}) + if not response_items['success']: + return True, render_to_string("open_ended_error.html", {'errors' : response_items['feedback']['errors']}) feedback_item_start='
' feedback_item_end='
' feedback_long="" - for k,v in response_items: + for k,v in response_items['feedback']: feedback_long+=feedback_item_start.format(feedback_key=k) feedback_long+=v feedback_long+=feedback_item_end @@ -2089,7 +2084,7 @@ class OpenEndedResponse(LoncapaResponse): log.error("External grader message should be a JSON-serialized dict." " Received score_result = %s" % score_result) return fail - for tag in ['score','feedback', 'grader_type']: + for tag in ['score','feedback', 'grader_type', 'success', 'errors']: if tag not in score_result: log.error("External grader message is missing one or more required" " tags: 'score', 'feedback") @@ -2100,11 +2095,7 @@ class OpenEndedResponse(LoncapaResponse): # 1) Make sure that the message is valid XML (proper opening/closing tags) # 2) TODO: Is the message actually HTML? - feedback = self._format_feedback({ - 'score' : score_result['score'], - 'feedback_items' : score_result['feedback'], - 'grader_type' : score_result['grader_type'], - }) + feedback = self._format_feedback(score_result) score_ratio=int(score_result['score'])/self.max_score From 7330bef8e6e727708525afa133b93777cb55e02a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 12:11:58 -0500 Subject: [PATCH 055/234] Changes to fix compatibility with controller feedback --- common/lib/capa/capa/responsetypes.py | 19 +++++++------------ lms/templates/open_ended_feedback.html | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index c587034931..42f3091e6f 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2039,21 +2039,17 @@ class OpenEndedResponse(LoncapaResponse): """ if not response_items['success']: - return True, render_to_string("open_ended_error.html", {'errors' : response_items['feedback']['errors']}) + return True, render_to_string("open_ended_error.html", {'errors' : response_items['feedback']}) + problem_areas=response_items['feedback'].count("") - feedback_item_start='
' - feedback_item_end='
' - feedback_long="" - for k,v in response_items['feedback']: - feedback_long+=feedback_item_start.format(feedback_key=k) - feedback_long+=v - feedback_long+=feedback_item_end + feedback=response_items['feedback'] feedback_template=render_to_string("open_ended_feedback.html",{ 'grader_type' : response_items['grader_type'], 'score' : response_items['score'], - 'feedback_long' : feedback_long, + 'feedback' : feedback, + 'problem_areas' : problem_areas, }) return True, feedback_template @@ -2084,10 +2080,9 @@ class OpenEndedResponse(LoncapaResponse): log.error("External grader message should be a JSON-serialized dict." " Received score_result = %s" % score_result) return fail - for tag in ['score','feedback', 'grader_type', 'success', 'errors']: + for tag in ['score', 'feedback', 'grader_type', 'success', 'errors']: if tag not in score_result: - log.error("External grader message is missing one or more required" - " tags: 'score', 'feedback") + log.error("External grader message is missing required tag: {0}".format(tag)) return fail # Next, we need to check that the contents of the external grader message diff --git a/lms/templates/open_ended_feedback.html b/lms/templates/open_ended_feedback.html index 39de3ad1c3..91efc20958 100644 --- a/lms/templates/open_ended_feedback.html +++ b/lms/templates/open_ended_feedback.html @@ -10,7 +10,7 @@
- ${feedback_long} + ${feedback}
\ No newline at end of file From 2a0c9d0c705091403661c74db79476964c7d2b47 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 12:48:13 -0500 Subject: [PATCH 056/234] Handle templating within lms --- common/lib/capa/capa/responsetypes.py | 11 +++++------ lms/templates/open_ended_error.html | 2 +- lms/templates/open_ended_feedback.html | 10 +++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 42f3091e6f..7eff89fbb4 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2035,13 +2035,12 @@ class OpenEndedResponse(LoncapaResponse): Input: Dictionary called feedback. Must contain keys seen below. Output: - Return success/fail, error message or feedback template + Return error message or feedback template """ if not response_items['success']: - return True, render_to_string("open_ended_error.html", {'errors' : response_items['feedback']}) + return render_to_string("open_ended_error.html", {'errors' : response_items['feedback']}) - problem_areas=response_items['feedback'].count("") feedback=response_items['feedback'] @@ -2049,10 +2048,9 @@ class OpenEndedResponse(LoncapaResponse): 'grader_type' : response_items['grader_type'], 'score' : response_items['score'], 'feedback' : feedback, - 'problem_areas' : problem_areas, }) - return True, feedback_template + return feedback_template def _parse_score_msg(self, score_msg): @@ -2080,7 +2078,7 @@ class OpenEndedResponse(LoncapaResponse): log.error("External grader message should be a JSON-serialized dict." " Received score_result = %s" % score_result) return fail - for tag in ['score', 'feedback', 'grader_type', 'success', 'errors']: + for tag in ['score', 'feedback', 'grader_type', 'success']: if tag not in score_result: log.error("External grader message is missing required tag: {0}".format(tag)) return fail @@ -2098,6 +2096,7 @@ class OpenEndedResponse(LoncapaResponse): if score_ratio>=.66: correct=True + log.debug(feedback) try: etree.fromstring(feedback) except etree.XMLSyntaxError as err: diff --git a/lms/templates/open_ended_error.html b/lms/templates/open_ended_error.html index c91beb88b0..fb3698ccd3 100644 --- a/lms/templates/open_ended_error.html +++ b/lms/templates/open_ended_error.html @@ -6,7 +6,7 @@
- {errors} + {{errors}}
\ No newline at end of file diff --git a/lms/templates/open_ended_feedback.html b/lms/templates/open_ended_feedback.html index 91efc20958..1e59652d33 100644 --- a/lms/templates/open_ended_feedback.html +++ b/lms/templates/open_ended_feedback.html @@ -2,15 +2,15 @@
Feedback
-

Score: ${score}

- % if grader_type=="ML" -

Number of potential problem areas identified: ${problem_areas}

- % endif +

Score: {{score}}

+ {% if grader_type == "ML" %} +

Check below for full feedback:

+ {% endif %}
- ${feedback} + {% autoescape off %} {{feedback |safe }} {% endautoescape %}
\ No newline at end of file From 9fb6c6f9891006f5372c3bd46e83add9b67f0c09 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 13:18:32 -0500 Subject: [PATCH 057/234] Debug template issues --- lms/templates/open_ended_feedback.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/open_ended_feedback.html b/lms/templates/open_ended_feedback.html index 1e59652d33..2dc8f555f6 100644 --- a/lms/templates/open_ended_feedback.html +++ b/lms/templates/open_ended_feedback.html @@ -10,7 +10,7 @@
- {% autoescape off %} {{feedback |safe }} {% endautoescape %} + {% autoescape off %} {{feedback}} {% endautoescape %}
\ No newline at end of file From 2621d07ced5904b0c52882c5d0d7af0329c7c9ca Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 13:48:17 -0500 Subject: [PATCH 058/234] Remove unneeded debug statements --- common/lib/capa/capa/responsetypes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 7eff89fbb4..aec90627f2 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1966,8 +1966,6 @@ class OpenEndedResponse(LoncapaResponse): 'max_score' : self.max_score }) - log.debug(xheader) - log.debug(contents) # Submit request. When successful, 'msg' is the prior length of the queue (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) From 32d8a6e92275dbbee7bbda26625b4beb46896a4a Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 30 Nov 2012 14:11:11 -0500 Subject: [PATCH 059/234] Pass through course_id, location to openendedresponse - set default queue in response type--don't use the default per-course one --- common/lib/capa/capa/responsetypes.py | 27 +++++++++++++--------- common/lib/xmodule/xmodule/capa_module.py | 5 ++++ common/lib/xmodule/xmodule/x_module.py | 6 ++++- lms/djangoapps/courseware/module_render.py | 1 + 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index ba9f03549e..6346b08a42 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1730,9 +1730,9 @@ class ImageResponse(LoncapaResponse): Regions is list of lists [region1, region2, region3, ...] where regionN is disordered list of points: [[1,1], [100,100], [50,50], [20, 70]]. - + If there is only one region in the list, simpler notation can be used: - regions="[[10,10], [30,30], [10, 30], [30, 10]]" (without explicitly + regions="[[10,10], [30,30], [10, 30], [30, 10]]" (without explicitly setting outer list) Returns: @@ -1817,19 +1817,24 @@ class ImageResponse(LoncapaResponse): class OpenEndedResponse(LoncapaResponse): """ - Grade student open ended responses using an external queueing server, called 'xqueue' + Grade student open ended responses using an external grading system, + accessed through the xqueue system. + + Expects 'xqueue' dict in ModuleSystem with the following keys that are + needed by OpenEndedResponse: - Expects 'xqueue' dict in ModuleSystem with the following keys that are needed by OpenEndedResponse: system.xqueue = { 'interface': XqueueInterface object, 'callback_url': Per-StudentModule callback URL where results are posted (string), - 'default_queuename': Default queuename to submit request (string) } External requests are only submitted for student submission grading (i.e. and not for getting reference answers) + + By default, uses the OpenEndedResponse.DEFAULT_QUEUE queue. """ + DEFAULT_QUEUE = 'open-ended' response_tag = 'openendedresponse' allowed_inputfields = ['openendedinput'] max_inputfields = 1 @@ -1841,7 +1846,7 @@ class OpenEndedResponse(LoncapaResponse): xml = self.xml # TODO: XML can override external resource (grader/queue) URL self.url = xml.get('url', None) - self.queue_name = xml.get('queuename', self.system.xqueue['default_queuename']) + self.queue_name = xml.get('queuename', self.DEFAULT_QUEUE) #Look for tag named openendedparam that encapsulates all grader settings oeparam = self.xml.find('openendedparam') @@ -1883,11 +1888,12 @@ class OpenEndedResponse(LoncapaResponse): #Update grader payload with student id. If grader payload not json, error. try: grader_payload=json.loads(grader_payload) - location=self.system.ajax_url.split("://")[1] - org,course,type,name=location.split("/") + # NOTE: self.system.location is valid because the capa_module + # __init__ adds it (easiest way to get problem location into + # response types) grader_payload.update({ - 'location' : location, - 'course_id' : "{0}/{1}".format(org,course), + 'location' : self.system.location, + 'course_id' : self.system.course_id, 'prompt' : prompt_string, 'rubric' : rubric_string, }) @@ -1997,7 +2003,6 @@ class OpenEndedResponse(LoncapaResponse): msg='Invalid grader reply. Please contact the course staff.') return oldcmap - correctness = 'correct' if correct else 'incorrect' # TODO: Find out how this is used elsewhere, if any diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 47d5d5c423..ead138a225 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -146,6 +146,11 @@ class CapaModule(XModule): else: self.seed = None + # Need the problem location in openendedresponse to send out. Adding + # it to the system here seems like the least clunky way to get it + # there. + self.system.set('location', self.location) + try: # TODO (vshnayder): move as much as possible of this work and error # checking to descriptor load time diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 6f3fb73356..19a592191e 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -809,7 +809,8 @@ class ModuleSystem(object): debug=False, xqueue=None, node_path="", - anonymous_student_id=''): + anonymous_student_id='', + course_id=None): ''' Create a closure around the system environment. @@ -844,6 +845,8 @@ class ModuleSystem(object): ajax results. anonymous_student_id - Used for tracking modules with student id + + course_id - the course_id containing this module ''' self.ajax_url = ajax_url self.xqueue = xqueue @@ -856,6 +859,7 @@ class ModuleSystem(object): self.replace_urls = replace_urls self.node_path = node_path self.anonymous_student_id = anonymous_student_id + self.course_id = course_id self.user_is_staff = user is not None and user.is_staff def get(self, attr): diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index eb7b41b1e9..bd919eeb15 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -225,6 +225,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi replace_urls=replace_urls, node_path=settings.NODE_PATH, anonymous_student_id=unique_id_for_user(user), + course_id=course_id, ) # pass position specified in URL to module through ModuleSystem system.set('position', position) From a20a6c8fb59ec755521857458dc2096cae9d911e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 14:11:43 -0500 Subject: [PATCH 060/234] Do all html rendering and generation in lms --- common/lib/capa/capa/responsetypes.py | 47 ++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index aec90627f2..ec3f4763d5 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2028,6 +2028,46 @@ class OpenEndedResponse(LoncapaResponse): def get_initial_display(self): return {self.answer_id: self.initial_display} + def _convert_longform_feedback_to_html(response_items): + """ + Take in a dictionary, and return html formatted strings appropriate for sending via xqueue. + Input: + Dictionary with keys success, feedback, and errors + Output: + String + """ + + feedback_item_start='
' + feedback_item_end='
' + + for tag in ['status', 'feedback']: + if tag not in response_items: + feedback_long=feedback_item_start.format(feedback_key="errors") + "Error getting feedback." + feedback_item_end + + feedback_items=response_items['feedback'] + try: + feedback_items=json.loads(feedback_items) + except: + pass + + success=response_items['success'] + + if success: + feedback_long="" + for k,v in feedback_items.items(): + feedback_long+=feedback_item_start.format(feedback_key=k) + feedback_long+=str(v) + feedback_long+=feedback_item_end + + if len(feedback_items)==0: + feedback_long=feedback_item_start.format(feedback_key="feedback") + "No feedback available." + feedback_item_end + + else: + feedback_long=feedback_item_start.format(feedback_key="errors") + response_items['feedback'] + feedback_item_end + + return feedback_long + + def _format_feedback(self, response_items): """ Input: @@ -2036,11 +2076,10 @@ class OpenEndedResponse(LoncapaResponse): Return error message or feedback template """ + feedback=self._convert_longform_feedback_to_html(response_items) + if not response_items['success']: - return render_to_string("open_ended_error.html", {'errors' : response_items['feedback']}) - - - feedback=response_items['feedback'] + return render_to_string("open_ended_error.html", {'errors' : feedback}) feedback_template=render_to_string("open_ended_feedback.html",{ 'grader_type' : response_items['grader_type'], From 6e75584d06f7954cf21abf4f9ef7edf9f6d497ee Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 14:14:29 -0500 Subject: [PATCH 061/234] Convert templates to mako --- common/lib/capa/capa/responsetypes.py | 5 ++--- lms/templates/open_ended_error.html | 2 +- lms/templates/open_ended_feedback.html | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index ec3f4763d5..56be2ae584 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -24,7 +24,6 @@ import os import subprocess import xml.sax.saxutils as saxutils from shapely.geometry import Point, MultiPoint -from django.template.loader import render_to_string # specific library imports from calc import evaluator, UndefinedVariable @@ -2079,9 +2078,9 @@ class OpenEndedResponse(LoncapaResponse): feedback=self._convert_longform_feedback_to_html(response_items) if not response_items['success']: - return render_to_string("open_ended_error.html", {'errors' : feedback}) + return self.system.render_template("open_ended_error.html", {'errors' : feedback}) - feedback_template=render_to_string("open_ended_feedback.html",{ + feedback_template=self.system.render_template("open_ended_feedback.html",{ 'grader_type' : response_items['grader_type'], 'score' : response_items['score'], 'feedback' : feedback, diff --git a/lms/templates/open_ended_error.html b/lms/templates/open_ended_error.html index fb3698ccd3..58a90f86ef 100644 --- a/lms/templates/open_ended_error.html +++ b/lms/templates/open_ended_error.html @@ -6,7 +6,7 @@
- {{errors}} + ${errors}
\ No newline at end of file diff --git a/lms/templates/open_ended_feedback.html b/lms/templates/open_ended_feedback.html index 2dc8f555f6..cb90006456 100644 --- a/lms/templates/open_ended_feedback.html +++ b/lms/templates/open_ended_feedback.html @@ -2,15 +2,15 @@
Feedback
-

Score: {{score}}

- {% if grader_type == "ML" %} +

Score: ${score}

+ % if grader_type == "ML":

Check below for full feedback:

- {% endif %} + % endif
- {% autoescape off %} {{feedback}} {% endautoescape %} + ${ feedback | n}
\ No newline at end of file From 3fa1cfd010b5e2f5573d074da92c2b5de60449d2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 14:16:30 -0500 Subject: [PATCH 062/234] Minor bugfix --- common/lib/capa/capa/responsetypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 56be2ae584..451a863573 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2027,7 +2027,7 @@ class OpenEndedResponse(LoncapaResponse): def get_initial_display(self): return {self.answer_id: self.initial_display} - def _convert_longform_feedback_to_html(response_items): + def _convert_longform_feedback_to_html(self,response_items): """ Take in a dictionary, and return html formatted strings appropriate for sending via xqueue. Input: From bcd30223208177f6cfdd3ae2b41f6ef75e3a39fc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 15:26:52 -0500 Subject: [PATCH 063/234] Add in import for reverse in staff grading (wouldn't work without it) --- common/lib/capa/capa/responsetypes.py | 2 -- lms/djangoapps/instructor/views.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 451a863573..f387a20bb5 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1921,8 +1921,6 @@ class OpenEndedResponse(LoncapaResponse): else: self.max_score = 1 - log.debug(self.max_score) - def get_score(self, student_answers): try: diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index 389a64721a..79cf0caaf3 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -12,6 +12,7 @@ from django.http import HttpResponse from django_future.csrf import ensure_csrf_cookie from django.views.decorators.cache import cache_control from mitxmako.shortcuts import render_to_response +from django.core.urlresolvers import reverse from courseware import grades from courseware.access import has_access, get_access_group_name From 4e4bd4aba275873de94297a54f04a77be53591f0 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 30 Nov 2012 15:52:11 -0500 Subject: [PATCH 064/234] Altered staff grading to display message about ml grading performance. --- .../coffee/src/staff_grading/staff_grading.coffee | 13 ++++++++++--- lms/templates/instructor/staff_grading.html | 3 +++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 5edc5e5c35..c953b38c2e 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -26,6 +26,7 @@ class StaffGradingBackend rubric: 'A rubric! ' + @mock_cnt submission_id: @mock_cnt max_score: 2 + @mock_cnt % 3 + ml_error_info : 'ML error info!' + @mock_cnt else if cmd == 'save_grade' console.log("eval: #{data.score} pts, Feedback: #{data.feedback}") @@ -71,7 +72,8 @@ class StaffGrading @rubric_wrapper = $('.rubric-wrapper') @feedback_area = $('.feedback-area') @score_selection_container = $('.score-selection-container') - @submit_button = $('.submit-button') + @submit_button = $('.submit-button') + @ml_error_info_container = $('.ml-error-info-container') # model state @state = state_no_data @@ -81,6 +83,7 @@ class StaffGrading @error_msg = '' @message = '' @max_score = 0 + @ml_error_info= '' @score = null @@ -127,7 +130,7 @@ class StaffGrading if response.success if response.submission - @data_loaded(response.submission, response.rubric, response.submission_id, response.max_score) + @data_loaded(response.submission, response.rubric, response.submission_id, response.max_score, response.ml_error_info) else @no_more(response.message) else @@ -150,18 +153,20 @@ class StaffGrading @error_msg = msg @state = state_error - data_loaded: (submission, rubric, submission_id, max_score) -> + data_loaded: (submission, rubric, submission_id, max_score, ml_error_info) -> @submission = submission @rubric = rubric @submission_id = submission_id @feedback_area.val('') @max_score = max_score @score = null + @ml_error_info=ml_error_info @state = state_grading no_more: (message) -> @submission = null @rubric = null + @ml_error_info = null @submission_id = null @message = message @score = null @@ -183,6 +188,7 @@ class StaffGrading @set_button_text('Try loading again') else if @state == state_grading + @ml_error_info_container.html(@ml_error_info) @submission_container.html(@submission) @rubric_container.html(@rubric) show_grading_elements = true @@ -206,6 +212,7 @@ class StaffGrading @submit_button.toggle(show_submit_button) @submission_wrapper.toggle(show_grading_elements) @rubric_wrapper.toggle(show_grading_elements) + @ml_error_info_container.toggle(show_grading_elements) submit: (event) => diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index dbf2e97dde..ad704ae5a4 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -25,6 +25,9 @@
+
+
+

Submission

From 63c1a8ea10b3c315cdbe16434afd19fc3450eb55 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 3 Dec 2012 12:43:43 -0500 Subject: [PATCH 065/234] whitespace --- common/lib/capa/capa/responsetypes.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 508845f773..bda1f5f1b1 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1844,32 +1844,35 @@ class OpenEndedResponse(LoncapaResponse): Configure OpenEndedResponse from XML. ''' xml = self.xml - # TODO: XML can override external resource (grader/queue) URL self.url = xml.get('url', None) self.queue_name = xml.get('queuename', self.DEFAULT_QUEUE) - #Look for tag named openendedparam that encapsulates all grader settings + # The openendedparam tag encapsulates all grader settings oeparam = self.xml.find('openendedparam') - prompt=self.xml.find('prompt') - rubric=self.xml.find('openendedrubric') - self._parse_openendedresponse_xml(oeparam,prompt,rubric) + prompt = self.xml.find('prompt') + rubric = self.xml.find('openendedrubric') + self._parse(oeparam,prompt,rubric) - def stringify_children(self,node,strip_tags=True): + def stringify_children(self, node, strip_tags=True): """ - Modify code from stringify_children in xmodule. Didn't import directly in order to avoid capa depending - on xmodule (seems to be avoided in code) + Modify code from stringify_children in xmodule. Didn't import directly + in order to avoid capa depending on xmodule (seems to be avoided in + code) """ parts=[node.text] [parts.append((etree.tostring(p, with_tail=True))) for p in node.getchildren()] node_string=' '.join(parts) - #Strip html tags from prompt. This may need to be removed in order to display prompt to instructors properly. + # Strip html tags from result. This may need to be removed in order to + # display prompt to instructors properly. + # TODO: what breaks if this is removed? The ML code can strip tags + # as part of its input filtering. if strip_tags: node_string=re.sub('<[^<]+?>', '', node_string) return node_string - def _parse_openendedresponse_xml(self,oeparam,prompt,rubric): + def _parse(self, oeparam, prompt, rubric): ''' Parse OpenEndedResponse XML: self.initial_display @@ -1879,8 +1882,8 @@ class OpenEndedResponse(LoncapaResponse): self.answer - What to display when show answer is clicked ''' # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload - prompt_string=self.stringify_children(prompt) - rubric_string=self.stringify_children(rubric) + prompt_string = self.stringify_children(prompt) + rubric_string = self.stringify_children(rubric) grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' From 22ce306f6443966a72f872708b119eae8e8d5c90 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 3 Dec 2012 12:49:58 -0500 Subject: [PATCH 066/234] whitespace and comments --- common/lib/capa/capa/responsetypes.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index bda1f5f1b1..98421ce0b3 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1851,7 +1851,7 @@ class OpenEndedResponse(LoncapaResponse): oeparam = self.xml.find('openendedparam') prompt = self.xml.find('prompt') rubric = self.xml.find('openendedrubric') - self._parse(oeparam,prompt,rubric) + self._parse(oeparam, prompt, rubric) def stringify_children(self, node, strip_tags=True): """ @@ -1890,7 +1890,7 @@ class OpenEndedResponse(LoncapaResponse): #Update grader payload with student id. If grader payload not json, error. try: - grader_payload=json.loads(grader_payload) + grader_payload = json.loads(grader_payload) # NOTE: self.system.location is valid because the capa_module # __init__ adds it (easiest way to get problem location into # response types) @@ -1900,7 +1900,7 @@ class OpenEndedResponse(LoncapaResponse): 'prompt' : prompt_string, 'rubric' : rubric_string, }) - grader_payload=json.dumps(grader_payload) + grader_payload = json.dumps(grader_payload) except Exception as err: log.error("Grader payload is not a json object!") @@ -1924,9 +1924,9 @@ class OpenEndedResponse(LoncapaResponse): top_score = oeparam.find('max_score') if top_score is not None: try: - self.max_score= int(top_score.text) + self.max_score = int(top_score.text) except: - self.top_score=1 + self.max_score = 1 else: self.max_score = 1 @@ -1992,7 +1992,8 @@ class OpenEndedResponse(LoncapaResponse): # 2) Frontend: correctness='incomplete' eventually trickles down # through inputtypes.textbox and .filesubmission to inform the # browser to poll the LMS - cmap.set(self.answer_id, queuestate=queuestate, correctness='incomplete', msg=msg) + cmap.set(self.answer_id, queuestate=queuestate, + correctness='incomplete', msg=msg) return cmap @@ -2001,7 +2002,7 @@ class OpenEndedResponse(LoncapaResponse): (valid_score_msg, correct, points, msg) = self._parse_score_msg(score_msg) if not valid_score_msg: oldcmap.set(self.answer_id, - msg='Invalid grader reply. Please contact the course staff.') + msg = 'Invalid grader reply. Please contact the course staff.') return oldcmap correctness = 'correct' if correct else 'incorrect' @@ -2132,11 +2133,11 @@ class OpenEndedResponse(LoncapaResponse): feedback = self._format_feedback(score_result) - score_ratio=int(score_result['score'])/self.max_score - correct=False - if score_ratio>=.66: - correct=True + # HACK: for now, just assume it's correct if you got more than 2/3. + # Also assumes that score_result['score'] is an integer. + score_ratio = int(score_result['score']) / self.max_score + correct = (score_ratio >= 0.66) log.debug(feedback) try: From a44e8887a87394a13e833e484b9ed3325b39d1fa Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 3 Dec 2012 13:22:50 -0500 Subject: [PATCH 067/234] add docs on staff grading tab type - also updated some out-of-date docs --- doc/xml-format.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/xml-format.md b/doc/xml-format.md index 46082b90ad..d7c5027a79 100644 --- a/doc/xml-format.md +++ b/doc/xml-format.md @@ -418,6 +418,10 @@ If you want to customize the courseware tabs displayed for your course, specify * "external_link". Parameters "name", "link". * "textbooks". No parameters--generates tab names from book titles. * "progress". Parameter "name". +* "static_tab". Parameters "name", 'url_slug'--will look for tab contents in + 'tabs/{course_url_name}/{tab url_slug}.html' +* "staff_grading". No parameters. If specified, displays the staff grading tab for instructors. + # Tips for content developers @@ -429,9 +433,7 @@ before the week 1 material to make it easy to find in the file. * Come up with a consistent pattern for url_names, so that it's easy to know where to look for any piece of content. It will also help to come up with a standard way of splitting your content files. As a point of departure, we suggest splitting chapters, sequences, html, and problems into separate files. -* A heads up: our content management system will allow you to develop content through a web browser, but will be backed by this same xml at first. Once that happens, every element will be in its own file to make access and updates faster. - -* Prefer the most "semantic" name for containers: e.g., use problemset rather than vertical for a problem set. That way, if we decide to display problem sets differently, we don't have to change the xml. +* Prefer the most "semantic" name for containers: e.g., use problemset rather than sequential for a problem set. That way, if we decide to display problem sets differently, we don't have to change the xml. # Other file locations (info and about) From 65c56edb5cabea51f65b121e3bcc0a28122ebe5b Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 3 Dec 2012 15:04:34 -0500 Subject: [PATCH 068/234] Better error logging when login into queue fails --- common/lib/capa/capa/xqueue_interface.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py index f145cad23c..798867955b 100644 --- a/common/lib/capa/capa/xqueue_interface.py +++ b/common/lib/capa/capa/xqueue_interface.py @@ -81,7 +81,11 @@ class XQueueInterface(object): # Log in, then try again if error and (msg == 'login_required'): - self._login() + (error, content) = self._login() + if error != 0: + # when the login fails + log.debug("Failed to login to queue: %s", content) + return (error, content) if files_to_upload is not None: # Need to rewind file pointers for f in files_to_upload: From 9b1cad90b5ed56d7745ddefbc4885d8ddd34d0a7 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 3 Dec 2012 16:52:29 -0500 Subject: [PATCH 069/234] Fix location that gets used for open-ended responses --- common/lib/xmodule/xmodule/capa_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index ead138a225..4c10a1703a 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -149,7 +149,7 @@ class CapaModule(XModule): # Need the problem location in openendedresponse to send out. Adding # it to the system here seems like the least clunky way to get it # there. - self.system.set('location', self.location) + self.system.set('location', self.location.url()) try: # TODO (vshnayder): move as much as possible of this work and error From d4a3e4d0449db3b3b40fb7a6d8b366fcc56ebbf9 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Mon, 3 Dec 2012 14:08:23 -0500 Subject: [PATCH 070/234] fix logger name --- lms/djangoapps/instructor/staff_grading_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index c070bd6835..61654997bd 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -15,7 +15,7 @@ from courseware.access import has_access from util.json_request import expect_json from xmodule.course_module import CourseDescriptor -log = logging.getLogger("mitx.courseware") +log = logging.getLogger(__name__) class GradingServiceError(Exception): @@ -54,7 +54,6 @@ class StaffGradingService(object): self.get_next_url = self.url + '/get_next_submission/' self.save_grade_url = self.url + '/save_grade/' - # TODO: add auth self.session = requests.session() @@ -114,6 +113,7 @@ class StaffGradingService(object): return r.text + def save_grade(self, course_id, grader_id, submission_id, score, feedback): """ Save a score and feedback for a submission. From c57b9f324034a6fc620e60f4dd30ee1b479c2f7e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 4 Dec 2012 14:43:40 -0500 Subject: [PATCH 071/234] merge prompt and ml accuracy display --- .../src/staff_grading/staff_grading.coffee | 19 ++++++++++++++++--- lms/templates/instructor/staff_grading.html | 6 ++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index c953b38c2e..161fb33b7c 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -66,10 +66,16 @@ class StaffGrading # all the jquery selectors @error_container = $('.error-container') @message_container = $('.message-container') + + @prompt_container = $('.prompt-container') + @prompt_wrapper = $('.prompt-wrapper') + @submission_container = $('.submission-container') - @rubric_container = $('.rubric-container') @submission_wrapper = $('.submission-wrapper') + + @rubric_container = $('.rubric-container') @rubric_wrapper = $('.rubric-wrapper') + @feedback_area = $('.feedback-area') @score_selection_container = $('.score-selection-container') @submit_button = $('.submit-button') @@ -78,6 +84,7 @@ class StaffGrading # model state @state = state_no_data @submission_id = null + @prompt = '' @submission = '' @rubric = '' @error_msg = '' @@ -130,7 +137,7 @@ class StaffGrading if response.success if response.submission - @data_loaded(response.submission, response.rubric, response.submission_id, response.max_score, response.ml_error_info) + @data_loaded(response.prompt, response.submission, response.rubric, response.submission_id, response.max_score, response.ml_error_info) else @no_more(response.message) else @@ -153,7 +160,8 @@ class StaffGrading @error_msg = msg @state = state_error - data_loaded: (submission, rubric, submission_id, max_score, ml_error_info) -> + data_loaded: (prompt, submission, rubric, submission_id, max_score, ml_error_info) -> + @prompt = prompt @submission = submission @rubric = rubric @submission_id = submission_id @@ -162,8 +170,11 @@ class StaffGrading @score = null @ml_error_info=ml_error_info @state = state_grading + if not @max_score? + @error("No max score specified for submission.") no_more: (message) -> + @prompt = null @submission = null @rubric = null @ml_error_info = null @@ -189,6 +200,7 @@ class StaffGrading else if @state == state_grading @ml_error_info_container.html(@ml_error_info) + @prompt_container.html(@prompt) @submission_container.html(@submission) @rubric_container.html(@rubric) show_grading_elements = true @@ -210,6 +222,7 @@ class StaffGrading @error('System got into invalid state ' + @state) @submit_button.toggle(show_submit_button) + @prompt_wrapper.toggle(show_grading_elements) @submission_wrapper.toggle(show_grading_elements) @rubric_wrapper.toggle(show_grading_elements) @ml_error_info_container.toggle(show_grading_elements) diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index ad704ae5a4..a44ef68831 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -28,6 +28,12 @@
+
+

Question prompt

+
+
+
+

Submission

From 1d87dab3a77468c7221a880ec631610e32e748c4 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 4 Dec 2012 17:45:51 -0500 Subject: [PATCH 072/234] remove queueing spinner and polling logic, change message - no polling, since instructors may take a long time to grade --- common/lib/capa/capa/inputtypes.py | 7 ++++--- .../lib/capa/capa/templates/openendedinput.html | 5 +---- common/lib/xmodule/xmodule/css/capa/display.scss | 15 +++++---------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 70fe5dd6c8..73056bc09e 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -746,8 +746,9 @@ class OpenEndedInput(InputTypeBase): tags = ['openendedinput'] # pulled out for testing - submitted_msg = ("Submitted. As soon as your submission is" - " graded, this message will be replaced with the grader's feedback.") + submitted_msg = ("Feedback not yet available. Reload to check again. " + "Once the problem is graded, this message will be " + "replaced with the grader's feedback") @classmethod def get_attributes(cls): @@ -781,4 +782,4 @@ class OpenEndedInput(InputTypeBase): registry.register(OpenEndedInput) -#----------------------------------------------------------------------------- \ No newline at end of file +#----------------------------------------------------------------------------- diff --git a/common/lib/capa/capa/templates/openendedinput.html b/common/lib/capa/capa/templates/openendedinput.html index 697bff8082..8cc19a2705 100644 --- a/common/lib/capa/capa/templates/openendedinput.html +++ b/common/lib/capa/capa/templates/openendedinput.html @@ -13,15 +13,12 @@ % elif status == 'incorrect': Incorrect % elif status == 'queued': - Queued - + Submitted for grading % endif % if hidden:
% endif - -

${status}

diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index 58ba7b00ed..bbde1063ed 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -121,16 +121,6 @@ section.problem { } } - &.processing { - p.status { - @include inline-block(); - background: url('../images/spinner.gif') center center no-repeat; - height: 20px; - width: 20px; - text-indent: -9999px; - } - } - &.correct, &.ui-icon-check { p.status { @include inline-block(); @@ -266,6 +256,11 @@ section.problem { margin: -7px 7px 0 0; } + .grading { + text-indent: 0px; + margin: 0px 7px 0 0; + } + p { line-height: 20px; text-transform: capitalize; From 40f34d19a3c2591c0f923effb14c45cf2a1564cb Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 4 Dec 2012 17:46:34 -0500 Subject: [PATCH 073/234] add initial mock of get_problems call --- .../src/staff_grading/staff_grading.coffee | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 161fb33b7c..bca577b0c6 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -18,6 +18,7 @@ class StaffGradingBackend mock: (cmd, data) -> # Return a mock response to cmd and data + # TODO: needs (optional?) arg for problem location if cmd == 'get_next' @mock_cnt++ response = @@ -26,12 +27,26 @@ class StaffGradingBackend rubric: 'A rubric! ' + @mock_cnt submission_id: @mock_cnt max_score: 2 + @mock_cnt % 3 - ml_error_info : 'ML error info!' + @mock_cnt + ml_error_info : 'ML accuracy info: ' + @mock_cnt else if cmd == 'save_grade' console.log("eval: #{data.score} pts, Feedback: #{data.feedback}") response = @mock('get_next', {}) + else if cmd == 'get_problems' + # this one didn't have a name in the LMS--lookup fail + p1 = {'location': 'i4x://MITx/3.091x/problem/open_ended_demo',\ + 'name': 'i4x://MITx/3.091x/problem/open_ended_demo',\ + 'num_graded': 10,\ + 'num_to_grade': 90} + + p2 = {'location': 'i4x://MITx/3.091x/problem/open_ended_demo2',\ + 'name': 'Open ended demo',\ + 'num_graded': 42,\ + 'num_to_grade': 63} + + response = + problems: [p1, p2] else response = success: false From 7390015799831d4b3d7d7b074993a91f3cd02848 Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Wed, 5 Dec 2012 14:43:35 +0200 Subject: [PATCH 074/234] parameters in display video --- .../xmodule/xmodule/js/src/video/display/video_player.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee index 8829e25dac..9e5288b8a2 100644 --- a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee +++ b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee @@ -36,6 +36,7 @@ class @VideoPlayer extends Subview @volumeControl = new VideoVolumeControl el: @$('.secondary-controls') @speedControl = new VideoSpeedControl el: @$('.secondary-controls'), speeds: @video.speeds, currentSpeed: @currentSpeed() @progressSlider = new VideoProgressSlider el: @$('.slider') + @debugger_1 @player = new YT.Player @video.id, playerVars: controls: 0 @@ -44,6 +45,8 @@ class @VideoPlayer extends Subview showinfo: 0 enablejsapi: 1 modestbranding: 1 + start: 10 + end: 20 videoId: @video.youtubeId() events: onReady: @onReady From 90076c39e6c46cf17d87c8d46c57786a5f32702b Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 5 Dec 2012 09:25:05 -0500 Subject: [PATCH 075/234] add in some new mock responses for our staff grading API --- .../coffee/src/staff_grading/staff_grading.coffee | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 161fb33b7c..be7837ec93 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -18,10 +18,15 @@ class StaffGradingBackend mock: (cmd, data) -> # Return a mock response to cmd and data + # should take a problem id as an argument if cmd == 'get_next' @mock_cnt++ response = success: true + problem_name: 'Problem 1' + num_left: 3 + num_total: 5 + prompt: 'This is a fake prompt' submission: 'submission! ' + @mock_cnt rubric: 'A rubric! ' + @mock_cnt submission_id: @mock_cnt @@ -32,6 +37,16 @@ class StaffGradingBackend console.log("eval: #{data.score} pts, Feedback: #{data.feedback}") response = @mock('get_next', {}) + # get_probblem_list + # sends in a course_id and a grader_id + # should get back a list of problem_ids, problem_names, num_left, num_total + else if cmd == 'get_problem_list' + response = + success: true + problem_list: [ + {problem_id: 1, problem_name: "Problem 1", num_left: 3, num_total: 5} + {problem_id: 2, problem_name: "Problem 2", num_left: 1, num_total: 5} + ] else response = success: false From 7887f556d9e224b39d183737afd80edbaa1999ff Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 5 Dec 2012 09:29:42 -0500 Subject: [PATCH 076/234] Add docs on using a queue server on aws from a local LMS --- doc/development.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/development.md b/doc/development.md index b4ac52d202..ebc56fbf1b 100644 --- a/doc/development.md +++ b/doc/development.md @@ -67,6 +67,15 @@ To run a single nose test: Very handy: if you uncomment the `--pdb` argument in `NOSE_ARGS` in `lms/envs/test.py`, it will drop you into pdb on error. This lets you go up and down the stack and see what the values of the variables are. Check out http://docs.python.org/library/pdb.html +## Testing using queue servers + +When testing problems that use a queue server on AWS (e.g. sandbox-xqueue.edx.org), you'll need to run your server on your public IP, like so. + +`django-admin.py runserver --settings=lms.envs.dev --pythonpath=. 0.0.0.0:8000` + +When you connect to the LMS, you need to use the public ip. Use `ifconfig` to figure out the numnber, and connect e.g. to `http://18.3.4.5:8000/` + + ## Content development If you change course content, while running the LMS in dev mode, it is unnecessary to restart to refresh the modulestore. From 5ebde2a93e1f72008c89c2971c1cd95d82e8e316 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 5 Dec 2012 09:42:30 -0500 Subject: [PATCH 077/234] fix comment --- lms/static/coffee/src/staff_grading/staff_grading.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 7e8f39acac..f4831e35c4 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -18,7 +18,7 @@ class StaffGradingBackend mock: (cmd, data) -> # Return a mock response to cmd and data - # TODO: needs (optional?) arg for problem location + # should take a location as an argument if cmd == 'get_next' @mock_cnt++ response = From 97016d6168ae16ea1632febe41d43f86b31fa32e Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Wed, 5 Dec 2012 17:18:35 +0200 Subject: [PATCH 078/234] video time frame support for xmodule --- .../xmodule/js/src/video/display.coffee | 2 ++ .../js/src/video/display/video_player.coffee | 24 ++++++++------ common/lib/xmodule/xmodule/video_module.py | 31 ++++++++++++++++--- lms/templates/video.html | 4 ++- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/video/display.coffee b/common/lib/xmodule/xmodule/js/src/video/display.coffee index 6587f05899..a170075b68 100644 --- a/common/lib/xmodule/xmodule/js/src/video/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/video/display.coffee @@ -2,6 +2,8 @@ class @Video constructor: (element) -> @el = $(element).find('.video') @id = @el.attr('id').replace(/video_/, '') + @start = @el.data('start') + @end = @el.data('end') @caption_data_dir = @el.data('caption-data-dir') @show_captions = @el.data('show-captions') == "true" window.player = null diff --git a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee index 9e5288b8a2..ec52d15874 100644 --- a/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee +++ b/common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee @@ -36,17 +36,21 @@ class @VideoPlayer extends Subview @volumeControl = new VideoVolumeControl el: @$('.secondary-controls') @speedControl = new VideoSpeedControl el: @$('.secondary-controls'), speeds: @video.speeds, currentSpeed: @currentSpeed() @progressSlider = new VideoProgressSlider el: @$('.slider') - @debugger_1 + @playerVars = + controls: 0 + wmode: 'transparent' + rel: 0 + showinfo: 0 + enablejsapi: 1 + modestbranding: 1 + if @video.start + @playerVars.start = @video.start + if @video.end + # work in AS3, not HMLT5. but iframe use AS3 + @playerVars.end = @video.end + @player = new YT.Player @video.id, - playerVars: - controls: 0 - wmode: 'transparent' - rel: 0 - showinfo: 0 - enablejsapi: 1 - modestbranding: 1 - start: 10 - end: 20 + playerVars: @playerVars videoId: @video.youtubeId() events: onReady: @onReady diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py index 9a22950ca8..801e70fd06 100644 --- a/common/lib/xmodule/xmodule/video_module.py +++ b/common/lib/xmodule/xmodule/video_module.py @@ -7,6 +7,9 @@ from pkg_resources import resource_string, resource_listdir from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor +import datetime +import time + log = logging.getLogger(__name__) @@ -33,6 +36,7 @@ class VideoModule(XModule): self.show_captions = xmltree.get('show_captions', 'true') self.source = self._get_source(xmltree) self.track = self._get_track(xmltree) + self.start_time, self.end_time = self._get_timeframe(xmltree) if instance_state is not None: state = json.loads(instance_state) @@ -42,11 +46,11 @@ class VideoModule(XModule): def _get_source(self, xmltree): # find the first valid source return self._get_first_external(xmltree, 'source') - + def _get_track(self, xmltree): # find the first valid track return self._get_first_external(xmltree, 'track') - + def _get_first_external(self, xmltree, tag): """ Will return the first valid element @@ -61,6 +65,23 @@ class VideoModule(XModule): break return result + def _get_timeframe(self, xmltree): + """ Converts 'from' and 'to' parameters in video tag to seconds. + If there are no parameters, returns empty string. """ + + def parse_time(s): + """Converts s in '12:34:45' format to seconds. If s is + None, returns empty string""" + if s is None: + return '' + else: + x = time.strptime(s, '%H:%M:%S') + return datetime.timedelta(hours=x.tm_hour, + minutes=x.tm_min, + seconds=x.tm_sec).total_seconds() + + return parse_time(xmltree.get('from')), parse_time(xmltree.get('to')) + def handle_ajax(self, dispatch, get): ''' Handle ajax calls to this video. @@ -98,11 +119,13 @@ class VideoModule(XModule): 'id': self.location.html_id(), 'position': self.position, 'source': self.source, - 'track' : self.track, + 'track': self.track, 'display_name': self.display_name, # TODO (cpennington): This won't work when we move to data that isn't on the filesystem 'data_dir': self.metadata['data_dir'], - 'show_captions': self.show_captions + 'show_captions': self.show_captions, + 'start': self.start_time, + 'end': self.end_time }) diff --git a/lms/templates/video.html b/lms/templates/video.html index 5c041d5c70..6e45a91c31 100644 --- a/lms/templates/video.html +++ b/lms/templates/video.html @@ -2,7 +2,9 @@

${display_name}

% endif -
+
From 3795b3a7851b922cfe2a5ca81ef8e64e27a2e039 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 5 Dec 2012 10:56:12 -0500 Subject: [PATCH 079/234] Address Dave's comments - factor out some common code - improve variable names --- common/lib/capa/capa/responsetypes.py | 53 +++++++-------------------- common/lib/capa/capa/util.py | 22 +++++++++++ 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 98421ce0b3..cf804f381d 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1140,7 +1140,7 @@ class CodeResponse(LoncapaResponse): else: self._parse_coderesponse_xml(codeparam) - def _parse_coderesponse_xml(self,codeparam): + def _parse_coderesponse_xml(self, codeparam): ''' Parse the new CodeResponse XML format. When successful, sets: self.initial_display @@ -1152,17 +1152,9 @@ class CodeResponse(LoncapaResponse): grader_payload = grader_payload.text if grader_payload is not None else '' self.payload = {'grader_payload': grader_payload} - answer_display = codeparam.find('answer_display') - if answer_display is not None: - self.answer = answer_display.text - else: - self.answer = 'No answer provided.' - - initial_display = codeparam.find('initial_display') - if initial_display is not None: - self.initial_display = initial_display.text - else: - self.initial_display = '' + self.initial_display = find_with_default(codeparam, 'initial_display', '') + self.answer = find_with_default(codeparam, 'answer_display', + 'No answer provided.') def _parse_externalresponse_xml(self): ''' @@ -1890,44 +1882,27 @@ class OpenEndedResponse(LoncapaResponse): #Update grader payload with student id. If grader payload not json, error. try: - grader_payload = json.loads(grader_payload) + parsed_grader_payload = json.loads(grader_payload) # NOTE: self.system.location is valid because the capa_module # __init__ adds it (easiest way to get problem location into # response types) - grader_payload.update({ + parsed_grader_payload.update({ 'location' : self.system.location, 'course_id' : self.system.course_id, 'prompt' : prompt_string, 'rubric' : rubric_string, }) - grader_payload = json.dumps(grader_payload) + updated_grader_payload = json.dumps(parsed_grader_payload) except Exception as err: - log.error("Grader payload is not a json object!") + log.exception("Grader payload %r is not a json object!", grader_payload) - self.payload = {'grader_payload': grader_payload} + self.payload = {'grader_payload': updated_grader_payload} - #Parse initial display - initial_display = oeparam.find('initial_display') - if initial_display is not None: - self.initial_display = initial_display.text - else: - self.initial_display = '' - - #Parse answer display - answer_display = oeparam.find('answer_display') - if answer_display is not None: - self.answer= answer_display.text - else: - self.answer = "No answer given." - - #Parse max_score - top_score = oeparam.find('max_score') - if top_score is not None: - try: - self.max_score = int(top_score.text) - except: - self.max_score = 1 - else: + self.initial_display = find_with_default(oeparam, 'initial_display', '') + self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.') + try: + self.max_score = int(find_with_default(oeparam, 'max_score', 1)) + except ValueError: self.max_score = 1 def get_score(self, student_answers): diff --git a/common/lib/capa/capa/util.py b/common/lib/capa/capa/util.py index 10e984611b..0df58c216f 100644 --- a/common/lib/capa/capa/util.py +++ b/common/lib/capa/capa/util.py @@ -65,3 +65,25 @@ def is_file(file_to_test): Duck typing to check if 'file_to_test' is a File object ''' return all(hasattr(file_to_test, method) for method in ['read', 'name']) + + +def find_with_default(node, path, default): + """ + Look for a child of node using , and return its text if found. + Otherwise returns default. + + Arguments: + node: lxml node + path: xpath search expression + default: value to return if nothing found + + Returns: + node.find(path).text if the find succeeds, default otherwise. + + """ + v = node.find(path) + if v is not None: + return v.text + else: + return default + From e813dc35147a739064f4b9d18cfc3796f539ec59 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 5 Dec 2012 12:08:26 -0500 Subject: [PATCH 080/234] Address format issue and exception --- common/lib/capa/capa/responsetypes.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index cf804f381d..dda3ce0e43 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1910,9 +1910,8 @@ class OpenEndedResponse(LoncapaResponse): try: submission = student_answers[self.answer_id] except Exception as err: - log.error('Error in OpenEndedResponse %s: cannot get student answer for %s;' - ' student_answers=%s', err, self.answer_id) - raise Exception(err) + log.error('Error in OpenEndedResponse {0}: cannot get student answer for {1}'.format(err,self.answer_id)) + raise # Prepare xqueue request #------------------------------------------------------------ @@ -1958,8 +1957,8 @@ class OpenEndedResponse(LoncapaResponse): cmap = CorrectMap() if error: cmap.set(self.answer_id, queuestate=None, - msg='Unable to deliver your submission to grader. (Reason: %s.)' - ' Please try again later.' % msg) + msg='Unable to deliver your submission to grader. (Reason: {0}.)' + ' Please try again later.'.format(msg)) else: # Queueing mechanism flags: # 1) Backend: Non-null CorrectMap['queuestate'] indicates that From 1eaf424c2036dc0bc666ba9b4283c68f579912ed Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 5 Dec 2012 13:17:16 -0500 Subject: [PATCH 081/234] Change some things to use format --- common/lib/capa/capa/responsetypes.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index dda3ce0e43..b69119cf6d 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1996,13 +1996,13 @@ class OpenEndedResponse(LoncapaResponse): oldcmap.set(self.answer_id, npoints=points, correctness=correctness, msg=msg.replace(' ', ' '), queuestate=None) else: - log.debug('OpenEndedResponse: queuekey %s does not match for answer_id=%s.' % - (queuekey, self.answer_id)) + log.debug('OpenEndedResponse: queuekey {0} does not match for answer_id={1}.'.format( + queuekey, self.answer_id)) return oldcmap def get_answers(self): - anshtml = '
%s
' % self.answer + anshtml = '
{0}
'.format(self.answer) return {self.answer_id: anshtml} def get_initial_display(self): @@ -2089,11 +2089,11 @@ class OpenEndedResponse(LoncapaResponse): score_result = json.loads(score_msg) except (TypeError, ValueError): log.error("External grader message should be a JSON-serialized dict." - " Received score_msg = %s" % score_msg) + " Received score_msg = {0}".format(score_msg)) return fail if not isinstance(score_result, dict): log.error("External grader message should be a JSON-serialized dict." - " Received score_result = %s" % score_result) + " Received score_result = {0}".format(score_result)) return fail for tag in ['score', 'feedback', 'grader_type', 'success']: if tag not in score_result: From cc870f8ecd2379b0221bf39f50721cc1b644865c Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 6 Dec 2012 14:37:09 -0500 Subject: [PATCH 082/234] homepage - changed partner university listing layout to handle 6 partners --- lms/static/sass/multicourse/_home.scss | 21 ++++++++++++++++++++- lms/templates/index.html | 18 +++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lms/static/sass/multicourse/_home.scss b/lms/static/sass/multicourse/_home.scss index b9f621977f..3d04165484 100644 --- a/lms/static/sass/multicourse/_home.scss +++ b/lms/static/sass/multicourse/_home.scss @@ -196,6 +196,24 @@ display: block; } + + hr { + @extend .faded-hr-divider-light; + border: none; + margin: 0px; + position: relative; + z-index: 2; + + &::after { + @extend .faded-hr-divider; + bottom: 0px; + content: ""; + display: block; + position: absolute; + top: -1px; + } + } + .partners { margin: 0 auto; padding: 20px 0px; @@ -206,6 +224,7 @@ padding: 0px 30px; position: relative; vertical-align: middle; + overflow: hidden; &::before { @extend .faded-vertical-divider; @@ -281,7 +300,7 @@ } img { - max-width: 160px; + max-width: 190px; position: relative; @include transition(all, 0.25s, ease-in-out); vertical-align: middle; diff --git a/lms/templates/index.html b/lms/templates/index.html index 05fef3dffa..fffe84ee54 100644 --- a/lms/templates/index.html +++ b/lms/templates/index.html @@ -48,7 +48,7 @@

Explore free courses from edX universities

-
    +
    1. @@ -65,7 +65,7 @@
-
  • +
  • @@ -73,6 +73,11 @@
  • + + +
    + +
    1. @@ -81,6 +86,14 @@
    +
  • + + +
    + WellesleyX +
    +
    +
  • @@ -89,7 +102,6 @@
  • -
    From edbc12e1e32d5c1ef226041166ab987764a95323 Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 6 Dec 2012 17:02:43 -0500 Subject: [PATCH 083/234] homepage - added georgetown logo assets and resized other university logos to match 3x3 layout --- .../georgetown/georgetown-rollover_350x150.png | Bin 0 -> 10499 bytes .../images/university/georgetown/georgetown.png | Bin 0 -> 10499 bytes .../university/ut/ut-rollover_350x150.png | Bin 0 -> 11746 bytes .../wellesley/wellesley-rollover_350x150.png | Bin 0 -> 5608 bytes lms/templates/index.html | 10 +++++----- 5 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 lms/static/images/university/georgetown/georgetown-rollover_350x150.png create mode 100644 lms/static/images/university/georgetown/georgetown.png create mode 100644 lms/static/images/university/ut/ut-rollover_350x150.png create mode 100644 lms/static/images/university/wellesley/wellesley-rollover_350x150.png diff --git a/lms/static/images/university/georgetown/georgetown-rollover_350x150.png b/lms/static/images/university/georgetown/georgetown-rollover_350x150.png new file mode 100644 index 0000000000000000000000000000000000000000..d5d6f1e84907baa23d6059595a7b967b0b976027 GIT binary patch literal 10499 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 literal 0 HcmV?d00001 diff --git a/lms/static/images/university/wellesley/wellesley-rollover_350x150.png b/lms/static/images/university/wellesley/wellesley-rollover_350x150.png new file mode 100644 index 0000000000000000000000000000000000000000..8636424d152b311e474f5bea53afc43c4a9a0f14 GIT binary patch literal 5608 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
  • - +
    UTx
    @@ -88,17 +88,17 @@
  • - +
    WellesleyX
  • - - + +
    - WellesleyX + GeorgetownX
  • From 0bc06579395083619c4c2f8be9e85b3c7a4ed3d1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 7 Dec 2012 09:21:23 -0500 Subject: [PATCH 084/234] Add in support for ordering tags properly in feedback box --- common/lib/capa/capa/responsetypes.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index b69119cf6d..e4a4469d03 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2017,6 +2017,10 @@ class OpenEndedResponse(LoncapaResponse): String """ + #Tags that need to be shown at the end of the feedback block (in this order) + tags_displayed_last=['markup-text', 'markup_text'] + tags_displayed_first=['spelling', 'grammar'] + feedback_item_start='
    ' feedback_item_end='
    ' @@ -2032,12 +2036,21 @@ class OpenEndedResponse(LoncapaResponse): success=response_items['success'] + if success: feedback_long="" + #Add in feedback that needs to be shown first for k,v in feedback_items.items(): - feedback_long+=feedback_item_start.format(feedback_key=k) - feedback_long+=str(v) - feedback_long+=feedback_item_end + if k in tags_displayed_first: + feedback_long+= feedback_item_start.format(feedback_key=k) +str(v) + feedback_item_end + #Add in feedback whose order does not matter + for k,v in feedback_items.items(): + if k not in tags_displayed_last and k not in tags_displayed_first: + feedback_long+= feedback_item_start.format(feedback_key=k) +str(v) + feedback_item_end + #Add in feedback that needs to be displayed last + for k,v in feedback_items.items(): + if k in tags_displayed_last: + feedback_long+= feedback_item_start.format(feedback_key=k) +str(v) + feedback_item_end if len(feedback_items)==0: feedback_long=feedback_item_start.format(feedback_key="feedback") + "No feedback available." + feedback_item_end From 6d2688cb66b7b8f492c6b9c003f615aab6d42287 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Dec 2012 11:05:28 -0500 Subject: [PATCH 085/234] unicode fix and cleanup in stringify children --- common/lib/capa/capa/responsetypes.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index e4a4469d03..f55bea4187 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1845,24 +1845,18 @@ class OpenEndedResponse(LoncapaResponse): rubric = self.xml.find('openendedrubric') self._parse(oeparam, prompt, rubric) - def stringify_children(self, node, strip_tags=True): + @staticmethod + def stringify_children(node): """ Modify code from stringify_children in xmodule. Didn't import directly in order to avoid capa depending on xmodule (seems to be avoided in code) """ parts=[node.text] - [parts.append((etree.tostring(p, with_tail=True))) for p in node.getchildren()] - node_string=' '.join(parts) + for p in node.getchildren(): + parts.append(etree.tostring(p, with_tail=True, encoding='unicode')) - # Strip html tags from result. This may need to be removed in order to - # display prompt to instructors properly. - # TODO: what breaks if this is removed? The ML code can strip tags - # as part of its input filtering. - if strip_tags: - node_string=re.sub('<[^<]+?>', '', node_string) - - return node_string + return ' '.join(parts) def _parse(self, oeparam, prompt, rubric): ''' From 3da727125f86fa9f783b37d3c1409ea206292823 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Dec 2012 11:06:13 -0500 Subject: [PATCH 086/234] fix feedback formatting --- common/lib/capa/capa/responsetypes.py | 99 +++++++++++++++------------ 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index f55bea4187..c05918382f 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -2002,57 +2002,67 @@ class OpenEndedResponse(LoncapaResponse): def get_initial_display(self): return {self.answer_id: self.initial_display} - def _convert_longform_feedback_to_html(self,response_items): + def _convert_longform_feedback_to_html(self, response_items): """ - Take in a dictionary, and return html formatted strings appropriate for sending via xqueue. + Take in a dictionary, and return html strings for display to student. Input: - Dictionary with keys success, feedback, and errors + response_items: Dictionary with keys success, feedback. + if success is True, feedback should be a dictionary, with keys for + types of feedback, and the corresponding feedback values. + if success is False, feedback is actually an error string. + + NOTE: this will need to change when we integrate peer grading, because + that will have more complex feedback. + Output: - String + String -- html that can be displayed to the student. """ - #Tags that need to be shown at the end of the feedback block (in this order) - tags_displayed_last=['markup-text', 'markup_text'] - tags_displayed_first=['spelling', 'grammar'] + # We want to display available feedback in a particular order. + # This dictionary specifies which goes first--lower first. + priorities = {# These go at the start of the feedback + 'spelling': 0, + 'grammar': 1, + # needs to be after all the other feedback + 'markup_text': 3} - feedback_item_start='
    ' - feedback_item_end='
    ' + default_priority = 2 - for tag in ['status', 'feedback']: + def get_priority(elt): + """ + Args: + elt: a tuple of feedback-type, feedback + Returns: + the priority for this feedback type + """ + return priorities.get(elt[0], default_priority) + + def format_feedback(feedback_type, value): + return """ +
    + {value} +
    + """.format(feedback_type, value) + + for tag in ['success', 'feedback']: if tag not in response_items: - feedback_long=feedback_item_start.format(feedback_key="errors") + "Error getting feedback." + feedback_item_end + return format_feedback('errors', 'Error getting feedback') - feedback_items=response_items['feedback'] + feedback_items = response_items['feedback'] try: - feedback_items=json.loads(feedback_items) - except: - pass + feedback = json.loads(feedback_items) + except ValueError: + log.exception("feedback_items have invalid json %r", feedback_items) + return format_feedback('errors', 'Could not parse feedback') - success=response_items['success'] - - - if success: - feedback_long="" - #Add in feedback that needs to be shown first - for k,v in feedback_items.items(): - if k in tags_displayed_first: - feedback_long+= feedback_item_start.format(feedback_key=k) +str(v) + feedback_item_end - #Add in feedback whose order does not matter - for k,v in feedback_items.items(): - if k not in tags_displayed_last and k not in tags_displayed_first: - feedback_long+= feedback_item_start.format(feedback_key=k) +str(v) + feedback_item_end - #Add in feedback that needs to be displayed last - for k,v in feedback_items.items(): - if k in tags_displayed_last: - feedback_long+= feedback_item_start.format(feedback_key=k) +str(v) + feedback_item_end - - if len(feedback_items)==0: - feedback_long=feedback_item_start.format(feedback_key="feedback") + "No feedback available." + feedback_item_end + if response_items['success']: + if len(feedback) == 0: + return format_feedback('errors', 'No feedback available') + feedback_lst = sorted(feedback.items(), key=get_priority) + return u"\n".join(format_feedback(k, v) for k, v in feedback_lst) else: - feedback_long=feedback_item_start.format(feedback_key="errors") + response_items['feedback'] + feedback_item_end - - return feedback_long + return format_feedback('errors', response_items['feedback']) def _format_feedback(self, response_items): @@ -2063,15 +2073,16 @@ class OpenEndedResponse(LoncapaResponse): Return error message or feedback template """ - feedback=self._convert_longform_feedback_to_html(response_items) + feedback = self._convert_longform_feedback_to_html(response_items) if not response_items['success']: - return self.system.render_template("open_ended_error.html", {'errors' : feedback}) + return self.system.render_template("open_ended_error.html", + {'errors' : feedback}) - feedback_template=self.system.render_template("open_ended_feedback.html",{ - 'grader_type' : response_items['grader_type'], - 'score' : response_items['score'], - 'feedback' : feedback, + feedback_template = self.system.render_template("open_ended_feedback.html", { + 'grader_type': response_items['grader_type'], + 'score': response_items['score'], + 'feedback': feedback, }) return feedback_template From 42f8afb08346567b9bd88e5873fc6128b767d8c7 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Dec 2012 11:07:28 -0500 Subject: [PATCH 087/234] address minor code review comments --- common/lib/capa/capa/responsetypes.py | 31 ++++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index c05918382f..89bc7e4a2c 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1880,15 +1880,15 @@ class OpenEndedResponse(LoncapaResponse): # NOTE: self.system.location is valid because the capa_module # __init__ adds it (easiest way to get problem location into # response types) - parsed_grader_payload.update({ - 'location' : self.system.location, - 'course_id' : self.system.course_id, - 'prompt' : prompt_string, - 'rubric' : rubric_string, - }) - updated_grader_payload = json.dumps(parsed_grader_payload) - except Exception as err: + except ValueError: log.exception("Grader payload %r is not a json object!", grader_payload) + parsed_grader_payload.update({ + 'location' : self.system.location, + 'course_id' : self.system.course_id, + 'prompt' : prompt_string, + 'rubric' : rubric_string, + }) + updated_grader_payload = json.dumps(parsed_grader_payload) self.payload = {'grader_payload': updated_grader_payload} @@ -1903,9 +1903,11 @@ class OpenEndedResponse(LoncapaResponse): try: submission = student_answers[self.answer_id] - except Exception as err: - log.error('Error in OpenEndedResponse {0}: cannot get student answer for {1}'.format(err,self.answer_id)) - raise + except KeyError: + msg = ('Cannot get student answer for answer_id: {0}. student_answers {1}' + .format(self.answer_id, student_answers)) + log.exception(msg) + raise LoncapaProblemError(msg) # Prepare xqueue request #------------------------------------------------------------ @@ -1959,7 +1961,7 @@ class OpenEndedResponse(LoncapaResponse): # the problem has been queued # 2) Frontend: correctness='incomplete' eventually trickles down # through inputtypes.textbox and .filesubmission to inform the - # browser to poll the LMS + # browser that the submission is queued (and it could e.g. poll) cmap.set(self.answer_id, queuestate=queuestate, correctness='incomplete', msg=msg) @@ -1984,9 +1986,8 @@ class OpenEndedResponse(LoncapaResponse): # Sanity check on returned points if points < 0: points = 0 - elif points > self.maxpoints[self.answer_id]: - points = self.maxpoints[self.answer_id] - # Queuestate is consumed + + # Queuestate is consumed, so reset it to None oldcmap.set(self.answer_id, npoints=points, correctness=correctness, msg=msg.replace(' ', ' '), queuestate=None) else: From bf593c6603a79a31d425bb20d2acb43227afdbeb Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Dec 2012 11:27:58 -0500 Subject: [PATCH 088/234] clean up imports --- common/lib/capa/capa/responsetypes.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 89bc7e4a2c..6d15aba54f 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -8,21 +8,23 @@ Used by capa_problem.py ''' # standard library imports +import abc import cgi +import hashlib import inspect import json import logging import numbers import numpy +import os import random import re import requests -import traceback -import hashlib -import abc -import os import subprocess +import traceback import xml.sax.saxutils as saxutils + +from collections import namedtuple from shapely.geometry import Point, MultiPoint # specific library imports From 7190e484f11b526b783bf16d18917c9fa8b06f53 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Dec 2012 11:32:33 -0500 Subject: [PATCH 089/234] Use a namedtuple for parsing ScoreMessages --- common/lib/capa/capa/responsetypes.py | 50 +++++++++++++++------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 6d15aba54f..5e496df649 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1103,6 +1103,15 @@ class SymbolicResponse(CustomResponse): #----------------------------------------------------------------------------- +""" +valid: Flag indicating valid score_msg format (Boolean) +correct: Correctness of submission (Boolean) +score: Points to be assigned (numeric, can be float) +msg: Message from grader to display to student (string) +""" +ScoreMessage = namedtuple('ScoreMessage', + ['valid', 'correct', 'points', 'msg']) + class CodeResponse(LoncapaResponse): """ @@ -1882,7 +1891,7 @@ class OpenEndedResponse(LoncapaResponse): # NOTE: self.system.location is valid because the capa_module # __init__ adds it (easiest way to get problem location into # response types) - except ValueError: + except TypeError, ValueError: log.exception("Grader payload %r is not a json object!", grader_payload) parsed_grader_payload.update({ 'location' : self.system.location, @@ -1971,13 +1980,13 @@ class OpenEndedResponse(LoncapaResponse): def update_score(self, score_msg, oldcmap, queuekey): log.debug(score_msg) - (valid_score_msg, correct, points, msg) = self._parse_score_msg(score_msg) - if not valid_score_msg: + score_msg = self._parse_score_msg(score_msg) + if not score_msg.valid: oldcmap.set(self.answer_id, msg = 'Invalid grader reply. Please contact the course staff.') return oldcmap - correctness = 'correct' if correct else 'incorrect' + correctness = 'correct' if score_msg.correct else 'incorrect' # TODO: Find out how this is used elsewhere, if any self.context['correct'] = correctness @@ -1986,12 +1995,13 @@ class OpenEndedResponse(LoncapaResponse): # does not match, we keep waiting for the score_msg whose key actually matches if oldcmap.is_right_queuekey(self.answer_id, queuekey): # Sanity check on returned points + points = score_msg.points if points < 0: points = 0 # Queuestate is consumed, so reset it to None oldcmap.set(self.answer_id, npoints=points, correctness=correctness, - msg=msg.replace(' ', ' '), queuestate=None) + msg = score_msg.msg.replace(' ', ' '), queuestate=None) else: log.debug('OpenEndedResponse: queuekey {0} does not match for answer_id={1}.'.format( queuekey, self.answer_id)) @@ -2047,6 +2057,10 @@ class OpenEndedResponse(LoncapaResponse):
    """.format(feedback_type, value) + # TODO (vshnayder): design and document the details of this format so + # that we can do proper escaping here (e.g. are the graders allowed to + # include HTML?) + for tag in ['success', 'feedback']: if tag not in response_items: return format_feedback('errors', 'Error getting feedback') @@ -2054,7 +2068,7 @@ class OpenEndedResponse(LoncapaResponse): feedback_items = response_items['feedback'] try: feedback = json.loads(feedback_items) - except ValueError: + except (TypeError, ValueError): log.exception("feedback_items have invalid json %r", feedback_items) return format_feedback('errors', 'Could not parse feedback') @@ -2105,45 +2119,35 @@ class OpenEndedResponse(LoncapaResponse): correct: Correctness of submission (Boolean) score: Points to be assigned (numeric, can be float) """ - fail = (False, False, 0, '') + fail = ScoreMessage(valid=False, correct=False, points=0, msg='') try: score_result = json.loads(score_msg) except (TypeError, ValueError): log.error("External grader message should be a JSON-serialized dict." " Received score_msg = {0}".format(score_msg)) return fail + if not isinstance(score_result, dict): log.error("External grader message should be a JSON-serialized dict." " Received score_result = {0}".format(score_result)) return fail + for tag in ['score', 'feedback', 'grader_type', 'success']: if tag not in score_result: - log.error("External grader message is missing required tag: {0}".format(tag)) + log.error("External grader message is missing required tag: {0}" + .format(tag)) return fail - # Next, we need to check that the contents of the external grader message - # is safe for the LMS. - # 1) Make sure that the message is valid XML (proper opening/closing tags) - # 2) TODO: Is the message actually HTML? - feedback = self._format_feedback(score_result) - # HACK: for now, just assume it's correct if you got more than 2/3. # Also assumes that score_result['score'] is an integer. score_ratio = int(score_result['score']) / self.max_score correct = (score_ratio >= 0.66) - log.debug(feedback) - try: - etree.fromstring(feedback) - except etree.XMLSyntaxError as err: - log.error("Unable to parse external grader message as valid" - "\n Feedback : score_result['feedback'] = %r",feedback) - return fail - #Currently ignore msg and only return feedback (which takes the place of msg) - return (True, correct, score_result['score'], feedback) + return ScoreMessage(valid=True, correct=correct, + score=score_result['score'], msg=feedback) #----------------------------------------------------------------------------- # TEMPORARY: List of all response subclasses From bd94474a50856e46576f26bae525858d8603dd0d Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Dec 2012 11:38:21 -0500 Subject: [PATCH 090/234] improve docstring --- common/lib/xmodule/xmodule/self_assessment_module.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 8498a210cd..eb8a275d35 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -373,6 +373,14 @@ class SelfAssessmentModule(XModule): def save_answer(self, get): """ After the answer is submitted, show the rubric. + + Args: + get: the GET dictionary passed to the ajax request. Should contain + a key 'student_answer' + + Returns: + Dictionary with keys 'success' and either 'error' (if not success), + or 'rubric_html' (if success). """ # Check to see if attempts are less than max if self.attempts > self.max_attempts: From 53580af95253cab12da3cd0ce3461b0fa608b7c5 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Dec 2012 11:59:18 -0500 Subject: [PATCH 091/234] fix aws setting name --- lms/envs/aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index d1abce8a6d..0516bddc56 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -76,7 +76,7 @@ DATABASES = AUTH_TOKENS['DATABASES'] XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE'] -STAFF_GRADING_BACKEND = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE') +STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE') PEARSON_TEST_USER = "pearsontest" From 730cdd3b4fff2be9a1aebbbaca8ea4b3a6d30345 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Dec 2012 12:00:03 -0500 Subject: [PATCH 092/234] minor changes from pull request comments - comments - more info in log msg - better error --- .../instructor/staff_grading_service.py | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index 61654997bd..a5b29e2c5b 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -98,8 +98,19 @@ class StaffGradingService(object): def get_next(self, course_id, grader_id): """ - Get the next thing to grade. Returns json, or raises GradingServiceError - if there's a problem. + Get the next thing to grade. + + Args: + course_id: course id to get submission for + grader_id: who is grading this? The anonymous user_id of the grader. + + Returns: + json string with the response from the service. (Deliberately not + writing out the fields here--see the docs on the staff_grading view + in the grading_controller repo) + + Raises: + GradingServiceError: something went wrong with the connection. """ op = lambda: self.session.get(self.get_next_url, allow_redirects=False, @@ -113,16 +124,18 @@ class StaffGradingService(object): return r.text - + def save_grade(self, course_id, grader_id, submission_id, score, feedback): """ Save a score and feedback for a submission. - Returns json dict with keys - 'success': bool - 'error': error msg, if something went wrong. + Returns: + json dict with keys + 'success': bool + 'error': error msg, if something went wrong. - Raises GradingServiceError if there's a problem connecting. + Raises: + GradingServiceError if there's a problem connecting. """ try: data = {'course_id': course_id, @@ -216,8 +229,10 @@ def _get_next(course_id, grader_id): try: return grading_service().get_next(course_id, grader_id) except GradingServiceError: - log.exception("Error from grading service") - return json.dumps({'success': False, 'error': 'Could not connect to grading service'}) + log.exception("Error from grading service. server url: {0}" + .format(grading_service().url)) + return json.dumps({'success': False, + 'error': 'Could not connect to grading service'}) @expect_json @@ -239,10 +254,12 @@ def save_grade(request, course_id): if request.method != 'POST': raise Http404 - required = ('score', 'feedback', 'submission_id') - for k in required: - if k not in request.POST.keys(): - return _err_response('Missing required key {0}'.format(k)) + required = set('score', 'feedback', 'submission_id') + actual = set(request.POST.keys()) + missing = required - actual + if len(missing) != 0: + return _err_response('Missing required keys {0}'.format( + ', '.join(missing))) grader_id = request.user.id p = request.POST From 44a8f31d0693db18ce1cf334cb8fa3e2bbf942df Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Dec 2012 13:39:07 -0500 Subject: [PATCH 093/234] Change are-we-logged-in detection to be less hackish. - has matching changes in controller staff_grading view. --- lms/djangoapps/instructor/staff_grading_service.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/instructor/staff_grading_service.py b/lms/djangoapps/instructor/staff_grading_service.py index a5b29e2c5b..b3b0f99e74 100644 --- a/lms/djangoapps/instructor/staff_grading_service.py +++ b/lms/djangoapps/instructor/staff_grading_service.py @@ -77,15 +77,16 @@ class StaffGradingService(object): def _try_with_login(self, operation): """ Call operation(), which should return a requests response object. If - the response status code is 302, call _login() and try the operation - again. NOTE: use requests.get(..., allow_redirects=False) to have - requests not auto-follow redirects. + the request fails with a 'login_required' error, call _login() and try + the operation again. Returns the result of operation(). Does not catch exceptions. """ response = operation() - if response.status_code == 302: - # redirect means we aren't logged in + if (response.json + and response.json.get('success') == False + and response.json.get('error') == 'login_required'): + # apparrently we aren't logged in. Try to fix that. r = self._login() if r and not r.get('success'): log.warning("Couldn't log into staff_grading backend. Response: %s", From cc68ba5bc9d0f465a245db37810038c68ac21c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Fri, 7 Dec 2012 11:59:13 -0500 Subject: [PATCH 094/234] Add Georgetown news announcement, landing page and updated FAQ [#40805397] --- .../releases/georgetown-seal_240x180.png | Bin 0 -> 32625 bytes .../georgetown/georgetown-cover_2025x550.jpg | Bin 0 -> 317367 bytes lms/templates/feed.rss | 11 ++- lms/templates/index.html | 2 +- lms/templates/static_templates/faq.html | 73 +++++++++--------- .../press_releases/Georgetown_joins_edX.html | 73 ++++++++++++++++++ .../university_profile/georgetownx.html | 24 ++++++ lms/urls.py | 5 +- 8 files changed, 147 insertions(+), 41 deletions(-) create mode 100644 lms/static/images/press/releases/georgetown-seal_240x180.png create mode 100644 lms/static/images/university/georgetown/georgetown-cover_2025x550.jpg create mode 100644 lms/templates/static_templates/press_releases/Georgetown_joins_edX.html create mode 100644 lms/templates/university_profile/georgetownx.html diff --git a/lms/static/images/press/releases/georgetown-seal_240x180.png b/lms/static/images/press/releases/georgetown-seal_240x180.png new file mode 100644 index 0000000000000000000000000000000000000000..1652b2805e0394c84c23b926d69ed4ec2c385570 GIT binary patch literal 32625 zcmYg&2RxQ-{I5!sy)rY3Bs)7Zdke|VCVOX(@JjY3WRulnW@TqSMzXTA_ugckpZ_`M zbIzx?m!yaLzOL*0`>tECvXV451~~=_3JSKYjD#w@-G$%Z@1eo}3%_Kp!W%Rrd1(oh z>ziL04SBKfk^A;CT23e^n1naKZ=ocAA%zd3JIg9cqEBI=x)=1;5^Gxju(CrVv;ums1BZr5R6Tl5%;vcb+nmJ6mqHf+*Fn~|)LQQz||G)eHUgCehS|hII8O!OKWW&IG%Q{s`G#A1J zJy5Kr*ZgxqCZ0#?^3!8M)o}vf!$ghGq*zAPq-(f8NGa}hQAfe6QyUoy$vxIFqFFS{ z@5F5Gt5tMtXC%%}bCJ%_wXt9N!(o_yr{UKzp{FfBB3Zqz^`}%AvW13H)WB50ugBI* zAYnOpXyNcQbw7xK@HCyCs#wachCFlJGFz3gl=P^#M7p-AT>7{1JGU|UijleFpFF~e z*oftz>oMU&=1pf*6s~0IOWNvWY_H|D*Iq0C1e3(bDpaH!)t2gZ*bI2qclnq2vTkjr{uoGhPnq^FyXs)6`C+^LlTR z&FDGV)n8iIijmBb?cdg}tInv}TR8q>l0=dPtC?z9${$opB#TtCNAd?OMuM)dTtzdL z?Hvo#)rxxqMua9P{g9b7yi~RvM906a-+!WW%zx9}bW)2v-Fx|HvioWwa|qvUsar_f z=2p0r>t+3sxdCjk$hlqv2aC=8Gk%k zhE=APma3T`Rx9<}I8W6|m0@&sPhh}mgxR`HbL^9YS5_ib6Je}qP>-1z`(`HZY?~!d zhAJa2^@82P_B@Z>w5C|zn}hlG?E1aGg6SUXeCKPAQC1>wkksGm?_3YPtfR$~T}&dw zuBdP5UrLeBae5%AgeleVakNAz2UF19>tIXkF;dW~PF9zPb4w{`%RJey zRQk%^^!D{NF4N?>bKP4pJ#_VoTe+%!q9rZ1+=FYM7~aJwC4WTzAsXq)|MTr(7J(qHgYr`pry8IPpP)l6W00$JEAB zn+Y|1H{N*BY$zN$d^@bfA3|px%#@GA$-Vxn2dcJ1PC15@*Cnk)Zmxp%m zoN|nX{=$|?FYj*Rv;tkyJ8#}#sqBu8a--iCYnVSuKOat|&6v@~WK9!mPJ}JEYL$J) zT9kid+he9NAph^@{)@%CG=!(`NR}*9 zcXZRG@#^TDo^V+{V66)-avgOmC1^4EZOBb=?`%^6C-Cz_tXU^`@k6Zt1iuSV{{2zb zHab%;|M>Od3abK~CkA!$?-P_&S!B%gLd3BV6`TQIggKgJ(j)i$P;NzH$EpO4n7k|* zd08DgV*RpY2wsb9>%^GLe0;KBwUA4>D2~~-Eq^#A?C|V&{)pF7{Kltg)=Ce|yy>u3 z%dEfF;v}%0^%CRNi(2=Og^{|!Eu}$T1>GZcR$Mxiw{0oZD|Dw~B7chn;GssV6)P00 znOZXZr=1IXqgJS?&uT}QZn472`CE{ZbhFW*lVu@wb*tm)`JP(Zj(2I7ev^BmCS!kI zx^Qr(;Uf#qSf)3!rt*T0)}Q=JQxekzKgt!n%vCkwSd}rAzgRliE-l4x%^xy&PQjo- zLB#alOzP*w@;laf*Gbq7d&hlG9wQ%)NX1s$vEm=|lVZ6)Q?pcCSJZ3b>D(M-ZMyiW zmF9VPk1*g?@r=$pk=Jdu&k6P?2J+ubCC|+FHy#Pc28gnzJv3#DHuVvb)7&QPLA}yq zQCkXmQ?JBim>y6*-aMrpS9^4?KsR`Suz^5j=eMkAWFSXyz=kjQqM`(B2(D$x#o_4@K^ReltnVLR@L15%Tr{LSw zKHW|UsMoLmc{wnY6NRoqvOJKHrvg86=mw^p?A#M)&R6>J?I+Ev1coVCusjg;G>f!{ z{lsv1CkL=llv09Sx4-TF>683nlt|kuisn!C^`rlAijB1y?TQ(iNq(g2n4!J#Z`8hNWJXEaj7?Q%jK&ep9|_0~YbRzVN1|FVsR zHaj>s--W`s0<2PDBqHkOzFK0_pT8J*G=;;NH71EC@s0kDD@pZu@VihFlHM#@KX!Lx& zED8H_xQDxF7wQ$+q~GLiz(YH=p1yiD`(Dd}Yf?}%+}_hL)T5$xSUE~M0-JMi zLmZ~qs71}5eSl{9hf&jZo9f@fc&`6><6N*ZxfH=t`i)vVO71;CFr7RncOE8Z`DS+K zReHLKC0|==yNxCGn!FiP@_fTMHvlSeHdRSaH}BuHr4~7fLR%{}x{4>Tq;k0g={+Gm zwjD+>_n84Fu*fShQl$cT(DAr16jsm{#9wR~eJ>&_09d8eoW;KfVq6S)0Kg<}$VQ(XVb|b94TH8l zCs9XXpYk-18Y9p@s$VpF+>j~ZEAQ;%#Y#>$8kgNV8-Qk%-AZ2Dy?Bk8yw083Qwiz~ z&u3vCEXJ2UY5cr?{iddzF-Gs7igHEMzRv$`UbV-^tbiS4KbbqP!5qIbEl~7ri{AYE zL>Mlr{dk}ATGnXc9zSB0%TnEy;4_xCKa~Kjw=kNRt(1qiZtB}RKjN&g)vHhLpMQBs zxp9}sT1u4x1!f0VbhfUQy{Avg3RUXQtTL8Vlv&cS5< z2xFTwN#Jea^UY^Jg`fIf9>0}PS@gXcd8hSvVg;6-^&#~fa%cf?Fzc`RLX{4nYaAms zG4DKl1OINbW+4TG6=dmmJ5k&-u8k!sMwb87(>a=RO~Pd^|9M>h@7JAe=1qfY@}!3` z9`!SkXO#?zcC3aR>!-a#d0j-^7vt5!Q5a$YqHy=Uj>2{7y`Ao(6d_TZ3wU)+17;BtZn)+|$ia5%8zqIq0SbFn1TVbOepd8Ovj`=F#zwunaWo9#nu^kZzHd(x z7Q%vx!0sH2oc$Pg4yT5b6L&i6(B~&kOu5Og%w88i4 z;k$QewG}nunQBY(1u9tDEV!eO6(avrDN-{DeUrR}Gi{rQ%=|rv^7F4xf>nhT%Eg~| z)bHN&wp7+xS#Rd%b8C}g^WTgsSK?~fWjQ_@`g&F-zy(pF+KSS?zdjL3W$e-xE-tiOPAcMxuzjxPRS1$EU(c14IG+9Cc>45w6}NUgBqxb%OEgz?-bM2>74||Y zd2Mxl&!E|t0Uv7N#CR9!mzm15-;uPR_Hiy=e=w^3rB2t!CghuN6%{HYMsP+_bQF+X zaI0yu<7Jm3(Cz?q3KXw{eP&=rk*$xXNe&+PoRd(mz&%jgU`a;s>^b}KifGbl6z;db zdfU8Es%*oBFM(ev7buqb^2l2p2X>`nr5|7xfz+H{^r)Zf6@H9U18QV4)I!o!KO zq;Nsa=>?Id<6BNv_)`D8G!;CIkb*nYIt#tgw_t+ZwgSuY+Y-~cE=C-^ z`Q=#a^)Az^y&&Ix{zN;LJ9vtVXC0F#zc;>n?HU9$W`t&zq8E+71oVUDQNX!7V5w$~ zvyRFE6JLapafE)rB+W5bw1}3ynmAY22F}-j1viMjDfBvh#DI|_gN*O*zNWK%SN7HK zfWCN&a&_}T#l)j#qxR#yFo;1i0iFXilR9fBR3FQlg>vZ(?v3Y<&cD|#OU43-8D@OY zr=HR$gHbjqS)gW6L0YC01_u}*txWA!xORJcZ1$IQvd22#c9#-MXdiyh$nh!5c|7z` za3^Vi{ZR=eLP}8@4-f(CKOt*r&U$a-=#ec{;pztgHzfWc@l+TZ<*jOLgm&MQHc=VV zyo2T{X3gT?QiWMxFQCQUm1s+r0r6YjFy#QvQ8VR9;MN3fB|lXP=X^o5_;;UG|KEgZ zNc;~jXZ-^F{TKJ#HsP2DclqED8B~*&C1aZpGk&+^t~CmbpiwR0W_Yu1&TV;NE8x#C zTb}ZKy>j(E`A=bV`T(4ic7|wWM2=>0Y;U~V;uyQJw~vy-v4gD{UiFYzmGWFit71qz zKhxxWAoTGbv7o?8`-8_GkZtdA2;*g7*1tE7*5r6qrrK)8gZGLA4*Emtoux$+k?NJ| zv&Gy6P-p~61mZ$^zo;pVEUeN&)YVWDM_>ivjA(_M=}?Kp-M@&u12?OPrL-y~XBvOJ zpXj&6$YKw-QNe8hC|K4nsbp=o+~uWI3-G;Az2Iq6=jsa&N)dE1`ma+dOx5Z;>DtlW zaII|P(e|A-+r$Aq>MVxx8nTtT(G~a2HU_1-V5y1#)yy1S8&I~S+j~aUa^k1_h>svB z08fBvfv5rBLa8XYU&u8Xgbfe`v+Oj9uU+T3e`L+%#t>5edIIpNAT;J2G}+fHbyMEb zCb0k&1(8shii)%jo;;X@P3kbt@)?WY<5KB1l|ac`?`DIp)}a;p=c|>lG=9@=5rl-^ z=y^drqLBf>kU@?9nIbmJrdsfODe)1;EGZF2yJ1nz_`1PwOAE|F=Yg^^JvhW0b+XpP zv>>@Wnd-jne1&N#4XVVEy^BqkKZ54d4SkiU>bhJKx?rW?%r?|@!Q4~x<1Oxw{-yDm z9OH4w+brZmppTBZzfKLJ%6w*|(a`uQ{g|pXPGjx#vQ@3Jlo>Rq!Z@4o+jB-?8!8v6 z3F?O1U;gF4!Q&la&*&j(G`I6*0(eyzX3yd*LFWRMi{_1Fs4+aJcn<>rj@? zy2NTz|1W8{1=KSX1*%!2me17Jhf`R-ehdKm1&RkaTeL|8taUk=?4G_}UCWYA&$n&* zPkERZzgE&_7vsLa#`$krvriTGxK=vS-h@se_m7DMr&8@ib4IL5BuqMao%6xBuMuJh zRgS5JR+BiFQpd_))qlO46C3~W7*+Rl*oFv%3dAwKhjRxG$xkyL_Thy`Fe!g+J%FvT z718Wjv9a04@t?O6tS-p16whZ8WdLazB8yU)%8pCN9NLxSK_gua`DQg;5Kb6R zg`Y>ZWWj`it4I(yOhl5XqKjvkL#vgR5@kKZ_ISCfEiPQNTe=u`mntKj2EPUjYTwt5k4a2IJh4Ms=D^2luChSRq^ z{!|n`)z~FNA;^4V*o05nC|CORE>VEsPa7?^SpJtuh3iutUee?q^g$;v%y}^n> z&BEp(4N_N@1G^Nt0o>~qX{}NGcrGQ7^7TGq?Qo2y9y$Hrfs?j#Fa^OlD<$mbc#dbn zzBzYe!@wZ|PA*5xO(g)n-P!fQiv?x*&OBKN1q;36zC>dHUy97)2_a^w=7JDnW9!K!Otf9!cSa2)h`M?UM^Ujb}gGW_#wES#IcKb zAk38+iQrDaHC8V>G23k!bIZS$=@+5zyHjGlb70TyKVxdju$c=K{*}HDZh&6s?N50a zZLjZEWs|{w^k{b^V((TNTYyBOZ5TvmCYcXeV(x?Lxrmj_yYba(e0)>QIbP{Ex6^ih zd?Cy_?JL6d+IQs`gIaKG@lZ`9wpA3=sy_{Yn%1;zd1K0A?;*-iw&6=vHE+IBWdtt_ zs_z0n=RO3`eCjRfT)lTYl)?f0t9m8O1@y9~^w~JED$>4Pm)B<(*~o{O6e_4HZ8p5w zd^8B{+#K3Z=*bpr9;qLTDvCfk@x2(oaVlg1hbA?sfise=9nF=eyzn;ppnpAc9XE5` zN8im&==o0zO<^8!J-ue61^u0jKN}50)NLe`0yMeF1to+=IZVi6? z!LmVeY-9FkFh0l`S7H|wJV_+3cF7QUTCk%R<=()31@@$u7wA$ap8F@mo2K$&@^trl zgo1_mk8!=LU8<7wVrcr04VL}`$ya&y035B3jMxc-BdHWXUvNyTCzoJyVg%j> zIaH-AN+cGgXX}Z^~k3c(uxhUvk8K>`cOoU<I>>8bOUokh)#9fo2R8`1n3DMJ z?06&yhOVv)A@Gh=&E?XiC*p8S-Oz0-D0@mN`Z)$yT5YPmB%)|s>OdiRr@atqA78um zx1pjN3~xatrYudmG}arnV)lK7U1qbgXb^P>Bv6@;h|sO4mhl9sQsT7}8Gp<()npn6 z0p?7qBp5Z8Q|$U+{;;PU9^TdMW!-^k)jfii?JcaNbTqVf55%l!k&2U*ZHgrVi5aPA zJZz;kBQNKwS3=pKu2`knV#Ec6lHrXU82XI4{sNc+UONGIeb&w@z%t$t+>lPJskl=W(Ijc@)cGy>FvbaRX3Hqjzh=4OSiYgwYOOa zPoFCMm6Gqit6Ib-!1+luZ_X|>7=jRuo#H`!iRC{zl-o%MuLrl^794DzJwy9x>&0r( zR`S;zrY_x4ycEl{^vEDi!KlSp*wB(*B{JF%Le2c?HxV@{ZF6HOrpas`+K5zYlBPk$mbvi9j$yqouq@2aWAcw>f+uu_lfEVT%HVYp|p0k=RuXX@H`<-};)eINUj<90Z9 zFTn6wj0sb+8N=qIRHlP1E8P2M4^MS@Bvd3al|ky1B^@b5egvJawxI|NIoKK~mm)vg zn-rZboIurZh0NPBF@3CWC;UGlGqO8Z;ye%toHJDz0B?)`z6Jiy{+u*pz#4?yfCbl- z{$JClX)Q(^KvY$p#pfKDT80z9i@=h>&+~6~;ZkHy>NMr$6ndDYYQ-)@VC2CSCWSB( zNL~gqRkz2SeJLCfc`~@*1pD-x_0#-eaypLo)~3DH=W9Otg~P%0xni^NlT0wn_vd47 zt{T`;xO(VXG`n_qvQcitu6OGZ99&@DK1=NtVMplft6YF}gJ&3+6wkNWcIMt|zj!h3 zN?IGkq+Y;15q4C!>QWcuai7~VKE|s+3i0=`#<#&Ox#Jhw;r|TGMNN3qa<%RDH)*Ba zuF5R_oWx+A{2L2?=T^htKT#Y6pvrotONm9WIQ+)NNDD?9&{A-s^7!RHSMi+)^JxNk32WfT}Sv!nogqH z!$~m$O(dCQxh2h{TEP;9=u4r|zt4IW&_+|y16lP{UldPry^jX~6?CYP180P4LGw2;PEmi#s521E=2$=Wa?#dxVK>};~esDyz%$tS15_umpl8TDbDx}wV zes#N6>_LS}WP46$#*k55s1}Exh^@8cP`%61qj0lOW8((kJ_`hbHgji_O*!D!A11{Yifl$?ccG$ERO9*=_`qM)S`2PkRx zdY->n6{{FB6i5)wd$SOc2CN1J(z@Pa~*s*3Na+vw|Pac>99RN_6>lcZ-56~5qj z_|uTP{6U}nzb=||&uuPHGa#++)!4-0%xhrrV}nTn34$42-&wZ!A^Nlg!64CauQ@K* zLE`Af!H-G;-u-$tU|t1G02L?XHGg>Wv}Qu=Yi}S2vP*Ib*ysh^+P$n>yIp05kd}ah z0#__I&Lj`^Xn1ZPUzwWUUJFVC1d?7!13n^vkz1M{wX@rAWG(r3Iu1#tSJ;66uuB9_ zx`=@IA5GIqH6-Y&SmLJ&bL>XfM2ppSbRP>N)X`u4_8&2!iH@IObP0_%D+=g0l`K#O z`vwLIfI+Di7P_m0Y``>y{c~`l^a}?m>@(FE!P1Yn1Uvfir^f<&%n*j@mdRBqXDSy% zSW2^6Detb7%Lvl=(ivZr$ikI>^Bo2VaG(%JU*o1gL&)5kap6?2m}3tt9ZC4v7%%q( zjPm7CT1b6CS;NKAwmt&fntD5BXh{39$M)+;!uE(+K-yJ?yq+^03gOK>Ot4>m3I}l^ zjCppE@=x7L%RB$+#bi>kMN&~LPi&IQ1>P>w^3@DvjoWBbjd9S;9yjx0k;-It=SmkI zjBI6mPzdrB^h!kd38-tBM{v&IHg6Ks9&D)mQow}(bKv3F1PJ1e^E=?C1aFy>SGW4h zme6W^BamIpA5Mn90s!(BC0WuAaQo355Z}JKDH}cfJaLKD8m4Hx%_u zZMXJYP#Ke9Q!IRNZ>tCwzwWJSEys~r%ECf}+ZT2B0VL|x>FyX(1^gAz_WfT{=7BHK z2Slh=g38WNE-b4d%cH$_F<+6FM2fX<|3324*$<-NPLtpBE;4f25S$meYLtC;M(@;G zdv1LrkMw{9#fnNZ$9GzAt03f~)8TAG+@=nq6ahy7^hlW&78Z5b=6lrWvv7uzCSF%| z+kEM4)$E~y`!%_HB0(c8*TfdeClXsOK>$2xLbyLVeDCCQ^Ae8j|nhPe+* z;F}Z(YqW+0IfS2F!D2=Km&d7WnL@w4@&G>F&!~(qh1bJ*U=smlshu#OS+~3JYfc)hgTHmi*C+4|Y{yP1O4@uW`?%3gT4HztU7%R%Ka~2Vx+op5l$Z8j?kN9M!r1epb9+9UUj*eac{8cH(`cVzVbqe_Srf4x|4)!_Z`Z7_Pu0F6X5eU zuYFP7etocFm;I)QPk2DSh{av<{H?mbUQqq2&%G}1S?XKV+HJy`;|sBg5wDB6fm?%D zt=adnl{6ODHCwEJ9!XsHwlJKSF*l|lljbem21s;S-`|(f=`)l4S!xV2OL7bk)ugJH7l1bMpF%jkTgN@h03AJ{p$wvSq4}ue+E_uWx~p6NqXsJMAYS zif}*M_Xh3+CXZYL(u}VC`tscjZCdwr>W~`8BIT*{M^QG}2Z`15s6xj9btb3{O0Dvd z)Jp>kv_X=oZAOC?b?L^itEaS4!=?Hb@}%~Dd;@I`Xa;2uJAF?` zTOG!GA-e~{49>q&k?b)tW~ceuo`DDh$ODzYb2;WWG#cNGxXhENaGdrQ$15ZrE&vm6bKCqo-tx_r;&a{tgI_r5M1#&2V-RSkFD`360(G94_`DHrf$!E;J+(gC9u zqEW~xU{g?UJAvDatA0Xf#LuGJP7m4{%0i7IRei zz#T^gR~`Jpj3L!x?tETtaJe}G_x&QY1N%)|EjX#Emc)h8p&3&)nJcnt^fc{-j21Kg zD-vkBfp-HTE!gxPm%pgl&8t5j<~R;+K!B;)ht?}adEG;jzS4V0GvYxA`?;XNCl2Gu ziN1QAlWPVknZS>hnl6d_Xf`ea*;iE)=doV>36 z!=!D884u09$2%gLD?ShYqEAeq<8yUEZN4WgjQQ>A<cVx(IsYR;+Y<~)WJOXjLX&2bbcoP<;$js7qbdcEq8Uh&)2T>CZB-BnKVhF zC)A8L)@(+$YOF3>0?$I*&|Uf#`x}??9Q+&)-D{Drdn` zi20^Qp8#|w&kw_F*XtC#?h6es(mb6*_HK#bs?##tbiCmC8`_?er-pcq;Kpy?Hnf@3 z&=>5VN~Sv|#qfms431XNSgor`WmkHf*Eoh33>h{ryZYgbWRFPYjqI-O z;s2r{YcqSUTr6wJ5UPUv7D>M%PFoF@79@tT+p(+^ogE&}$($t!Yg^S`{s2XA*%kd3 zPhy*O`^G+XHuHznG{mf^Z%IdUlf&&AvDy{01_XT(8Dsb@@5{)U&0s)ywK1tn`%VK3#l2>!5kIJb5w}Y+CDV z_672e(?0^ZV_`WKm3R650n%`1`u~0iHC||daSYUS&)|I|NOytf`bM2mR@D?V+6vZ^)sL2b5nag)CKF zv3kb0 zAMsj?$QfjIXVBW7z-Oxa4lW*e)ziKE>R}>R|LQ=G-#O-NkCe!|4Tjue$Q!wMesJ8u zD|38P{*xun5;{;sa@_8>y5Fq&&Q_TNlpDTO<_GL>>qh7Z(&h7^X{EMLMH7!@VpYL7dE$6P7(bd^C=m=!QY~) zEJ8AYg0zknz?_5*-q(*mgFfEr4&X~xlAZ`Z;>SKR3+-;H|303-(uYfoOMp))f$Kl~ z(X-)8GI|TaqYKZa>azI`OWQj zdv!LaZ`FVLAa@FB@2Oda2Q1k6)rAUxYTN&5JOrB0cY&U)K*jDmgxXLdxycx$Be9Ju zNV|v&Aw+@J)rF{hE00cL&qK@;*8O<4^5QG_X#z;Zm#?>K6JG&36uE2ZX^g%fl zUb-A#cJ6yJ>H3iYat->eZm9{a{_5EyW{&JK%x{`E;y&_$nFS+-bw=CFi{@qR#7iPc zf8Qeer-YgvxZ!HWZTo+O`_zL#eD0qjRdmrqJ?%t#Ahm@A?fAF?3`kOEa*jX_MAgfJ z3BKK)_0k`yv)h9>3wL~O6s7RJAHRqhbDT_J>+~Wd8Ag*IlHuF}L1*vc5j8sF`AI#y zW9566a&u~+jb5W;I+&pE(u83q)5mm`*H^w`elwf@Y*MVFRc06s&fEXsuaFt0 znz;xqh~g>j=p7fz)WJ$(6{&ZemoxPjK0KhIA_-D`-SELPHnsM@u! zho)lwNMXR#qr056hQe;8Elua4OhEXdPjWFK9~t%Q%_sVU)2SHG_Oql%A3X$v+LP6) z+TtgB+HhiS85an`Oi%CW-u6j=UXhcPUWm?kSPoDiK_^=bQKGZRnVOpzif0Ik<$pTI?Te|o3U;DO!Mp#ab~T|sWKB9>h? z-?e9rSId<3N*2rGwJ&_FPib_JAKG(Pw&!ucC;%{C#qKix z2zK3!+YUFGGvE>g&`?pXfKfJt&rY6`|7(tl?L6r=d8JzXh{cosW!U{ro`xw=qoUGy z_s=Pz1UI)~_gCmk%VktEd3Xp|2-)My#}_={KtnkK!F$u+UTJVQ>-c%Gq3;=?S=g+@ zOMJFpv|~8D2Fx2kUo>0Y;p`a0pC+n1!JfJeF~%$feH`ex=PUZ3T; z9i^UoEsdPqcc;Agmq*o}F|_aNy;KLg9>@w90LVazN2#n{tV}nxU*bR6;K&Z-pPN@r zfRB@;AGrrWz~K(9_-gT@1XX+>w)6q&`!%XB5d&GeRx}~g0bDH@L$s>@0nrDgkwL_s zz&2p*3K|!}OTc(%Lq~>F{OhLyN)+A&#?uf_;T1Poj4hQM~v+lxOfIB7#3qop2BrEKw73EYQ7i#XOVs$VvD zf+n8rpg{iVZ_AvYNv)Ri5X3?J01uhK@E-AqRn7J5w|J7q2zGU=ePm*A3k`&Zvz4yY zr4JNu&M)Ugz&L{z&Kj2`bo8txIcu!V6ugB&5I_pdPWJucXKd#Y}AB8o~o8-4|#>FSE^v1KJ6I z*}@}MbP+*drXeh=z25c5|4FrnG0N&&wF4hwnZ zIt|sZ5{y?kjEkWUpfv(t1n6RkRDfRISJl{y!V$(NAYMiBp1e;`3EFu1g!mPSc~!1w z`Lt2oaw^mN#*>!EaOj~=d}b7n&`h?@?1uFKEpiw%rGSGbYZ7viIBE-b1j2x`UQX5H zfn~iAN5#ThV9v^Rso2GI5sgnn4}KWA*t5!Id5Dg70Au!1`W;a`m?D6rxxGeBii@2{h4s0{a@;b;g%|;sZbr&0H*kJhB6!H=ocAZhs8_XUVJI z?+;)l$t$%79g5;EO7y+_CNXuOOpY{FEs6fwC=l}`3L48RS|giYzb6| z+@Z&meqKAY%0;f%$RYn#Su_(RsLz=}ApQ{{X!2Ie9FbKso2T%o1=v1N#Ti>^v*5gG zY6BP56oFDvt$%|(e;PhQGI5Xdawf@fw>VdGk7IvN-kLE0H) z&e3m17YGx{Xpj@nk#KhmOiRXi)k2q1a?&H!k17r09r>fsfCV-=K{>a}_!}S9@`#u` zoMLplL3#oaj)@C;USHde&lNIq ztOXjzN{UCI)fwQS77d&Up}IXR4O;}>u`EV0jKYD8%Uh2{-n;ns$%J^yd$!?*%|g8k zI$X@(I%_IUugg!;3}dE8-B(H+f7Lqu5_*A-g<^yjOe2FzA1tb;lC>&b&LEU9YE`m0 zqHV!alJj<4o#sh$&gWeq=>|VzYww>CPiubN(Gi{7HqRH z(Yb3OKE^0Z(ch_6P|8%xX{zwk&201cjg6tPzy7Ni=7D{1g?)@wH85K~UrHWK&vB6= zaz;GbL2}mGFD$P`WF>(Kb(ij@^I<|jb6cfi_SzaH_AeO&Hrbj4$_yqWUsGd#nURp# z^~WNY%aV_3tiRSZqQ5)3F6U<{plZHWgIw&dr?o%}f={C(!R>sVYftSX`pLx+%sSOily4t zoi*t-a+I0NvP;r4i={%I_%*svGi~7Od7M|pUDlsGkj z#J_>UC)9=|Lt+M;5pvI@A8ADWT2d*BJFJ$!`pKBM0;2FGu2g)4nc z^tYCZ{f3rJB!Al_bdL3^6iOO?-rn5YgrNo7_`0~W)X#}9USN-4xjR`(J}zPVsHum0 zkap)Xr{eB^<)0Mj8;*A)Y>i}Pi5ltR?pwE&rNEW#UuTAY8eztMK1H^mdtXX(@>6*g zzNx!VSf6!1$LC&asn_rj*OZHFW97J2tclTskcoejMuUdjYe*7#5LX4Xsl8FXG9gs$ zF)szz4EHTo-Z?t9X`&Y{C94~KC@1!h#zq@$tH zZBtv*0Q1L+d}&f8Zw@wtg{D61W{#{hTMv&2CYo@NwN5Mc7XPQI6&cm-6*<4^q;0-G zw*D>01LfL0s(Uf`NyQ@O6Ek&xt{*mCrW_I~Il9DPZV37~Q$$jcE&p5OShJ=|QjpTG zOHAWI4oaMeSkPvv%XUXJ*ytsUwkojQ9S_Ns&2eHb33_(l^iQ=>SJddSo@AMr7LiKV z(x;L4ma+`7YzZ>&Fh7TLpo-EZB{2Fa^Qj0lT}7ag>;9Q&EP@ErD2CkGFPwD6uJ8HS zxQqek$MO7j8{RUas9(kz*?!JdFSUr^Mt|YsYO8p9ygPZlBuPHaGtBrT7IWi^Ag^4u z&IBuC9IDtHDk0`ZSl`{8wW1cDFFeLVA2x1XY>VP`t8&WZF2XA3t6}H~@pd4cj4$9t z9C)&y!Ts?-Xmik79k5qCTpV7KZIcT;nQjVb6{x#b>2w*-{iu}P*)f+oEkS6}xI1`P zPAo}+vGbo>4De&jBlD$WU5&S=>PS%0ymEsHT|nyU?6fdOYrY4u`MMJQDwXj z%TDA-=CAyf;ZC?hpaPDXVo#1-roUW4)TXo|zqXePAZg~Ph0p38YtCee%qX*#X_wbF zibZN&WSiDGCDTFU`3qJhdUNp=*SKDMdNPo-+@)U=#S~bJq2y^l*(Whi z7KiadOe^O$R}JE@((^@mMT&F;Rz*W~l9-Bt&-;aRFV>~baZ`R0bb@f>Ca+{l4^KVD>nAB|12b&lSmwzWVvt2q*Uy$N!xmc(7 zrWCF+dRDI_ZvziPk*2=Q9N&G)wdc0=lzRM_d(l z`2h-z&8jpP-u+fjNFNa?&A!)GW?UpanrUiF)zw6wojq1M#&*s4_RDsM%$in*VgI<& zqGS0F9_pGG)XP{?fHlcrU@%{tjs7+Ac~2OwJyCSIiu>Z1CHv1nodW;~ z)$!Z@%4X8~n0k3+DdS*{KPHmJi^z2tSNPA^Vp*N%*4Uq+w(x=ne|U@-7C9Kzl)IJ~ zinJ6yhLvk((#7osWmSgdjpRdR=~P^0KX6d!4pF>Z;N(ydz?w4S(mgS<$ks4b2R4Cx zW#;D<53@8Gp>k#6tmD48t88O;9)aklyj=AXRb8r~z301fP1#rcPf-JR))g0$USm&> zvjw*L4smoeO>D{g9~Zhz$_sy~`KR&VDiswhUlyq`y4mZCT%VBWKh^-t0z;$5?%DxyV5lyaxmH)T(~7w8o% zEs3<5R)llaUhf-QU7~a|GhO#|V%Hw(3%LjG=sjGSb&;)~er>wb%N*!-^ph06SnNuN zOzr`f$2V@5m`=mF<6cH%C#$huv>*TetTz_Bifx&{x}w!QtGj)igljQ@1RG>03S<>FnY?Tqu4bc3~k9Xz%qwYWGJaXs!A43A_MJ7XFRzCiu#{N!*Wj4M&#I-B@GkyO$`jv2Cmf7jZZ292d)fhAqG5jzO z*$B{TvxpyOx7<>RZ>BxKHNL!A7fFaSSFO3T4m|g(OZIk=sYK6A$CXgXMvr`29f-i~ z6o%Nwf65ZZx&@Bi93p4{l)a?gx-?^epOUJrGDm1iF?o|Fd? z4E&eF)X+$_!b$D(!S5GZ%$lIQAIn7x13V)Zt}V%zFuy#EG5R#&8PQ$TG6lgT$$}_j z&W!h(VYWz>^*}Kvr{3L~sleS;8k+RH5nW~vQ z31qKKwGQoMo!cADwr7Sq3z%LL$(s^6z5wgxglf_!GOMfG4tjtoBnKCi3 zhl6miX3LW_asIon-uYyqfSF@9^UfXaXVd)aWT%}cri_bN&OGeGBrp?WCHRBD<$1^b zL3I9Jp3_|rcujkIg!Nu#M2T+)ODFCIDX)=r z`{3$##^eSUB1elEtvnU@VF*UtYhg&@=t)@u}r@~seB{A(585pA1{|&&tp`;yx?@}D{%l6jfD?a8|#F9VW9_CZyyxt zptj!E^V%R*DvHjo`vIs=prMV1dBtLu$O{x0c3@6r}uMsLEP_U3`q z=|fjJ=bu$*dBg55pWT->88~|$d1yC)IPH~Uc`YAfb`;TQhN32SH)VjX(X!Lk(+=}w zXKe|#v%DYs^ax7|U~hJX6$rLXvpcbvqCd2!1kak4_>*-}tLN(-EC`BZBRdVVl!>+- z9cuy#+G$!4sMl+}@H}JD&_!S9%J|ODRf6;6M>{9|HNI1np~b5~f6aI$PajLmLyke2L~Rba_b}(%TN*UJGcevUgUFIOMsYc>K7{!5ms335Wo1%n7h{YXY$bJ zEshllQ?+>g$?wg}_2!7fpC030q~ravyot*vT(-NXnRg-jg;*K@ z+gT|*Is42DZRJ$`JWNw5W(=_{Fh~MZmN7GJljc^Ul8Owqb}_znm^`G3{;8So?F{A~ z*@N^84(|Ei^Hu04l5~Kug8*u1=EE?VPtG`K{X_}>q)MI1aq0BFzj6?g%QRl={;p%h zlW!TLVRXUx!PM;Z)atEq=62e`H1XeLDRwX)3vlu#PB^1--`~BM_tT;I^K&?=TV}0o zGiP(>kzO_6pdVR-A|VMdtdnhgjyQ) zR}aKgWTgLY20UcwFo|R^lKpi`8ZG|NS`~SM-94QI?Tsu0lWqHkUv6I!*6pvqR37CA zhR3L!XoPE`;Z!XtvtBKxc;ExH@_Vs`Yh}LOdV7XL+J0Sd2{}NS4#gT=7T_`3WVyVN z!`*DOE{Lv$qJ#r>cPlw&e=33yPUc~nShuxRd@xj-UUZB;4BuE;teq6FYF6fEM1MbC zO?2?s<)C~Q&enQou5&@>>;7ApGv_>1w2P2`gIO3yx1M-aV z!kLN#CI4_lrRk8kp(^D`4$+?+tn+!1_zRFT4%Ig_05!K6^Lx4l!bWLgS{0jp!-5u7 zisV1$9BIn*cUt@U4plV2m!q`Tl7^Ll;OKu`ab0V<3&{BdM7*Q;Gd>&(l)y=4>*3z+ zs8Sa?S&YnWgmB1UUMsW{I%^!yl{2%B3`(ho1dt#)b;z`Hf=8cg>fykMrxnYi*^+po ze^zgcawFvmUNOc~ zC*_(pl>5@)eF-Bc@dz0Es&VHF>JiUx*>qC93J(1@-|n~eS8SLn+u+16es1U8pg=BKq zbd8%-HLJ$Ft)+L{g%yDfQf6bL5(dQU7|2KoLT+_>C%rcPJ#IJ9q}_*rx(W1%F= zO6fZJJ0of$18--vQWYnDM*UoFU*+htG4s|rRJZj5`nq35`U9tOAW;)ZmVVn z;7b<4Co`Glwrn*-3zQM$ub2=oS=W|Z23TV-*(>rYtghX@5oE-s(?tU}nd7JtyVubB zP%j9lW@dVBdD`QFpPs0;-x~_`I}`-;ZC?hlI#+L=&-+W{1Dg^;7d(-mW#TEQSW2Y| z{>e5%ss@LEW~t0uZ)Dei$cHfU@0Z1UErI_y4S~6M%OQ;_sLXlb$sQwIIQT%pL@Afx zrz++lR%w6#5;fulC{4u`W#X9>Jj~x&-GiH1p>ZopF*XU?SjAfBt*19uu4d1rH5m{H zElano#Hc>jqO87Y;098$8as$jaN;ehNmF@4wM{CkL>~ua;SJBTeQRJsho3uVz9DYs zDW-}x-x-BX*PIRp^%R^iRrs08N|bg3O0bO-PZ8~Zf2y*(ueU8a$b7sZbyjInit9rc ztW=nm-y1ag&4}lLsnbwTN`s4GJ0qZyN|A>FgV3v$jrGZD%lb#%p8(-3kZ$|_MM}^f zVLP+@9aR)4FjjLh0;d6ZhVzXMgZ+>v&{#_@H!50t#01JxY z5kq)`c~_U%PqGS8b^z3$c+5S8{sk_k*Q}Kf-kRt}RRTr2;6N-Gml=yCtIAZUf|>ZG z-59nrc9@cG!NF6H0iu&lVvrbbs{~b$5Pl|OFwnBb4!3C@-D-Nn1DoDvJTraGE>q%5 z|DF1`&`bFTC?ueAx|jl>I-o#^@*)bAo})oAGkUAs>^#r5*&P`1DvYtfrhYI6oN66S z%8|ynmOWWJj+`&uGXk)2>x&Kst0TDZG2B>XAwCG4VF1_?sa2dC z+%r0F9#)wDMY6-)1tk!migqW$5a|dodmC^-3lklt20;y~PlbUe2Y&#%*ShkdclI3G-^tWCyqmCSk1ojQcC(OccL^2Sa`aEZZO5W|R<^7W7Iq z=9SDC|E$Cu)1!;B(elJD2!ByhO>SC*^o1?i8l6_SUBM4?4pf$A{|6_EW{k_IhmhoJ ziKo$psUNyZ`8_g)VG5cNqd&nU(YIcFP5U#Vmwo+;qnIsq_yUUXqxCIGnmBtCC*Md- zp^FWy^HzPYoSYBDb7RN21T&X4HBRNw7gp93K4l6Z7d~@Q*+W_c1GK@}Ylhu-u-$NwHeSmo2)HyBvuBpnZ~Ah| zp}**t6^4-7igUjxzdRm_0zgGZjyH$6=5I7b5d-e#z%otKF?DhGs~p54RihNlx*ehv zAjc&7ITT8;79dg*d+{qhBcl{szP-VlkeT@vzs>W45jZ)*;PcBuf$Mk0?6r)WkA*$u zdWD^&hBw?22Nl#rf*y5Bhs=DuNo0X37fO#0^aKbnAkS%HiI1|4w7fMFt(gbmB-Q(l zs`M}ylfr`u8DP91YC-^iARZJ;hiETKa00hbR6Kuf!ySm*qI|!hUjgcy?HEK{C2Lur z2u-UE2P~E-U0gAdGkhV6L262tsfxYvYsUgq?4(n@sst`^KwAQ_Za4nA>5Zlf&aa)W zoyok<>7KKh_kOu;z}jzfqwc$K_6_}`B9+~d^x2~a%t3geEruY5UJ*EF0Ke6s+RGN> zR2QQ`KqDa0szn~gR#oVGuG!X77p1n*K&m1lXP{UyseJ(fP{hFeFPN_P*D`bV9#kKc z0Riu9ogRi~@j$%-We;$4bM1`K&tp*<1y#gEc@+jiuk)kNy&5%fpQ;Fu5Fy4eB#7h% zMzKn;Tvy@k{x{(Bn}u(+m~cH)6N`!`jTA$#l>6NLjZ$OqgSVZ&kM+8}#mhF>3@QGw zPNqtNu@~=Q^Br?Z;4^nD1`m+gx*#(OGs&xkZVgoZ6+&Ow6^Hy=!CFSg5vD>9L_Q!L zc=xqQ5g?*M8la5pw47UuASa{2m)|}dG%t9~_%J9-6L=Jr4jAL$xnGP{u}*XOZ;!T; zv$!j5LlIKDP4T4H8rhYK;Ce+24?(%~>h65s=p*q|nsFS_{1PTqwTyK;`QQ#hB%M zU!EiENX?%O)ks0wQO`(4?)JGcnqp&g&_tR%KQLs3039p1vtR)FDa{K(4Du@CFfACv zmR^qPbY%{ewKPsfFaM)c_wRsXy9awIXRp~Xv$0s9hGS(=CryRz*X7~Lcvrs7ErPMf zNpRF5A)I#?R^w#7Hbe_j4T*MR?c8_xviRy#y=f>gn3r$y^CjAu1sp@=3ff%dr%j+vOdt!* z2GE1|i<5hFkooTJ;yr4LAa*b?XUnBn)xJUw%bOvq3Q7R;r@jCXZ5k)af3$=J79_ze ze-PEUQ*xe*%QN35PS%B)d1*=vnphHlf2y4CVnh_%{=6XnV<}7rA-L_4jPYLcV>D^H zH~{0H=FLHhps67QDqQ)QWLD&lA3*5PXWsIY_XEq5AO4=8M-13d~E%i4X zbKOfJI%N8Djro+Kp{V>FkfSU` zw*RMyxH_JT!GtmyA!Q7c;$uU4Bf%^|2ija}Hoe{HOxiGx5r zf{1(HnIpab=g+4*{+%X~K=0>SH^HKu-p)6h;jMiVi)H{R^locEfC{+WBY{-zTVCTM zP!3WgQ>Jv`hJ&`C{b!!V(Mg@lYeTRjeIb`2uJb_$1+tUmngCmxBd>~wqzB+5=Iy7^ zMJzSZhd}NC+n~&Ncfa%uUg?*N1xge|7V-eizHzP5ZyD{)Z`J3k4At}`;c_m4At43o zWJwnnB=PIr#_U9(b!d`N@b2M9g99HlYBE(-T(vwE4W041i7?HQ6WqG}F0ZC2&8b?> zupaX?6?@o>DRedE$L_HK=aW9B zL%>p3I!yT$U0h`6!awKL-)yf+@ z*^EHr%e~0UPO(Rp~@7PSe@o|dORP*>=wcRRVn$$Cxx`b2`+T%G<2h1?k1XjX@{Z@-sub)&SrW|yEt9ajQ;uIG|R|n8(Ztq zgYYbFM_|_W)P)p>y$j4{Qpth50WK2C-|dtOG!F4rW161K-rJ_JA^^-F8}*P*qt2$0 zYZ$e0Popc{JjjzJUKc2eWh(E^TzY%T-!si%XR9fD*lgIBHkbn{0KPvfl;p+Az__(7 zH1bx81kvWS7&E65H#`ZZ5XEhD9Ft@JonOlMJWlqFUY}4tmBv#<+OV`y3Kvb>Nj@}C z064a@w1~O*Lk5gEfI8%_vgUv%qt`VFNc$Zlp5IJ4-dvwZdPT;&a?BVf5co{I z^TV$zdeP6Frr$H##cxaB;>4G~^|yaDt$Fz^TS9bKo9s1k2Gk=Cgg>&*A3|=aZzLZJ zv6E{|+qK@yG!N>}N|Le4(43=UwhGI?0YIpL1msi57Eufcx~bVi#p^dLGxS^ef!SVj zmG|ba_~Y%2vv%Wq*DL_=!tT=-d=_;*k7RVJ1$rhrXsFXdwW#xBbUE=dS2Z&K7=pR` zfvjnTo26c#zPw3LR*4FI)DwG21kAV=u4JT$?z{%X3b_G_R6xMbwiPgSm}Iw?R+Kdl z>($V=x5akwJH4I3h}{E%*@-hkk?;h`TqmuafzbC}B0{O=4#H~lyEu^ms|W#-V$U*h z{fR{(h8?`=--@^$5OjDGs7fyQB0=;U6guVuy_)a6wne`xl-Z74X6^^)M3SxtMc~sa z4I=`Opu(v@kvv~tCJ%t#u>F3ae$ou86S1&MjhTrHJK%ZT>jgpQxO-(B4sqW93aIMJWwng84~x)+a4;T)ln zq@4El{YxqR0GwnsajFypi|10D>aWtiWGLm1^p1NfAZ0Stq|>(uDVw9Vy@bZ!s-x9i ztk6PvN!9NzczW#Za#RjI=yWlkR?+ChGR9k`8aT0Sw(_PQ0O(lxNT+t<`p4$R4f3(; zX9T?4A34r63kTuflK34p5k#=v_M<0hbs1?|v9Taix}VotuM@w6j_7L!7tBxJk^UZ2 z<7>ap6_ZWEw1Rp}W)Ju2v1}L!GYWom6yt{e?OmY24`(a*0;Y&&i$2QW7_EMpeC12U z{+G}Rcj0lJBLdR{drb0U7BKyjK%WO%wZDHgKQZp&j?$BbC zhm)&bc9O)={M&l>GjjVXtwON2NrnYxbwE#YWS5l7cc^zjtAqOKEvJP!R4THjE7p5| zM|HTy3NY-*u8i~0DbH8preN^(PTj@a{E^*%of{(@ z)dBmQs3tyf>b<|yv-4bn-szj^ia0a8Q&@SnEp+&|`LWsFPYO*n)anps!wTa^^=##@ zsHS-Ap~qXL_>EU_m*iG0;lE>ByXwUW;a#`(geelQ!5X5M3(aFu?F5j(AT5&; z{y8H1aTJgaEFVYziYo=;#Zxtq5~x?KQh*X{HMmR@Jy}b9IaEIPZZIlqBYAl0C3+Pl z^1YM=AfFr5MiHe~g{sqg2@aZ6iOgV(jK;hz zE)1<{z{!2-G%~{_hA)ijM8YeD3r~EbQBwS_wK2$X&UtXGbPG|;y~2^COq2O$Sj;}1)X|%D*9J8R7pS>yux*qHQT$Ud4jLb zr}d3E@G}ch0h5MrpFf~MpxFM7zM@Mq1_mCqin`r2jIX-bJEg+~xItO=<0ufF|4ZwV ze8Tq%F)W{^F}GhzT6HaQa}-I=umaJf&sjW|7GZPzLG=(k39*3;k=nk9+91+3KfHd>G?GQWxS{SjTwXDyxcZv354N+ z?k%W^F84caSzx~5xJ`OhvuMb0w;Fr%bgi2&6fX-7JTPx6N^z+$g|D+o@Q|ziw^Ztn^VS|Q$h?Kr4B9LD1ST!C0=}BE^F1NV}r1*z^ z3qx>A=?l5WDsuaRRe~*HA+y|O_dGih$OzC@%gxkII@&?K({K7Y{Pd2Ayl%2FTLM)= zo89ITBiR1HaQp4e-f8n)c*}*3hb`;6!zNBGJ&AF&(k6Sr& zFUsm|Mb%xP#6X9GPZPT3wgFd8pT|8O%K ziWMZFyPxr(cOOPP>HBQZ)Aode61QE+NaSR3dDy&{^qW<@m3IVtXf&jc3bdXvH3;>2F{pcymO#ku+;^267bDw*Gk8rW((^b{W zncsn0F6;BOG4s-26aLy8P69Q%mOc=C1vc4`^#+T(j zhoqGvk<%A(TXk#FBmc?(bPRN(%-Krvv+kBEcb{!eH^3iXk!NP#kT)~*{y>H%7vyhx zvo$L2H{P;np?1=o6~23k;c7Tw31rGPVCBnIZL{O?IHqme%jS&prB(%Cub;<>!^ulM z#nGPupBjv+w5b9sWN4@9+;PLr8-_uQE=XTsXWU2$3qqN$l@A4>U)m0?M4B@Zf@k-k z2hfSLXWuZ;w#GeKmhztGYP?aY@*$K6U|6_3pncM?ToNU>{MUR_<0~OJ6M<#&To3Yd z;;e$A06%N6zTmQ~O6DT2CzPe~!2-x;y1x_T!CuJ2y;@!@OllToA6#i((c zIo}#D(rjLu(;avMdqW;%?EtkVmD6lJ6~%zBsddljulcdMkWW&W-L3LJp>i~UAsv@m?tgr` z3k$_Q7*G#IJoE*egk9}BV_R7+$EEUo?qr%A0eL+|4QIv5cO~;Ftj*AZ4HSHtI)v^U zjoeUoQ6SpiSvr9+-E*TfH;J3Oso+wrc0XBmBIOXE9C%w;ONqWCo5dlsC%$25LTExH ziIX5yq+1}J9P}%+2sh>QR(RI8!GeSem5W&cE-!5b!LqF$w}Q5xpPBr6rRAi@&fzUJ zWWk}`jcetavg-E@x61)6xQhhW_C^>0$9TK(29O2-hIpJ!g2i|x^{Wbh#FWlVQy`do zdlwo_ha7<+Q}2n`*%$TrxAmoLDTXo@QN`F2zBhQ{kYGtSl0J0kI0NyM{}lz&KtxL;}J&DejHR$=qWR8A{L`9Wtd zh)Q7X_&M~ag0r@(;F9nVTe{(=GW~H^069E21RS1Cb_$fVJz+0PE5g-5Xb|`3al@T7>(UL?l8CyJ@Du6PihV$_^yg&k%9BQ z)bU1r?|F6rKn+Not<22#?EeTRs858o(OiFJ>&-~10bju<`b2sk8DtQ3MSBg@vR)C&Ua{6K0pa$ z72#s;Zj{^Z0V7#bNdYjXN9}qZr-cI=*gZ(mI z5A(X`?ntNtVN4UD@o?px10|weV>%dbBoy3H++|ruOyWUM(VwrP1J}J|f%eWp@I&vn zv@F!0EuT#SAS8W`*AyJ5H_27#so~gz(BS#6*Xt`*-yiD8juN;tDIMu5ZAx?tfFz2v zLxINA91q;nhT$vV838VU$oa7r$)>7?j?TEDu}c_bl?O|;=pju?M{zZ|M6N1Wsh4$e zO08P)4$t6XF^DnM~jn8 zs7(z1m@=i3ri(HL5g$s1y?m~My|694`QrxRVCtJ$gMrQ8uNqJfDoOJtx`FD2Ua8}K z>RL0M16P<3a>WgZ_(?D+e@59tn~8%on% zdowq}n6yZ~-`Avz-43Am`daH#Nn3N|Z9phbEkKSsV0zn%Wk2}U)=6xZi!;XEdM>>M&x-L% z8CB_GKVX7*N|-ud5PEV|>T23xt-DNiWiWl`<5Q_GGw7S&=_1oPJ7Y85^mFKg=j{{C zTM7`o^YPtH`Qvg~D5r$?tMX=Jg6s(X0qY<3tDG+M)Y%rJQP17e2$g?GDb3X~&)l-P zQT*_%=8@NpyLdkqjvF?)Q5OQZ)khN^-6yGzwOgJ?p2!gLG2Q?c;{9I(acumHrtijH z*C4+xr~ZyGI$I@9(4tDgXGI#Yr-;dA+Tm;bwOS(X`>C3T(Rkju;&OSO>fOCCLC+h8 zev}h|bM`~&hQ62E6W4N;y|BWG&MiT2I^Jq&K)*%M}#@CzTq$Lw76KbaF>O3gJ#-r?$w*q zbrYp%)^5&Ht>#f#uMtKX8O*(R+064&mM^T6{dE43HW?3WaG#yXYs=|g^nX`2Ppx+u z8WMHFl6#}7RX_QHBR)p4=P#p+Hd7@JItWoXq!vw@2|aZwHp^BX`tK!j7$%b@Z^?BR z_9^SjXewQr^4(+1JAO%mrL#&5{!53ew9k3i(?WthZTl39&1xy{7oK7y(4TZ8BkYG$ zazX<=8}7jSh#t!)6&hMHD#hNANix5l!<1GAbBo`1aS#_RJ=VmLaN90u$=m2>{`CPxBR~>K z*I>s^Iip+^Gi%N@>BknBE>?rQ15c+MS3iCJD}m>)N|112W~0IUaWNq}Eg@07JRep^ z$ZjOnb6J+4@AZ6Se*4&!#~o<-D%@7)7_aGCE_Dgm)p(BmXd^(EciTW=x0v=}0m*S~ zE6cyF_=WsFJeK2nLVC@LW~2IGO3H8W{Q+o&2W0`}pUNTUJAo+&Mhj`e-)DxwrU zk+H54mYSlDnFWfIN*eMiR|U1zFZ{@d%cf8<}N|@+_n#s7Dlp z3b-M(JTBj`YnYNL25MISJPO_3o_u}m_t1Mkq;rDQ*<@qV1xv19M_nTFMcitMi2T%a zaBF!3j=m3$Y`E{-#|Z_2JCf;l(|5$wlIz|tT^~2J*D>*nNUi+2p?{=ds^OtCYf0kg zQ?_J3HpU)!&F?M5hb?G3NYUIzry;x}IZtYykLs$2^_hr@tCxsY!Ng|gVKXza-SHl# z15)2BOrBo$UXhkuQbl1*m{jPL!J{=_tj1n$b(_$=8C}qeWB1nEDc5Zs95y>{z^=r5 zB-ioQGf&s}w*Q=O^140{p&qCRW+LGSV(=3c)1`1Ict&rfXla<3_Wtn;AjvLd{B1<` zx$)%?8VP(iGpl3$dpDnq>nkua*=3uNMld(^C8SnTGk2yu&Bs~jxHwP1yy z%PxI!X`s7*DrY*{>B$m&YmSPblc9>b^{5c5pKrY}HqP(B zXcRB_9wVtu!H=gsa?AP8V~5URQ@g9DI~6Xo(6autgeVA!M53awm3RENI{t0*K@zgk#T`sQm6YPy(}(0h_c z!}b-K39eP2`)}f;Qsf6Ir+up}Ari)~?Bi=dJ$%Ge9n7=mb{K2h4Y zn8mG-*{7-IvI>))5aKDMa(|T1Xh=y-)C>!&Qmb&YzVkeWVmb=3b0|44tz*MnR!u5D zF&srbWvpEP=)U9S>J1o~Zx$3Lrd+?KP(B&qZ6EK-i~iLvzfY*PzJ#l7>a&BGbb6D` zqOWLJ6W*=qtDcaG3G(#f|BR4uqLaI~#113$=vVZ&sb7R70HF<4br}vw;v5~NK z8cIjOcd{$1!4Zz8V<<6=ml>DNRNfCmz1-BwEJzQ0$A40Zsd3OJ%UrXsIccR{>UVdT zKF*^xK0)8e+da8cz({8$dB)C>BVu}7&W^H!A#1FXgH!TN7+Qv2qP=YNM@w!swcH^#{A-SX5KJ6VpaOH+(xK6H2l%6g8AqP~(? zxU@o$K!mFhsezN}T^QMJRO(lQwU}gG*W4CL5;y*5djNN1GDD6zZzl{P`R*sdVl^KN~znFGfstUTos_6AIxve>Gb z{+jRCvC}EEl%1@`z;3-wB@RuvvJI% zZ&u}y)%9V&p^u3k&EF5}aom&<+H%HX<3-9%kMPH2auTY0K`4~6mXND)Iee^(#)hwf z8i*!{N;HDUH+@OWFthNE!P6h>Y?zG6b3;qASWC-y{Gvj?vEdv$(pLUeLu=1*$uH6K zsB)g)Q{j!oNEB7^`rYl8%C+kLmmg4u>FFb%TOS#^b)cFp|5Uq_A*v2lj5t?L%JuaO z1d4o-O)Z-V%CJlCN>~1~g9Nki7|V*OTH`37l83joq_xLGmy+LWjvSr@VJ281ExAQy ziXiu>jZfGWfq!z8)sxr%H#^{O6ZJ7rU|KGFQYLLMR5`nCOu~ALe16}bTt`+fiJB98 zv!E%#@_eE%$9pMKLz{qq?eL2kr?RS6+wkQ&fxKW*+}8P}5!LO;yTdZ3O@z%(J0IyF zYPWZC^r`4Z-Nm>sIVabZ&%%oofO~}o6={GB^`|(GE(9?Z` z79Fliz`{*>f^4A zJ0C0Ji?qV1qyv@dEiI*UmA2e8g&*{%XecibIq@0Ba#*O2b<1TM*_0Q=eRuM-ppS9U zu&%RZeO|PVD<5G;1kmYc*3-5ee+z%dyLz{swBARgDapZJ;z{Qk&*o==}Lqj&6gb#k{ zJL$hNyvq?2PD|zk^wBaVOT4A08Skrfu?>}?18Jk;r^TdU5As>XUrt591{br{?QkKIS+0appQY>QYMvsUD$@3)(mRg5uDFPE33eCym}_J= zDT<`Ry#?`mVp17t^dsg3i?Ap(wCjp&H;9y#wd8||v9a;Z&bGF;Td|Rmw;3rZDeq}W zNJw12D3tLnF|lSn>;L_->sS4MFS~x#|Mjx6y}A!x^A8DDSzKz5|AO8=z C163FR literal 0 HcmV?d00001 diff --git a/lms/static/images/university/georgetown/georgetown-cover_2025x550.jpg b/lms/static/images/university/georgetown/georgetown-cover_2025x550.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e2ac736fd117bb78cbd574c57d26e631420719d5 GIT binary patch literal 317367 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 z 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%0DZJ-8VXg(2KKkYoH0r%ediqK+C1{(o$|@Y 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 Y6kR|%kGj ## EdX Blog - 2012-10-14T14:08:12-07:00 + 2012-12-10T14:00:12-07:00 + + tag:www.edx.org,2012:Post/9 + 2012-12-10T14:00:00-07:00 + 2012-12-10T14:00:00-07:00 + + Georgetown University joins edX + <img src="${static.url('images/press/releases/georgetown-seal_240x180.png')}" /> + <p>Sixth institution to join global movement in year one</p> + tag:www.edx.org,2012:Post/8 2012-12-04T14:00:00-07:00 diff --git a/lms/templates/index.html b/lms/templates/index.html index 9175de1737..ca15eeae81 100644 --- a/lms/templates/index.html +++ b/lms/templates/index.html @@ -95,7 +95,7 @@
  • - +
    GeorgetownX diff --git a/lms/templates/static_templates/faq.html b/lms/templates/static_templates/faq.html index acd00bafe8..96e781e817 100644 --- a/lms/templates/static_templates/faq.html +++ b/lms/templates/static_templates/faq.html @@ -12,83 +12,81 @@ Press Contact + +

    Organization

    -

    What is edX?

    -

    edX is a not-for-profit enterprise of its founding partners, the Massachusetts Institute of Technology (MIT) and Harvard University that offers online learning to on-campus students and to millions of people around the world. To do so, edX is building an open-source online learning platform and hosts an online web portal at www.edx.org for online education.

    -

    EdX currently offers HarvardX, MITx and BerkeleyX classes online for free. Beginning in Summer 2013, edX will also offer UTx (University of Texas) classes online for free. The University of Texas System includes nine universities and six health institutions. The edX institutions aim to extend their collective reach to build a global community of online students. Along with offering online courses, the three universities undertake research on how students learn and how technology can transform learning – both on-campus and online throughout the world.

    +

    What is edX?

    +

    edX is a not-for-profit enterprise of its founding partners, the Massachusetts Institute of Technology (MIT) and Harvard University that offers online learning to on-campus students and to millions of people around the world. To do so, edX is building an open-source online learning platform and hosts an online web portal at www.edx.org for online education.

    +

    EdX currently offers HarvardX, MITx and BerkeleyX classes online for free. Beginning in fall 2013, edX will offer WellesleyX and GeorgetownX classes online for free. The University of Texas System includes nine universities and six health institutions. The edX institutions aim to extend their collective reach to build a global community of online students. Along with offering online courses, the three universities undertake research on how students learn and how technology can transform learning – both on-campus and online throughout the world.

    -

    Why is Wellesley College joining edX?

    -

    Wellesley College brings a long history, nearly 150 years, of providing liberal arts courses of the highest quality. WellesleyX courses, and the creativity and innovation of the Wellesley faculty, will provide a new perspective from which the hundreds of thousands of edX learners can benefit.

    -

    Wellesley’s unique, highly personalized, discussion-based learning experience and its commitment to providing pedagogical innovation will mesh with ongoing research into how students learn and how technology can transform learning both on-campus and online.

    -

    As with all consortium members, the values of Wellesley are aligned with those of edX. Wellesley and edX are both committed to expanding access to education to learners of all ages, means, and backgrounds. Both institutions are also committed to the non-profit model.

    +

    Why is Georgetown University joining edX?

    +

    Georgetown University, the oldest Catholic and Jesuit university in America, has a long history of providing courses of the highest quality through its schools of foreign service, law, medicine, nursing, business, as well as the arts and sciences. GeorgetownX courses, and the mission-driven Georgetown faculty, will provide a new perspective from which the hundreds of thousands of edX learners can benefit.

    +

    Georgetown offers a world-class learning experience focused on educating the whole person through exposure to different faiths, cultures and beliefs. Georgetown's global perspective with presences in Qatar, Shanghai, Santiago, Buenos Aires and London aligns with edX's mission to extend access to education around the world and to perform research into how students learn and how technology can transform learning both on-campus and online.

    +

    As with all consortium members, the values of Georgetown are aligned with those of edX. Georgetown and edX are both committed to expanding access to education to learners of all ages, means, and backgrounds. Both institutions are also committed to the non-profit model. We value principle not profit.

    -

    Wellesley is the first women’s college to offer courses through a massive open online course (MOOC) platform. What does this mean for the world of online learning?

    -

    Wellesley is currently the only women’s college that has announced plans to offer courses through a massive open online course (MOOC) platform. Wellesley’s commitment to educating women to be leaders in their fields, their communities, and the world provides a unique opportunity for edX learners who come from virtually every nation around the world. Women who have had limited access to education, regardless of where they live, will have access to the best courses, taught by the best faculty, from the best women’s college in the world. The potential for a life-changing educational experience for women has never been as great.

    -
    -
    -

    How many WellesleyX courses will be offered initially? When?

    -

    Initially, WellesleyX will begin offering edX courses in the fall of 2013. The courses, which will offer students the opportunity to explore classic liberal arts and sciences as well as other subjects, will be of the same high quality and rigor as those offered on the Wellesley campus.

    +

    How many GeorgetownX courses will be offered initially? When?

    +

    Initially, GeorgetownX will begin offering edX courses in the fall of 2013. The courses, which will offer students the opportunity to explore a variety of subjects, will be of the same high quality and rigor as those offered on the Georgetown University campus.

    Will edX be adding additional X Universities?

    -

    More than 200 institutions from around the world have expressed interest in collaborating with edX since Harvard and MIT announced its creation in May. EdX is focused above all on quality and developing the best not-for-profit model for online education. In addition to providing online courses on the edX platform, the “X University” Consortium will be a forum in which members can share experiences around online learning. Harvard, MIT, UC Berkeley, the University of Texas system and the other consortium members will work collaboratively to establish the “X University” Consortium, whose membership will expand to include additional “X Universities”. Each member of the consortium will offer courses on the edX platform as an “X University.” The gathering of many universities’ educational content together on one site will enable learners worldwide to access the offered course content of any participating university from a single website, and to use a set of online educational tools shared by all participating universities.

    -

    edX will actively explore the addition of other institutions from around the world to the edX platform, and looks forward to adding more “X Universities.”

    +

    More than 200 institutions from around the world have expressed interest in collaborating with edX since Harvard and MIT announced its creation in May. EdX is focused above all on quality and developing the best not-for-profit model for online education. In addition to providing online courses on the edX platform, the "X University" Consortium will be a forum in which members can share experiences around online learning. Harvard, MIT, UC Berkeley, the University of Texas system and the other consortium members will work collaboratively to establish the "X University" Consortium, whose membership will expand to include additional "X Universities". Each member of the consortium will offer courses on the edX platform as an "X University." The gathering of many universities' educational content together on one site will enable learners worldwide to access the offered course content of any participating university from a single website, and to use a set of online educational tools shared by all participating universities.

    +

    edX will actively explore the addition of other institutions from around the world to the edX platform, and looks forward to adding more "X Universities."

    Students

    -

    Who can take edX courses? Will there be an admissions process?

    -

    EdX will be available to anyone in the world with an internet connection, and in general, there will not be an admissions process.

    +

    Who can take edX courses? Will there be an admissions process?

    +

    EdX will be available to anyone in the world with an internet connection, and in general, there will not be an admissions process.

    -

    Will certificates be awarded?

    -

    Yes. Online learners who demonstrate mastery of subjects can earn a certificate of completion. Certificates will be issued by edX under the name of the underlying "X University" from where the course originated, i.e. HarvardX, MITx or BerkeleyX. For the courses in Fall 2012, those certificates will be free. There is a plan to charge a modest fee for certificates in the future.

    +

    Will certificates be awarded?

    +

    Yes. Online learners who demonstrate mastery of subjects can earn a certificate of completion. Certificates will be issued by edX under the name of the underlying "X University" from where the course originated, i.e. HarvardX, MITx or BerkeleyX. For the courses in Fall 2012, those certificates will be free. There is a plan to charge a modest fee for certificates in the future.

    -

    What will the scope of the online courses be? How many? Which faculty?

    -

    Our goal is to offer a wide variety of courses across disciplines. There are currently seven courses offered for Fall 2012.

    +

    What will the scope of the online courses be? How many? Which faculty?

    +

    Our goal is to offer a wide variety of courses across disciplines. There are currently nine courses offered for Fall 2012.

    -

    Who is the learner? Domestic or international? Age range?

    -

    Improving teaching and learning for students on our campuses is one of our primary goals. Beyond that, we don’t have a target group of potential learners, as the goal is to make these courses available to anyone in the world – from any demographic – who has interest in advancing their own knowledge. The only requirement is to have a computer with an internet connection. More than 150,000 students from over 160 countries registered for MITx's first course, 6.002x: Circuits and Electronics. The age range of students certified in this course was from 14 to 74 years-old.

    +

    Who is the learner? Domestic or international? Age range?

    +

    Improving teaching and learning for students on our campuses is one of our primary goals. Beyond that, we don't have a target group of potential learners, as the goal is to make these courses available to anyone in the world - from any demographic - who has interest in advancing their own knowledge. The only requirement is to have a computer with an internet connection. More than 150,000 students from over 160 countries registered for MITx's first course, 6.002x: Circuits and Electronics. The age range of students certified in this course was from 14 to 74 years-old.

    -

    Will participating universities’ standards apply to all courses offered on the edX platform?

    -

    Yes: the reach changes exponentially, but the rigor remains the same.

    +

    Will participating universities' standards apply to all courses offered on the edX platform?

    +

    Yes: the reach changes exponentially, but the rigor remains the same.

    -

    How do you intend to test whether this approach is improving learning?

    -

    Edx institutions have assembled faculty members who will collect and analyze data to assess results and the impact edX is having on learning.

    +

    How do you intend to test whether this approach is improving learning?

    +

    Edx institutions have assembled faculty members who will collect and analyze data to assess results and the impact edX is having on learning.

    -

    How may I apply to study with edX?

    -

    Simply complete the online signup form. Enrolling will create your unique student record in the edX database, allow you to register for classes, and to receive a certificate on successful completion.

    +

    How may I apply to study with edX?

    +

    Simply complete the online signup form. Enrolling will create your unique student record in the edX database, allow you to register for classes, and to receive a certificate on successful completion.

    -

    How may another university participate in edX?

    -

    If you are from a university interested in discussing edX, please email university@edx.org

    +

    How may another university participate in edX?

    +

    If you are from a university interested in discussing edX, please email university@edx.org

    Technology Platform

    -

    What technology will edX use?

    -

    The edX open-source online learning platform will feature interactive learning designed specifically for the web. Features will include: self-paced learning, online discussion groups, wiki-based collaborative learning, assessment of learning as a student progresses through a course, and online laboratories and other interactive learning tools. The platform will also serve as a laboratory from which data will be gathered to better understand how students learn. Because it is open source, the platform will be continuously improved by a worldwide community of collaborators, with new features added as needs arise.

    -

    The first version of the technology was used in the first MITx course, 6.002x Circuits and Electronics, which launched in Spring, 2012.

    +

    What technology will edX use?

    +

    The edX open-source online learning platform will feature interactive learning designed specifically for the web. Features will include: self-paced learning, online discussion groups, wiki-based collaborative learning, assessment of learning as a student progresses through a course, and online laboratories and other interactive learning tools. The platform will also serve as a laboratory from which data will be gathered to better understand how students learn. Because it is open source, the platform will be continuously improved by a worldwide community of collaborators, with new features added as needs arise.

    +

    The first version of the technology was used in the first MITx course, 6.002x Circuits and Electronics, which launched in Spring, 2012.

    -

    How is this different from what other universities are doing online?

    -

    EdX is a not-for-profit enterprise built upon the shared educational missions of its founding partners, Harvard University and MIT. The edX platform will be available as open source. Also, a primary goal of edX is to improve teaching and learning on campus by experimenting with blended models of learning and by supporting faculty in conducting significant research on how students learn.

    +

    How is this different from what other universities are doing online?

    +

    EdX is a not-for-profit enterprise built upon the shared educational missions of its founding partners, Harvard University and MIT. The edX platform will be available as open source. Also, a primary goal of edX is to improve teaching and learning on campus by experimenting with blended models of learning and by supporting faculty in conducting significant research on how students learn.

    @@ -96,7 +94,6 @@ @@ -104,5 +101,5 @@
    %if user.is_authenticated(): - <%include file="../signup_modal.html" /> +<%include file="../signup_modal.html" /> %endif diff --git a/lms/templates/static_templates/press_releases/Georgetown_joins_edX.html b/lms/templates/static_templates/press_releases/Georgetown_joins_edX.html new file mode 100644 index 0000000000..310a4ced5e --- /dev/null +++ b/lms/templates/static_templates/press_releases/Georgetown_joins_edX.html @@ -0,0 +1,73 @@ +<%! from django.core.urlresolvers import reverse %> +<%inherit file="../../main.html" /> + +<%namespace name='static' file='../../static_content.html'/> + +<%block name="title">Georgetown University joins edX +
    + + +
    + +
    +

    Georgetown University joins edX

    +
    +
    +

    Georgetown becomes sixth institution to join global movement in year one, Broadens course options and brings its unique mission-driven perspective to the world of online learning

    + +

    CAMBRIDGE, MA — December 10, 2012 — EdX, the not-for-profit online learning initiative founded by Harvard University and the Massachusetts Institute of Technology (MIT), announced today the addition of Georgetown University to its group of educational leaders who are focused on providing a category-leading, quality higher education experience to the global online community.

    + +

    “It is a privilege to partner with edX and this extraordinary collection of universities,” said Dr. John J. DeGioia, President of Georgetown University. “Our Catholic and Jesuit identity compels us to work at the frontiers of excellence in higher education, and we see in this partnership an exciting opportunity to more fully realize this mission. Not only will it enrich our capacity to serve our global family–beyond our campuses here in Washington, D.C.–but it will also allow us to extend the applications of our research and our scholarship.”

    + +

    Georgetown University, the nation’s oldest Catholic and Jesuit university, is one of the world’s leading academic and research institutions, offering a unique educational experience that prepares the next generation of global citizens to lead and make a difference in the world. Students receive a world-class learning experience focused on educating the whole person through exposure to different faiths, cultures and beliefs. Georgetown University will provide a series of GeorgetownX courses to the open source platform and broaden the course offerings available on edx.org.

    + +

    “We welcome Georgetown University to edX,” said Anant Agarwal, President of edX. “Georgetown has a long history of research and educational excellence, with a demonstrated commitment to the arts and sciences, foreign service, law, medicine, public policy, business, and nursing and health studies. Georgetown, with its distinguished presence around the world including a School of Foreign Service campus in Qatar, shares with edX a global perspective and a mission to expand educational opportunities.”

    + +

    Through edX, the “X Universities” will provide interactive education wherever there is access to the Internet. They will enhance teaching and learning through research about how students learn, and how technologies and game-like experiences can facilitate effective teaching both on-campus and online. The University of California, Berkeley joined edX in July, the University of Texas System joined in October, and Wellesley College joined earlier in December.

    + +

    “Georgetown University is an excellent addition to edX,” said MIT President L. Rafael Reif. “It brings important strength in many areas of scholarship and has long had an especially powerful voice in public life and discourse. The edX community stands to benefit greatly from what Georgetown will offer.”

    + +

    “EdX is an innovation that will expand access to high-quality educational content for millions around the world while helping us better understand how technology can improve the academic experience for students in classrooms across our campuses,” said Harvard President Drew Faust. “Georgetown’s commitment to technology enhanced learning, its excellence in education, and its long history as an institution dedicated to public service make it a welcome addition to edX.”

    + +

    GeorgetownX will offer courses on edX beginning in the fall of 2013. All of the courses will be hosted from edX’s innovative platform at www.edx.org.

    + +

    About edX

    + +

    edX is a not-for-profit enterprise of its founding partners Harvard University and the Massachusetts Institute of Technology that features learning designed specifically for interactive study via the web. Based on a long history of collaboration and their shared educational missions the founders are creating a new online-learning experience. Anant Agarwal, former Director of MIT’s Computer Science and Artificial Intelligence Laboratory, serves as the first president of edX. Along with offering online courses, the institutions will use edX to research how students learn and how technology can transform learning-both on-campus and worldwide. EdX is based in Cambridge, Massachusetts and is governed by MIT and Harvard.

    + +

    About Georgetown University

    + +

    Georgetown University is the oldest Catholic and Jesuit university in America, founded in 1789 by Archbishop John Carroll. Georgetown today is a major student-centered, international, research university offering respected undergraduate, graduate and professional programs from its home in Washington, D.C. For more information about Georgetown University, visit www.georgetown.edu.

    + +
    +

    Contact: Brad Baker

    +

    BBaker@webershandwick.com

    +

    617-520-7043

    +
    +
    + + +
    +
    +
    diff --git a/lms/templates/university_profile/georgetownx.html b/lms/templates/university_profile/georgetownx.html new file mode 100644 index 0000000000..a519746c4c --- /dev/null +++ b/lms/templates/university_profile/georgetownx.html @@ -0,0 +1,24 @@ +<%inherit file="base.html" /> +<%namespace name='static' file='../static_content.html'/> + +<%block name="title">GeorgetownX + +<%block name="university_header"> + + + + +<%block name="university_description"> +

    Georgetown University, the nation’s oldest Catholic and Jesuit university, is one of the world’s leading academic and research institutions, offering a unique educational experience that prepares the next generation of global citizens to lead and make a difference in the world.  Students receive a world-class learning experience focused on educating the whole person through exposure to different faiths, cultures and beliefs.

    + + +${parent.body()} diff --git a/lms/urls.py b/lms/urls.py index a3c61a2687..9ef40f32cd 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -62,6 +62,7 @@ urlpatterns = ('', url(r'^university_profile/UTx$', 'courseware.views.static_university_profile', name="static_university_profile", kwargs={'org_id':'UTx'}), url(r'^university_profile/WellesleyX$', 'courseware.views.static_university_profile', name="static_university_profile", kwargs={'org_id':'WellesleyX'}), + url(r'^university_profile/GeorgetownX$', 'courseware.views.static_university_profile', name="static_university_profile", kwargs={'org_id':'GeorgetownX'}), url(r'^university_profile/(?P[^/]+)$', 'courseware.views.university_profile', name="university_profile"), #Semi-static views (these need to be rendered and have the login bar, but don't change) @@ -106,10 +107,12 @@ urlpatterns = ('', {'template': 'press_releases/Gates_Foundation_announcement.html'}, name="press/gates-foundation-announcement"), url(r'^press/wellesley-college-joins-edx$', 'static_template_view.views.render', {'template': 'press_releases/Wellesley_College_joins_edX.html'}, name="press/wellesley-college-joins-edx"), + url(r'^press/georgetown-joins-edx$', 'static_template_view.views.render', + {'template': 'press_releases/Georgetown_joins_edX.html'}, name="press/georgetown-joins-edx"), # Should this always update to point to the latest press release? - (r'^pressrelease$', 'django.views.generic.simple.redirect_to', {'url': '/press/wellesley-college-joins-edx'}), + (r'^pressrelease$', 'django.views.generic.simple.redirect_to', {'url': '/press/georgetown-joins-edx'}), (r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/static/images/favicon.ico'}), From e3b4d78d875016d10578f3ac14fb2668b6dca9f4 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 7 Dec 2012 16:03:05 -0500 Subject: [PATCH 095/234] IE7 compatibility: no trailing commas in arrays, including the third party json2.js lib because IE<8 has no JSON object. --- common/lib/xmodule/xmodule/js/src/capa/schematic.js | 10 +++++----- lms/envs/common.py | 1 + lms/static/js/form.ext.js | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/capa/schematic.js b/common/lib/xmodule/xmodule/js/src/capa/schematic.js index b033dbaf46..bebe6b1854 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/schematic.js +++ b/common/lib/xmodule/xmodule/js/src/capa/schematic.js @@ -1953,7 +1953,7 @@ cktsim = (function() { var module = { 'Circuit': Circuit, 'parse_number': parse_number, - 'parse_source': parse_source, + 'parse_source': parse_source } return module; }()); @@ -2068,7 +2068,7 @@ schematic = (function() { 'n': [NFet, 'NFet'], 'p': [PFet, 'PFet'], 's': [Probe, 'Voltage Probe'], - 'a': [Ammeter, 'Current Probe'], + 'a': [Ammeter, 'Current Probe'] }; // global clipboard @@ -5502,7 +5502,7 @@ schematic = (function() { 'magenta' : 'rgb(255,64,255)', 'yellow': 'rgb(255,255,64)', 'black': 'rgb(0,0,0)', - 'x-axis': undefined, + 'x-axis': undefined }; function Probe(x,y,rotation,color,offset) { @@ -6100,7 +6100,7 @@ schematic = (function() { 'Amplitude', 'Frequency (Hz)', 'Delay until sin starts (secs)', - 'Phase offset (degrees)'], + 'Phase offset (degrees)'] } // build property editor div @@ -6300,7 +6300,7 @@ schematic = (function() { var module = { 'Schematic': Schematic, - 'component_slider': component_slider, + 'component_slider': component_slider } return module; }()); diff --git a/lms/envs/common.py b/lms/envs/common.py index 79d0bb78f9..1b1be28ead 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -404,6 +404,7 @@ courseware_only_js += [ ] main_vendor_js = [ + 'js/vendor/json2.js', 'js/vendor/jquery.min.js', 'js/vendor/jquery-ui.min.js', 'js/vendor/jquery.cookie.js', diff --git a/lms/static/js/form.ext.js b/lms/static/js/form.ext.js index db98f42ddb..c64872a1e9 100644 --- a/lms/static/js/form.ext.js +++ b/lms/static/js/form.ext.js @@ -28,7 +28,7 @@ CSRFProtection: function(xhr) { var token = $.cookie('csrftoken'); if (token) xhr.setRequestHeader('X-CSRFToken', token); - }, + } } $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { form_ext.CSRFProtection(xhr); }}); $(document).delegate('form', 'submit', function(e) { From 48b9043213093473452ff3243f3fa2354c8b99d4 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 5 Dec 2012 13:08:22 -0500 Subject: [PATCH 096/234] Initial problem list functionality --- .../src/staff_grading/staff_grading.coffee | 93 +++++++++++++------ 1 file changed, 64 insertions(+), 29 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index f4831e35c4..629efdfd50 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -36,19 +36,20 @@ class StaffGradingBackend else if cmd == 'save_grade' console.log("eval: #{data.score} pts, Feedback: #{data.feedback}") response = - @mock('get_next', {}) - # get_probblem_list + @mock('get_next', {location}) + # get_problem_list # sends in a course_id and a grader_id # should get back a list of problem_ids, problem_names, num_left, num_total else if cmd == 'get_problem_list' - response = - success: true - problem_list: [ - {location: 'i4x://MITx/3.091x/problem/open_ended_demo', \ - problem_name: "Problem 1", num_left: 3, num_total: 5}, - {location: 'i4x://MITx/3.091x/problem/open_ended_demo', \ - problem_name: "Problem 2", num_left: 1, num_total: 5} - ] + @mock_cnt++ + response = + success: true + problem_list: [ + {location: 'i4x://MITx/3.091x/problem/open_ended_demo', \ + problem_name: "Problem 1", num_left: 3, num_total: 5}, + {location: 'i4x://MITx/3.091x/problem/open_ended_demo', \ + problem_name: "Problem 2", num_left: 1, num_total: 5} + ] else response = success: false @@ -79,8 +80,13 @@ class StaffGradingBackend class StaffGrading constructor: (backend) -> @backend = backend + @list_view = true # all the jquery selectors + + @problem_list_container = $('.problem-list-container') + @problem_list = $('.problem-list') + @error_container = $('.error-container') @message_container = $('.message-container') @@ -108,17 +114,19 @@ class StaffGrading @message = '' @max_score = 0 @ml_error_info= '' + @location = '' @score = null + @problems = null # action handlers @submit_button.click @submit # render intial state - @render_view() + #@render_view() # send initial request automatically - @get_next_submission() + @get_problem_list() setup_score_selection: => @@ -153,7 +161,9 @@ class StaffGrading @message = '' if response.success - if response.submission + if response.problem_list + @problems = response.problem_list + else if response.submission @data_loaded(response.prompt, response.submission, response.rubric, response.submission_id, response.max_score, response.ml_error_info) else @no_more(response.message) @@ -162,8 +172,13 @@ class StaffGrading @render_view() - get_next_submission: () -> - @backend.post('get_next', {}, @ajax_callback) + get_next_submission: (location) -> + @location = location + @list_view = false + @backend.post('get_next', {location}, @ajax_callback) + + get_problem_list: () -> + @backend.post('get_problem_list', {}, @ajax_callback) submit_and_get_next: () -> data = @@ -202,14 +217,41 @@ class StaffGrading @state = state_no_data render_view: () -> - # make the view elements match the state. Idempotent. - show_grading_elements = false - show_submit_button = true - + # clear the problem list + @problem_list.html('') @message_container.html(@message) + # only show the grading elements when we are not in list view or the state + # is invalid + show_grading_elements = !(@list_view || @state == state_error || + @state == state_no_data) + @prompt_wrapper.toggle(show_grading_elements) + @submission_wrapper.toggle(show_grading_elements) + @rubric_wrapper.toggle(show_grading_elements) + @ml_error_info_container.toggle(show_grading_elements) + @submit_button.hide() + if @backend.mock_backend @message_container.append("

    NOTE: Mocking backend.

    ") - + if @list_view + @render_list() + else + @render_problem() + + problem_link:(problem) -> + link = $('').attr('href', "javascript:void(0)").append( + "#{problem.problem_name} (#{problem.num_left} / #{problem.num_total})") + .click => + @get_next_submission problem.location + + + render_list: () -> + for problem in @problems + @problem_list.append($('
  • ').append(@problem_link(problem))) + + render_problem: () -> + # make the view elements match the state. Idempotent. + show_submit_button = true + @error_container.html(@error_msg) if @state == state_error @@ -220,7 +262,6 @@ class StaffGrading @prompt_container.html(@prompt) @submission_container.html(@submission) @rubric_container.html(@rubric) - show_grading_elements = true # no submit button until user picks grade. show_submit_button = false @@ -228,7 +269,6 @@ class StaffGrading @setup_score_selection() else if @state == state_graded - show_grading_elements = true @set_button_text('Submit') else if @state == state_no_data @@ -239,21 +279,16 @@ class StaffGrading @error('System got into invalid state ' + @state) @submit_button.toggle(show_submit_button) - @prompt_wrapper.toggle(show_grading_elements) - @submission_wrapper.toggle(show_grading_elements) - @rubric_wrapper.toggle(show_grading_elements) - @ml_error_info_container.toggle(show_grading_elements) - submit: (event) => event.preventDefault() if @state == state_error - @get_next_submission() + @get_next_submission(@location) else if @state == state_graded @submit_and_get_next() else if @state == state_no_data - @get_next_submission() + @get_next_submission(@location) else @error('System got into invalid state for submission: ' + @state) From 94e1a9ffd3cb465a23dbe757619ca565e8a756bd Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 5 Dec 2012 14:38:53 -0500 Subject: [PATCH 097/234] Clean up some minor issues with list view and display some new information on the problem-specific page --- .../src/staff_grading/staff_grading.coffee | 18 +++++++++++++++--- lms/templates/instructor/staff_grading.html | 7 +++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 629efdfd50..1e0f89228c 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -80,7 +80,6 @@ class StaffGradingBackend class StaffGrading constructor: (backend) -> @backend = backend - @list_view = true # all the jquery selectors @@ -90,6 +89,7 @@ class StaffGrading @error_container = $('.error-container') @message_container = $('.message-container') + @prompt_name_container = $('.prompt-name') @prompt_container = $('.prompt-container') @prompt_wrapper = $('.prompt-wrapper') @@ -115,6 +115,9 @@ class StaffGrading @max_score = 0 @ml_error_info= '' @location = '' + @prompt_name = '' + @num_total = 0 + @num_left = 0 @score = null @problems = null @@ -164,7 +167,7 @@ class StaffGrading if response.problem_list @problems = response.problem_list else if response.submission - @data_loaded(response.prompt, response.submission, response.rubric, response.submission_id, response.max_score, response.ml_error_info) + @data_loaded(response.prompt, response.submission, response.rubric, response.submission_id, response.max_score, response.ml_error_info, response.problem_name, response.num_left, response.num_total) else @no_more(response.message) else @@ -178,6 +181,7 @@ class StaffGrading @backend.post('get_next', {location}, @ajax_callback) get_problem_list: () -> + @list_view = true @backend.post('get_problem_list', {}, @ajax_callback) submit_and_get_next: () -> @@ -192,7 +196,7 @@ class StaffGrading @error_msg = msg @state = state_error - data_loaded: (prompt, submission, rubric, submission_id, max_score, ml_error_info) -> + data_loaded: (prompt, submission, rubric, submission_id, max_score, ml_error_info, prompt_name, num_left, num_total) -> @prompt = prompt @submission = submission @rubric = rubric @@ -201,12 +205,18 @@ class StaffGrading @max_score = max_score @score = null @ml_error_info=ml_error_info + @prompt_name = prompt_name + @num_left = num_left + @num_total = num_total @state = state_grading if not @max_score? @error("No max score specified for submission.") no_more: (message) -> @prompt = null + @prompt_name = '' + @num_left = 0 + @num_total = 0 @submission = null @rubric = null @ml_error_info = null @@ -219,6 +229,7 @@ class StaffGrading render_view: () -> # clear the problem list @problem_list.html('') + @problem_list_container.toggle(@list_view) @message_container.html(@message) # only show the grading elements when we are not in list view or the state # is invalid @@ -260,6 +271,7 @@ class StaffGrading else if @state == state_grading @ml_error_info_container.html(@ml_error_info) @prompt_container.html(@prompt) + @prompt_name_container.html("#{@prompt_name} (#{@num_left} / #{@num_total})") @submission_container.html(@submission) @rubric_container.html(@rubric) diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index a44ef68831..0666b3d7ec 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -19,6 +19,8 @@

    Staff grading

    +
    @@ -27,8 +29,13 @@
    +
    +
      +
    +
    +

    Question prompt

    From 5914b1cf916939e4b1a5f4502a06b3aaf60beba7 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 5 Dec 2012 16:15:49 -0500 Subject: [PATCH 098/234] Make the mock backend support different problems, and make sure the location is passed around properly. Clarify some of the numbers for the user --- .../src/staff_grading/staff_grading.coffee | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 1e0f89228c..57366842d1 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -21,22 +21,40 @@ class StaffGradingBackend # should take a location as an argument if cmd == 'get_next' @mock_cnt++ - response = - success: true - problem_name: 'Problem 1' - num_left: 3 - num_total: 5 - prompt: 'This is a fake prompt' - submission: 'submission! ' + @mock_cnt - rubric: 'A rubric! ' + @mock_cnt - submission_id: @mock_cnt - max_score: 2 + @mock_cnt % 3 - ml_error_info : 'ML accuracy info: ' + @mock_cnt + switch data.location + when 'i4x://MITx/3.091x/problem/open_ended_demo1' + response = + success: true + problem_name: 'Problem 1' + num_left: 3 + num_total: 5 + prompt: 'This is a fake prompt' + submission: 'submission! ' + @mock_cnt + rubric: 'A rubric! ' + @mock_cnt + submission_id: @mock_cnt + max_score: 2 + @mock_cnt % 3 + ml_error_info : 'ML accuracy info: ' + @mock_cnt + when 'i4x://MITx/3.091x/problem/open_ended_demo2' + response = + success: true + problem_name: 'Problem 2' + num_left: 2 + num_total: 5 + prompt: 'This is a fake second problem' + submission: 'This is the best submission ever! ' + @mock_cnt + rubric: 'I am a rubric for grading things! ' + @mock_cnt + submission_id: @mock_cnt + max_score: 2 + @mock_cnt % 3 + ml_error_info : 'ML accuracy info: ' + @mock_cnt + else + response = + success: false + else if cmd == 'save_grade' console.log("eval: #{data.score} pts, Feedback: #{data.feedback}") response = - @mock('get_next', {location}) + @mock('get_next', {location: data.location}) # get_problem_list # sends in a course_id and a grader_id # should get back a list of problem_ids, problem_names, num_left, num_total @@ -45,9 +63,9 @@ class StaffGradingBackend response = success: true problem_list: [ - {location: 'i4x://MITx/3.091x/problem/open_ended_demo', \ + {location: 'i4x://MITx/3.091x/problem/open_ended_demo1', \ problem_name: "Problem 1", num_left: 3, num_total: 5}, - {location: 'i4x://MITx/3.091x/problem/open_ended_demo', \ + {location: 'i4x://MITx/3.091x/problem/open_ended_demo2', \ problem_name: "Problem 2", num_left: 1, num_total: 5} ] else @@ -178,7 +196,7 @@ class StaffGrading get_next_submission: (location) -> @location = location @list_view = false - @backend.post('get_next', {location}, @ajax_callback) + @backend.post('get_next', {location: location}, @ajax_callback) get_problem_list: () -> @list_view = true @@ -189,6 +207,7 @@ class StaffGrading score: @score feedback: @feedback_area.val() submission_id: @submission_id + location: @location @backend.post('save_grade', data, @ajax_callback) @@ -250,7 +269,7 @@ class StaffGrading problem_link:(problem) -> link = $('
    ').attr('href', "javascript:void(0)").append( - "#{problem.problem_name} (#{problem.num_left} / #{problem.num_total})") + "#{problem.problem_name} (#{problem.num_left} left out of #{problem.num_total})") .click => @get_next_submission problem.location @@ -271,7 +290,7 @@ class StaffGrading else if @state == state_grading @ml_error_info_container.html(@ml_error_info) @prompt_container.html(@prompt) - @prompt_name_container.html("#{@prompt_name} (#{@num_left} / #{@num_total})") + @prompt_name_container.html("#{@prompt_name} (#{@num_left} left out of #{@num_total})") @submission_container.html(@submission) @rubric_container.html(@rubric) From b5f3386d275e2cef1dd3431a9c02076f2a3fbd04 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 5 Dec 2012 16:38:54 -0500 Subject: [PATCH 099/234] Add breadcrumbing and clarify some of the text on the instructor grading interface. --- .../coffee/src/staff_grading/staff_grading.coffee | 13 +++++++++++-- lms/templates/instructor/staff_grading.html | 6 +++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 57366842d1..38daad61dc 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -59,7 +59,7 @@ class StaffGradingBackend # sends in a course_id and a grader_id # should get back a list of problem_ids, problem_names, num_left, num_total else if cmd == 'get_problem_list' - @mock_cnt++ + @mock_cnt = 1 response = success: true problem_list: [ @@ -121,6 +121,8 @@ class StaffGrading @score_selection_container = $('.score-selection-container') @submit_button = $('.submit-button') @ml_error_info_container = $('.ml-error-info-container') + + @breadcrumbs = $('.breadcrumbs') # model state @state = state_no_data @@ -246,8 +248,9 @@ class StaffGrading @state = state_no_data render_view: () -> - # clear the problem list + # clear the problem list and breadcrumbs @problem_list.html('') + @breadcrumbs.html('') @problem_list_container.toggle(@list_view) @message_container.html(@message) # only show the grading elements when we are not in list view or the state @@ -283,6 +286,12 @@ class StaffGrading show_submit_button = true @error_container.html(@error_msg) + problem_list_link = $('').attr('href', 'javascript:void(0);') + .append("Problem List") + .click => @get_problem_list() + + # set up the breadcrumbing + @breadcrumbs.append(problem_list_link).append(" > #{@prompt_name}") if @state == state_error @set_button_text('Try loading again') diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index 0666b3d7ec..0a3bc0dd82 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -36,19 +36,19 @@

    -

    Question prompt

    +

    Question

    -

    Submission

    +

    Student Submission

    -

    Rubric

    +

    Grading Rubric

    From fcc1ab71f6028eace4dfdd761bf24bf13f517539 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 6 Dec 2012 10:44:03 -0500 Subject: [PATCH 100/234] Update the formatting and layout of the Instructor Grading interface. --- .../src/staff_grading/staff_grading.coffee | 56 +++++++++++++++---- lms/templates/instructor/staff_grading.html | 34 ++++++----- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 38daad61dc..a70c33fdff 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -28,9 +28,27 @@ class StaffGradingBackend problem_name: 'Problem 1' num_left: 3 num_total: 5 - prompt: 'This is a fake prompt' - submission: 'submission! ' + @mock_cnt - rubric: 'A rubric! ' + @mock_cnt + prompt: ''' +

    S11E3: Metal Bands

    +

    Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.

    + +

    * Why is it that both sodium and magnesium behave as metals, even though the s-band of magnesium is filled?

    +

    This is a self-assessed open response question. Please use as much space as you need in the box below to answer the question.

    + ''' + submission: ''' + Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. + +The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham. + ''' + rubric: ''' +
      +
    • Metals tend to be good electronic conductors, meaning that they have a large number of electrons which are able to access empty (mobile) energy states within the material.
    • +
    • Sodium has a half-filled s-band, so there are a number of empty states immediately above the highest occupied energy levels within the band.
    • +
    • Magnesium has a full s-band, but the the s-band and p-band overlap in magnesium. Thus are still a large number of available energy states immediately above the s-band highest occupied energy level.
    • +
    + +

    Please score your response according to how many of the above components you identified:

    + ''' submission_id: @mock_cnt max_score: 2 + @mock_cnt % 3 ml_error_info : 'ML accuracy info: ' + @mock_cnt @@ -116,6 +134,7 @@ class StaffGrading @rubric_container = $('.rubric-container') @rubric_wrapper = $('.rubric-wrapper') + @grading_wrapper = $('.grading-wrapper') @feedback_area = $('.feedback-area') @score_selection_container = $('.score-selection-container') @@ -176,6 +195,7 @@ class StaffGrading graded_callback: (event) => @score = event.target.value @state = state_graded + @message = '' @render_view() ajax_callback: (response) => @@ -247,12 +267,23 @@ class StaffGrading @max_score = 0 @state = state_no_data + hide_if_empty: (container,message) -> + if message == '' + container.hide() + else + container.html(message) + render_view: () -> # clear the problem list and breadcrumbs @problem_list.html('') @breadcrumbs.html('') @problem_list_container.toggle(@list_view) - @message_container.html(@message) + if @backend.mock_backend + @message = @message + "

    NOTE: Mocking backend.

    " + @hide_if_empty(@message_container, @message) + @hide_if_empty(@error_container, @error_msg) + + # only show the grading elements when we are not in list view or the state # is invalid show_grading_elements = !(@list_view || @state == state_error || @@ -260,11 +291,10 @@ class StaffGrading @prompt_wrapper.toggle(show_grading_elements) @submission_wrapper.toggle(show_grading_elements) @rubric_wrapper.toggle(show_grading_elements) + @grading_wrapper.toggle(show_grading_elements) @ml_error_info_container.toggle(show_grading_elements) @submit_button.hide() - if @backend.mock_backend - @message_container.append("

    NOTE: Mocking backend.

    ") if @list_view @render_list() else @@ -276,6 +306,12 @@ class StaffGrading .click => @get_next_submission problem.location + make_paragraphs: (text) -> + paragraph_split = text.split("\n") + new_text = '' + for paragraph in paragraph_split + new_text += "

    #{paragraph}

    " + return new_text render_list: () -> for problem in @problems @@ -285,13 +321,13 @@ class StaffGrading # make the view elements match the state. Idempotent. show_submit_button = true - @error_container.html(@error_msg) problem_list_link = $('
    ').attr('href', 'javascript:void(0);') - .append("Problem List") + .append("< Back to problem list") .click => @get_problem_list() # set up the breadcrumbing - @breadcrumbs.append(problem_list_link).append(" > #{@prompt_name}") + @breadcrumbs.append(problem_list_link) + if @state == state_error @set_button_text('Try loading again') @@ -300,7 +336,7 @@ class StaffGrading @ml_error_info_container.html(@ml_error_info) @prompt_container.html(@prompt) @prompt_name_container.html("#{@prompt_name} (#{@num_left} left out of #{@num_total})") - @submission_container.html(@submission) + @submission_container.html(@make_paragraphs(@submission)) @rubric_container.html(@rubric) # no submit button until user picks grade. diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index 0a3bc0dd82..3c687c643f 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -18,49 +18,52 @@

    Staff grading

    -
    -
    - +
    -
    +
    -
    +

    +

    Question

    -
    - -
    -

    Student Submission

    -
    -
    - -
    +

    Grading Rubric

    +
    +
    +

    Student Submission

    +
    +
    +
    +
    + + +
    +

    Grading

    + +
    -

    -
    @@ -68,4 +71,5 @@
    +
    From ffa42b6f115914c4cdb740727f1944d57d550813 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 6 Dec 2012 10:45:48 -0500 Subject: [PATCH 101/234] Styles for the new instructor grading interface --- lms/static/sass/course/_staff_grading.scss | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lms/static/sass/course/_staff_grading.scss b/lms/static/sass/course/_staff_grading.scss index 4900f78bd0..935a93ee96 100644 --- a/lms/static/sass/course/_staff_grading.scss +++ b/lms/static/sass/course/_staff_grading.scss @@ -26,4 +26,39 @@ div.staff-grading { input[name='score-selection'] { display: none; } + + .prompt-information-container, + .submission-wrapper, + .rubric-wrapper, + .grading-container + { + border: 1px solid gray; + padding: 15px; + } + .error-container + { + background-color: $error-red; + } + .ml-error-info-container, + { + background-color: #eee; + padding:15px; + margin-left:0px; + } + .message-container + { + background-color: $yellow; + padding: 10px; + margin-left:0px; + } + + .breadcrumbs + { + margin-top:20px; + margin-left:0px; + margin-bottom:5px; + font-size: .8em; + } + + padding: 40px; } From ef82ced2af3f8852e1af71b61bd00f1bd9867059 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 6 Dec 2012 13:24:20 -0500 Subject: [PATCH 102/234] Improve the paragraph maker on the staff grading page --- lms/static/coffee/src/staff_grading/staff_grading.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index a70c33fdff..3bdefb455c 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -307,7 +307,7 @@ class StaffGrading @get_next_submission problem.location make_paragraphs: (text) -> - paragraph_split = text.split("\n") + paragraph_split = text.split(/\n\s*\n/) new_text = '' for paragraph in paragraph_split new_text += "

    #{paragraph}

    " From aee1e5e9a92cff16861a89bbaedc165208c32abe Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 6 Dec 2012 14:29:45 -0500 Subject: [PATCH 103/234] Updates to the list view and error container --- lms/static/sass/course/_staff_grading.scss | 12 +++++++++++- lms/templates/instructor/staff_grading.html | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lms/static/sass/course/_staff_grading.scss b/lms/static/sass/course/_staff_grading.scss index 935a93ee96..1122129b9d 100644 --- a/lms/static/sass/course/_staff_grading.scss +++ b/lms/static/sass/course/_staff_grading.scss @@ -27,6 +27,14 @@ div.staff-grading { display: none; } + ul + { + li + { + margin: 16px 0px; + } + } + .prompt-information-container, .submission-wrapper, .rubric-wrapper, @@ -37,7 +45,9 @@ div.staff-grading { } .error-container { - background-color: $error-red; + background-color: #FFCCCC; + padding: 15px; + margin-left: 0px; } .ml-error-info-container, { diff --git a/lms/templates/instructor/staff_grading.html b/lms/templates/instructor/staff_grading.html index 3c687c643f..63bc2fa098 100644 --- a/lms/templates/instructor/staff_grading.html +++ b/lms/templates/instructor/staff_grading.html @@ -28,6 +28,12 @@
    +

    Instructions

    +
    +

    This is the list of problems that current need to be graded in order to train the machine learning models. Each problem needs to be trained separately, and we have indicated the number of student submissions that need to be graded in order for a model to be generated. Any number of problems can be graded, not just the amount required to create the model.

    +
    + +

    Problem List

    @@ -52,6 +58,9 @@
    +
    + +

    Grading

    From 7c768cbbff78a6833b13a48eef98da742e8db01a Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 6 Dec 2012 14:31:02 -0500 Subject: [PATCH 104/234] Wire up the separate action button --- .../src/staff_grading/staff_grading.coffee | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index 3bdefb455c..db40356354 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -26,7 +26,7 @@ class StaffGradingBackend response = success: true problem_name: 'Problem 1' - num_left: 3 + num_graded: 3 num_total: 5 prompt: '''

    S11E3: Metal Bands

    @@ -56,7 +56,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t response = success: true problem_name: 'Problem 2' - num_left: 2 + num_graded: 2 num_total: 5 prompt: 'This is a fake second problem' submission: 'This is the best submission ever! ' + @mock_cnt @@ -75,16 +75,16 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t @mock('get_next', {location: data.location}) # get_problem_list # sends in a course_id and a grader_id - # should get back a list of problem_ids, problem_names, num_left, num_total + # should get back a list of problem_ids, problem_names, num_graded, num_total else if cmd == 'get_problem_list' @mock_cnt = 1 response = success: true problem_list: [ {location: 'i4x://MITx/3.091x/problem/open_ended_demo1', \ - problem_name: "Problem 1", num_left: 3, num_total: 5}, + problem_name: "Problem 1", num_graded: 3, num_total: 5}, {location: 'i4x://MITx/3.091x/problem/open_ended_demo2', \ - problem_name: "Problem 2", num_left: 1, num_total: 5} + problem_name: "Problem 2", num_graded: 1, num_total: 5} ] else response = @@ -97,7 +97,7 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t message: 'No more submissions' - if @mock_cnt % 7 == 0 + if @mock_cnt % 3 == 0 response = success: false error: 'An error for testing' @@ -139,6 +139,7 @@ class StaffGrading @feedback_area = $('.feedback-area') @score_selection_container = $('.score-selection-container') @submit_button = $('.submit-button') + @action_button = $('.action-button') @ml_error_info_container = $('.ml-error-info-container') @breadcrumbs = $('.breadcrumbs') @@ -156,16 +157,15 @@ class StaffGrading @location = '' @prompt_name = '' @num_total = 0 - @num_left = 0 + @num_graded = 0 @score = null @problems = null # action handlers @submit_button.click @submit - - # render intial state - #@render_view() + # TODO: fix this to do something more intelligent + @action_button.click @submit # send initial request automatically @get_problem_list() @@ -190,7 +190,7 @@ class StaffGrading set_button_text: (text) => - @submit_button.attr('value', text) + @action_button.attr('value', text) graded_callback: (event) => @score = event.target.value @@ -207,7 +207,7 @@ class StaffGrading if response.problem_list @problems = response.problem_list else if response.submission - @data_loaded(response.prompt, response.submission, response.rubric, response.submission_id, response.max_score, response.ml_error_info, response.problem_name, response.num_left, response.num_total) + @data_loaded(response.prompt, response.submission, response.rubric, response.submission_id, response.max_score, response.ml_error_info, response.problem_name, response.num_graded, response.num_total) else @no_more(response.message) else @@ -237,7 +237,7 @@ class StaffGrading @error_msg = msg @state = state_error - data_loaded: (prompt, submission, rubric, submission_id, max_score, ml_error_info, prompt_name, num_left, num_total) -> + data_loaded: (prompt, submission, rubric, submission_id, max_score, ml_error_info, prompt_name, num_graded, num_total) -> @prompt = prompt @submission = submission @rubric = rubric @@ -247,7 +247,7 @@ class StaffGrading @score = null @ml_error_info=ml_error_info @prompt_name = prompt_name - @num_left = num_left + @num_graded = num_graded @num_total = num_total @state = state_grading if not @max_score? @@ -256,7 +256,7 @@ class StaffGrading no_more: (message) -> @prompt = null @prompt_name = '' - @num_left = 0 + @num_graded = 0 @num_total = 0 @submission = null @rubric = null @@ -267,11 +267,6 @@ class StaffGrading @max_score = 0 @state = state_no_data - hide_if_empty: (container,message) -> - if message == '' - container.hide() - else - container.html(message) render_view: () -> # clear the problem list and breadcrumbs @@ -280,8 +275,10 @@ class StaffGrading @problem_list_container.toggle(@list_view) if @backend.mock_backend @message = @message + "

    NOTE: Mocking backend.

    " - @hide_if_empty(@message_container, @message) - @hide_if_empty(@error_container, @error_msg) + @message_container.html(@message) + @error_container.html(@error_msg) + @message_container.toggle(@message != "") + @error_container.toggle(@error_msg != "") # only show the grading elements when we are not in list view or the state @@ -293,7 +290,7 @@ class StaffGrading @rubric_wrapper.toggle(show_grading_elements) @grading_wrapper.toggle(show_grading_elements) @ml_error_info_container.toggle(show_grading_elements) - @submit_button.hide() + @action_button.hide() if @list_view @render_list() @@ -302,7 +299,7 @@ class StaffGrading problem_link:(problem) -> link = $('
    ').attr('href', "javascript:void(0)").append( - "#{problem.problem_name} (#{problem.num_left} left out of #{problem.num_total})") + "#{problem.problem_name} (#{problem.num_graded} graded out of #{problem.num_total})") .click => @get_next_submission problem.location @@ -320,6 +317,7 @@ class StaffGrading render_problem: () -> # make the view elements match the state. Idempotent. show_submit_button = true + show_action_button = true problem_list_link = $('').attr('href', 'javascript:void(0);') .append("< Back to problem list") @@ -331,21 +329,24 @@ class StaffGrading if @state == state_error @set_button_text('Try loading again') + show_action_button = true else if @state == state_grading @ml_error_info_container.html(@ml_error_info) @prompt_container.html(@prompt) - @prompt_name_container.html("#{@prompt_name} (#{@num_left} left out of #{@num_total})") + @prompt_name_container.html("#{@prompt_name} (#{@num_graded} completed out of #{@num_total})") @submission_container.html(@make_paragraphs(@submission)) @rubric_container.html(@rubric) # no submit button until user picks grade. show_submit_button = false + show_action_button = false @setup_score_selection() else if @state == state_graded @set_button_text('Submit') + show_action_button = false else if @state == state_no_data @message_container.html(@message) @@ -355,6 +356,7 @@ class StaffGrading @error('System got into invalid state ' + @state) @submit_button.toggle(show_submit_button) + @action_button.toggle(show_action_button) submit: (event) => event.preventDefault() From 275c9ef7fd3c5c30c07b5d9b76a97920eb3e68ea Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 6 Dec 2012 17:19:22 -0500 Subject: [PATCH 105/234] Update API with server and add new meta info box for meta information --- .../src/staff_grading/staff_grading.coffee | 56 +++++++++++-------- lms/templates/instructor/staff_grading.html | 11 +++- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/lms/static/coffee/src/staff_grading/staff_grading.coffee b/lms/static/coffee/src/staff_grading/staff_grading.coffee index db40356354..e5e8d93634 100644 --- a/lms/static/coffee/src/staff_grading/staff_grading.coffee +++ b/lms/static/coffee/src/staff_grading/staff_grading.coffee @@ -27,7 +27,8 @@ class StaffGradingBackend success: true problem_name: 'Problem 1' num_graded: 3 - num_total: 5 + min_for_ml: 5 + num_pending: 4 prompt: '''

    S11E3: Metal Bands

    Shown below are schematic band diagrams for two different metals. Both diagrams appear different, yet both of the elements are undisputably metallic in nature.

    @@ -57,7 +58,8 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t success: true problem_name: 'Problem 2' num_graded: 2 - num_total: 5 + min_for_ml: 5 + num_pending: 4 prompt: 'This is a fake second problem' submission: 'This is the best submission ever! ' + @mock_cnt rubric: 'I am a rubric for grading things! ' + @mock_cnt @@ -74,17 +76,16 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t response = @mock('get_next', {location: data.location}) # get_problem_list - # sends in a course_id and a grader_id - # should get back a list of problem_ids, problem_names, num_graded, num_total + # should get back a list of problem_ids, problem_names, num_graded, min_for_ml else if cmd == 'get_problem_list' @mock_cnt = 1 response = success: true problem_list: [ {location: 'i4x://MITx/3.091x/problem/open_ended_demo1', \ - problem_name: "Problem 1", num_graded: 3, num_total: 5}, + problem_name: "Problem 1", num_graded: 3, num_pending: 5, min_for_ml: 10}, {location: 'i4x://MITx/3.091x/problem/open_ended_demo2', \ - problem_name: "Problem 2", num_graded: 1, num_total: 5} + problem_name: "Problem 2", num_graded: 1, num_pending: 5, min_for_ml: 10} ] else response = @@ -140,6 +141,9 @@ class StaffGrading @score_selection_container = $('.score-selection-container') @submit_button = $('.submit-button') @action_button = $('.action-button') + + @problem_meta_info = $('.problem-meta-info-container') + @meta_info_wrapper = $('.meta-info-wrapper') @ml_error_info_container = $('.ml-error-info-container') @breadcrumbs = $('.breadcrumbs') @@ -156,8 +160,9 @@ class StaffGrading @ml_error_info= '' @location = '' @prompt_name = '' - @num_total = 0 + @min_for_ml = 0 @num_graded = 0 + @num_pending = 0 @score = null @problems = null @@ -207,7 +212,7 @@ class StaffGrading if response.problem_list @problems = response.problem_list else if response.submission - @data_loaded(response.prompt, response.submission, response.rubric, response.submission_id, response.max_score, response.ml_error_info, response.problem_name, response.num_graded, response.num_total) + @data_loaded(response) else @no_more(response.message) else @@ -237,18 +242,19 @@ class StaffGrading @error_msg = msg @state = state_error - data_loaded: (prompt, submission, rubric, submission_id, max_score, ml_error_info, prompt_name, num_graded, num_total) -> - @prompt = prompt - @submission = submission - @rubric = rubric - @submission_id = submission_id + data_loaded: (response) -> + @prompt = response.prompt + @submission = response.submission + @rubric = response.rubric + @submission_id = response.submission_id @feedback_area.val('') - @max_score = max_score + @max_score = response.max_score @score = null - @ml_error_info=ml_error_info - @prompt_name = prompt_name - @num_graded = num_graded - @num_total = num_total + @ml_error_info=response.ml_error_info + @prompt_name = response.problem_name + @num_graded = response.num_graded + @min_for_ml = response.min_for_ml + @num_pending = response.num_pending @state = state_grading if not @max_score? @error("No max score specified for submission.") @@ -257,7 +263,7 @@ class StaffGrading @prompt = null @prompt_name = '' @num_graded = 0 - @num_total = 0 + @min_for_ml = 0 @submission = null @rubric = null @ml_error_info = null @@ -289,7 +295,7 @@ class StaffGrading @submission_wrapper.toggle(show_grading_elements) @rubric_wrapper.toggle(show_grading_elements) @grading_wrapper.toggle(show_grading_elements) - @ml_error_info_container.toggle(show_grading_elements) + @meta_info_wrapper.toggle(show_grading_elements) @action_button.hide() if @list_view @@ -299,7 +305,7 @@ class StaffGrading problem_link:(problem) -> link = $('
    ').attr('href', "javascript:void(0)").append( - "#{problem.problem_name} (#{problem.num_graded} graded out of #{problem.num_total})") + "#{problem.problem_name} (#{problem.num_graded} graded, #{problem.num_pending} pending)") .click => @get_next_submission problem.location @@ -333,8 +339,14 @@ class StaffGrading else if @state == state_grading @ml_error_info_container.html(@ml_error_info) + meta_list = $("