<commit_before>"""
Add Self Assessment module so students can write essay, submit, then see a rubric and rate themselves.
Incredibly hacky solution to persist state and properly display information
"""

import copy
from fs.errors import ResourceNotFoundError
import logging
import os
import sys
from lxml import etree
from lxml.html import rewrite_links
from path import path
import json
from progress import Progress

from .x_module import XModule
from pkg_resources import resource_string
from .xml_module import XmlDescriptor, name_to_pathname
from .editing_module import EditingDescriptor
from .stringify import stringify_children
from .html_checker import check_html
from xmodule.modulestore import Location

from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent

log = logging.getLogger("mitx.courseware")

#Set the default number of max attempts.  Should be 1 for production
#Set higher for debugging/testing
max_attempts = 1

def only_one(lst, default="", process=lambda x: x):
    """
    If lst is empty, returns default
    If lst has a single element, applies process to that element and returns it
    Otherwise, raises an exeception
    """
    if len(lst) == 0:
        return default
    elif len(lst) == 1:
        return process(lst[0])
    else:
        raise Exception('Malformed XML')


class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, complex):
            return "{real:.7g}{imag:+.7g}*j".format(real=obj.real, imag=obj.imag)
        return json.JSONEncoder.default(self, obj)


class SelfAssessmentModule(XModule):
    js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'),
                     resource_string(__name__, 'js/src/collapsible.coffee'),
                     resource_string(__name__, 'js/src/selfassessment/display.coffee')
    ]
    }
    js_module_name = "SelfAssessment"

    def get_html(self):
        # cdodge: perform link substitutions for any references to course static content (e.g. images)
        return rewrite_links(self.html, self.rewrite_content_links)

    def __init__(self, system, location, definition, descriptor,
                 instance_state=None, shared_state=None, **kwargs):
        XModule.__init__(self, system, location, definition, descriptor,
            instance_state, shared_state, **kwargs)

        """
        Definition file should have 4 blocks -- problem, rubric, submitmessage, and maxattempts
        Sample file:

        <selfassessment>
            <problem>
                Insert problem text here.
            </problem>
            <rubric>
                Insert grading rubric here.
            </rubric>
            <submitmessage>
                Thanks for submitting!
            </submitmessage>
            <maxattempts>
            1
            </maxattempts>
        </selfassessment>
        """

        #Initialize variables
        self.answer = []
        self.score = 0
        self.top_score = 1
        self.attempts = 0
        self.correctness = "incorrect"
        self.done = False
        self.max_attempts = self.metadata.get('attempts', None)
        self.hint=""

        #Pull variables from instance state if available
        if self.max_attempts is not None:
            self.max_attempts = int(self.max_attempts)
        else:
            self.max_attempts = max_attempts

        if instance_state is not None:
            instance_state = json.loads(instance_state)
            log.debug(instance_state)

        if instance_state is not None and 'attempts' in instance_state:
            self.attempts = instance_state['attempts']

        if instance_state is not None and 'student_answers' in instance_state:
            if(type(instance_state['student_answers']) in [type(u''),type('')]):
                self.answer.append(instance_state['student_answers'])
            elif(type(instance_state['student_answers'])==type([])):
                self.answer = instance_state['student_answers']

        if instance_state is not None and 'done' in instance_state:
            self.done = instance_state['done']

        if instance_state is not None and 'hint' in instance_state:
            self.hint = instance_state['hint']

        if instance_state is not None and 'correct_map' in instance_state:
            if 'self_assess' in instance_state['correct_map']:
                self.score = instance_state['correct_map']['self_assess']['npoints']
                self.correctness = instance_state['correct_map']['self_assess']['correctness']

        #Parse definition file
        dom2 = etree.fromstring("<selfassessment>" + self.definition['data'] + "</selfassessment>")

        max_attempt_parsed=dom2.xpath('maxattempts')[0].text

        try:
            self.max_attempts=int(max_attempt_parsed)
        except:<commit_msg>add some documentation<commit_after>"""
Add Self Assessment module so students can write essay, submit, then see a rubric and rate themselves.
Incredibly hacky solution to persist state and properly display information
"""

import copy
from fs.errors import ResourceNotFoundError
import logging
import os
import sys
from lxml import etree
from lxml.html import rewrite_links
from path import path
import json
from progress import Progress

from .x_module import XModule
from pkg_resources import resource_string
from .xml_module import XmlDescriptor, name_to_pathname
from .editing_module import EditingDescriptor
from .stringify import stringify_children
from .html_checker import check_html
from xmodule.modulestore import Location

from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent

log = logging.getLogger("mitx.courseware")

#Set the default number of max attempts.  Should be 1 for production
#Set higher for debugging/testing
#maxattempts specified in xml definition overrides this
max_attempts = 1

def only_one(lst, default="", process=lambda x: x):
    """
    If lst is empty, returns default
    If lst has a single element, applies process to that element and returns it
    Otherwise, raises an exeception
    """
    if len(lst) == 0:
        return default
    elif len(lst) == 1:
        return process(lst[0])
    else:
        raise Exception('Malformed XML')


class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, complex):
            return "{real:.7g}{imag:+.7g}*j".format(real=obj.real, imag=obj.imag)
        return json.JSONEncoder.default(self, obj)


class SelfAssessmentModule(XModule):
    js = {'coffee': [resource_string(__name__, 'js/src/javascript_loader.coffee'),
                     resource_string(__name__, 'js/src/collapsible.coffee'),
                     resource_string(__name__, 'js/src/selfassessment/display.coffee')
    ]
    }
    js_module_name = "SelfAssessment"

    def get_html(self):
        # cdodge: perform link substitutions for any references to course static content (e.g. images)
        return rewrite_links(self.html, self.rewrite_content_links)

    def __init__(self, system, location, definition, descriptor,
                 instance_state=None, shared_state=None, **kwargs):
        XModule.__init__(self, system, location, definition, descriptor,
            instance_state, shared_state, **kwargs)

        """
        Definition file should have 4 blocks -- problem, rubric, submitmessage, and maxattempts
        Sample file:

        <selfassessment>
            <problem>
                Insert problem text here.
            </problem>
            <rubric>
                Insert grading rubric here.
            </rubric>
            <submitmessage>
                Thanks for submitting!
            </submitmessage>
            <maxattempts>
            1
            </maxattempts>
        </selfassessment>
        """

        #Initialize variables
        self.answer = []
        self.score = 0
        self.top_score = 1
        self.attempts = 0
        self.correctness = "incorrect"
        self.done = False
        self.max_attempts = self.metadata.get('attempts', None)
        self.hint=""

        #Try setting maxattempts, use default if not available in metadata
        if self.max_attempts is not None:
            self.max_attempts = int(self.max_attempts)
        else:
            self.max_attempts = max_attempts

        #Load instance state
        if instance_state is not None:
            instance_state = json.loads(instance_state)
            log.debug(instance_state)

        #Pull variables from instance state if available
        if instance_state is not None and 'attempts' in instance_state:
            self.attempts = instance_state['attempts']

        if instance_state is not None and 'student_answers' in instance_state:
            if(type(instance_state['student_answers']) in [type(u''),type('')]):
                self.answer.append(instance_state['student_answers'])
            elif(type(instance_state['student_answers'])==type([])):
                self.answer = instance_state['student_answers']

        if instance_state is not None and 'done' in instance_state:
            self.done = instance_state['done']

        if instance_state is not None and 'hint' in instance_state:
            self.hint = instance_state['hint']

        if instance_state is not None and 'correct_map' in instance_state:
            if 'self_assess' in instance_state['correct_map']:
                self.score = instance_state['correct_map']['self_assess']['npoints']
                self.correctness = instance_state['correct_map']['self_assess']['correctness']

        #Parse definition file
        dom2 = etree.fromstring("<selfassessment>" + self.definition['data'] + "</selfassessment>")

        #Try setting max_attempts from definition xml
        max_attempt_parsed=dom2.xpath('maxattempts')[0].text
        try:
            self.max_attempts=int(max_attempt_parsed)
        except:
{'fraction_non_alphanumeric': 0.0879331184528606, 'fraction_numerical': 0.0030217566478646252, 'mean_word_length': 2.7467924528301886, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5623446', 'n_tokens_mistral': 2789, 'n_tokens_neox': 2638, 'n_words': 769}