""" A Self Assessment module that allows students to write open-ended responses, submit, then see a rubric and rate themselves. Persists student supplied hints, answers, and assessment judgment (currently only correct/incorrect). Parses xml definition file--see below for exact format. """ import copy from fs.errors import ResourceNotFoundError import itertools import json import logging from lxml import etree from lxml.html import rewrite_links from path import path import os import sys from pkg_resources import resource_string from .capa_module import only_one, ComplexEncoder from .editing_module import EditingDescriptor from .html_checker import check_html from progress import Progress from .stringify import stringify_children from .x_module import XModule from .xml_module import XmlDescriptor from xmodule.modulestore import Location log = logging.getLogger("mitx.courseware") # Set the default number of max attempts. Should be 1 for production # Set higher for debugging/testing # attempts specified in xml definition overrides this. MAX_ATTEMPTS = 1 # Set maximum available number of points. # Overriden by max_score specified in xml. MAX_SCORE = 1 class OpenEndedModule(): """ States: initial (prompt, textbox shown) | assessing (read-only textbox, rubric + assessment input shown) | request_hint (read-only textbox, read-only rubric and assessment, hint input box shown) | done (submitted msg, green checkmark, everything else read-only. If attempts < max, shows a reset button that goes back to initial state. Saves previous submissions too.) """ DEFAULT_QUEUE = 'open-ended' DEFAULT_MESSAGE_QUEUE = 'open-ended-message' response_tag = 'openendedresponse' allowed_inputfields = ['openendedinput'] max_inputfields = 1 STATE_VERSION = 1 # states INITIAL = 'initial' ASSESSING = 'assessing' POST_ASSESSMENT = 'post_assessment' DONE = 'done' def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): """ Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt, and two optional attributes: attempts, which should be an integer that defaults to 1. If it's > 1, the student will be able to re-submit after they see the rubric. max_score, which should be an integer that defaults to 1. It defines the maximum number of points a student can get. Assumed to be integer scale from 0 to max_score, with an interval of 1. Note: all the submissions are stored. Sample file: Insert prompt text here. (arbitrary html) Insert grading rubric here. (arbitrary html) Please enter a hint below: (arbitrary html) Thanks for submitting! (arbitrary html) """ # Load instance state if instance_state is not None: instance_state = json.loads(instance_state) else: instance_state = {} instance_state = self.convert_state_to_current_format(instance_state) # History is a list of tuples of (answer, score, hint), where hint may be # None for any element, and score and hint can be None for the last (current) # element. # Scores are on scale from 0 to max_score self.history = instance_state.get('history', []) self.state = instance_state.get('state', 'initial') self.attempts = instance_state.get('attempts', 0) self.max_attempts = int(instance_state.get('attempts', MAX_ATTEMPTS)) # Used for progress / grading. Currently get credit just for # completion (doesn't matter if you self-assessed correct/incorrect). self._max_score = int(instance_state.get('max_score', MAX_SCORE)) oeparam = definition['openendedparam'] prompt = definition['prompt'] rubric = definition['rubric'] self.url = definition.get('url', None) self.queue_name = definition.get('queuename', self.DEFAULT_QUEUE) self.message_queue_name = definition.get('message-queuename', self.DEFAULT_MESSAGE_QUEUE) #This is needed to attach feedback to specific responses later self.submission_id=None self.grader_id=None if oeparam is None: raise ValueError("No oeparam found in problem xml.") if prompt is None: raise ValueError("No prompt found in problem xml.") if rubric is None: raise ValueError("No rubric found in problem xml.") self._parse(oeparam, prompt, rubric) def handle_ajax(self, dispatch, get): ''' This is called by courseware.module_render, to handle an AJAX call. "get" is request.POST. Returns a json dictionary: { 'progress_changed' : True/False, 'progress' : 'none'/'in_progress'/'done', } ''' handlers = { 'problem_get': self.get_problem, 'problem_check': self.check_problem, 'problem_reset': self.reset_problem, 'problem_save': self.save_problem, 'problem_show': self.get_answer, 'score_update': self.update_score, 'message_post' : self.message_post, } if dispatch not in handlers: return 'Error' before = self.get_progress() d = handlers[dispatch](get) after = self.get_progress() d.update({ 'progress_changed': after != before, 'progress_status': Progress.to_js_status_str(after), }) return json.dumps(d, cls=ComplexEncoder)