<commit_before>from xmodule.modulestore import Location
from contentstore.utils import get_modulestore
import re
from util import converters


class CourseGradingModel(object):
    """
    Basically a DAO and Model combo for CRUD operations pertaining to grading policy.
    """
    def __init__(self, course_descriptor):
        self.course_location = course_descriptor.location
        self.graders = [CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course_descriptor.raw_grader)]   # weights transformed to ints [0..100]
        self.grade_cutoffs = course_descriptor.grade_cutoffs
        self.grace_period = CourseGradingModel.convert_set_grace_period(course_descriptor)

    @classmethod
    def fetch(cls, course_location):
        """
        Fetch the course details for the given course from persistence and return a CourseDetails model.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)

        model = cls(descriptor)
        return model

    @staticmethod
    def fetch_grader(course_location, index):
        """
        Fetch the course's nth grader
        Returns an empty dict if there's no such grader.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        # # ??? it would be good if these had the course_location in them so that they stand alone sufficiently
        # # but that would require not using CourseDescriptor's field directly. Opinions?

        index = int(index)
        if len(descriptor.raw_grader) > index:
            return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])

        # return empty model
        else:
            return {
                    "id": index,
                    "type": "",
                    "min_count": 0,
                    "drop_count": 0,
                    "short_label": None,
                    "weight": 0
                }

    @staticmethod
    def fetch_cutoffs(course_location):
        """
        Fetch the course's grade cutoffs.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        return descriptor.grade_cutoffs

    @staticmethod
    def fetch_grace_period(course_location):
        """
        Fetch the course's default grace period.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        return {'grace_period': CourseGradingModel.convert_set_grace_period(descriptor)}

    @staticmethod
    def update_from_json(jsondict):
        """
        Decode the json into CourseGradingModel and save any changes. Returns the modified model.
        Probably not the usual path for updates as it's too coarse grained.
        """
        course_location = jsondict['course_location']
        descriptor = get_modulestore(course_location).get_item(course_location)

        graders_parsed = [CourseGradingModel.parse_grader(jsonele) for jsonele in jsondict['graders']]

        descriptor.raw_grader = graders_parsed
        descriptor.grade_cutoffs = jsondict['grade_cutoffs']

        get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])
        CourseGradingModel.update_grace_period_from_json(course_location, jsondict['grace_period'])

        return CourseGradingModel.fetch(course_location)


    @staticmethod
    def update_grader_from_json(course_location, grader):
        """
        Create or update the grader of the given type (string key) for the given course. Returns the modified
        grader which is a full model on the client but not on the server (just a dict)
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        # # ??? it would be good if these had the course_location in them so that they stand alone sufficiently
        # # but that would require not using CourseDescriptor's field directly. Opinions?

        # parse removes the id; so, grab it before parse
        index = int(grader.get('id', len(descriptor.raw_grader)))
        grader = CourseGradingModel.parse_grader(grader)

        if index < len(descriptor.raw_grader):
            descriptor.raw_grader[index] = grader
        else:
            descriptor.raw_grader.append(grader)

        get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])

        return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])

    @staticmethod
    def update_cutoffs_from_json(course_location, cutoffs):
        """
        Create or update the grade cutoffs for the given course. Returns sent in cutoffs (ie., no extra
        db fetch).
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        descriptor.grade_cutoffs = cutoffs
        get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])

        return cutoffs


    @staticmethod
    def update_grace_period_from_json(course_location, graceperiodjson):
        """
        Update the course's default grace period. Incoming dict is {hours: h, minutes: m} possibly as a
        grace_period entry in an enclosing dict. It is also safe to call this method with a value of
        None for graceperiodjson.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        # Before a graceperiod has ever been created, it will be None (once it has been
        # created, it cannot be set back to None).
        if graceperiodjson is not None:
            if 'grace_period' in graceperiodjson:
                graceperiodjson = graceperiodjson['grace_period']

            grace_rep = " ".join(["%s %s" % (value, key) for (key, value) in graceperiodjson.iteritems()])

            descriptor = get_modulestore(course_location).get_item(course_location)
            descriptor.metadata['graceperiod'] = grace_rep
            get_modulestore(course_location).update_metadata(course_location, descriptor.metadata)

    @staticmethod
    def delete_grader(course_location, index):
        """
        Delete the grader of the given type from the given course.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        index = int(index)
        if index < len(descriptor.raw_grader):
            del descriptor.raw_grader[index]
            # force propagation to definition
            descriptor.raw_grader = descriptor.raw_grader
            get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])

    # NOTE cannot delete cutoffs. May be useful to reset
    @staticmethod
    def delete_cutoffs(course_location, cutoffs):
        """
        Resets the cutoffs to the defaults
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        descriptor.grade_cutoffs = descriptor.defaut_grading_policy['GRADE_CUTOFFS']
        get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])

        return descriptor.grade_cutoffs

    @staticmethod
    def delete_grace_period(course_location):
        """
        Delete the course's default grace period.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        if 'graceperiod' in descriptor.metadata: del descriptor.metadata['graceperiod']   
        get_modulestore(course_location).update_metadata(course_location, descriptor.metadata)

    @staticmethod
    def get_section_grader_type(location):
        if not isinstance(location, Location):
            location = Location(location)

        descriptor = get_modulestore(location).get_item(location)
        return {
                "graderType": descriptor.metadata.get('format', u"Not Graded"),
                "location": location,
                "id": 99   # just an arbitrary value to
                }

    @staticmethod
    def update_section_grader_type(location, jsondict):
        if not isinstance(location, Location):
            location = Location(location)

        descriptor = get_modulestore(location).get_item(location)
        if 'graderType' in jsondict and jsondict['graderType'] != u"Not Graded":
            descriptor.metadata['format'] = jsondict.get('graderType')
            descriptor.metadata['graded'] = True
        else:
            if 'format' in descriptor.metadata: del descriptor.metadata['format'] 
            if 'graded' in descriptor.metadata: del descriptor.metadata['graded'] 

        get_modulestore(location).update_metadata(location, descriptor.metadata)


    @staticmethod
    def convert_set_grace_period(descriptor):
        # 5 hours 59 minutes 59 seconds => converted to iso format
        rawgrace = descriptor.metadata.get('graceperiod', None)
        if rawgrace:
            parsedgrace = {str(key): val for (val, key) in re.findall('\s*(\d+)\s*(\w+)', rawgrace)}
            return parsedgrace
        else: return None

    @staticmethod
    def parse_grader(json_grader):
        # manual to clear out kruft
        result = {
                    "type": json_grader["type"],
                    "min_count": int(json_grader.get('min_count', 0)),
                    "drop_count": int(json_grader.get('drop_count', 0)),
                    "short_label": json_grader.get('short_label', None),
                    "weight": float(json_grader.get('weight', 0)) / 100.0
                }

        return result

    @staticmethod
    def jsonize_grader(i, grader):
        grader['id'] = i
        if grader['weight']:
            grader['weight'] *= 100
        if not 'short_label' in grader:
            grader['short_label'] = ""

        return grader
<commit_msg>lms required grace period fields to be in specific order<commit_after>from xmodule.modulestore import Location
from contentstore.utils import get_modulestore
import re
from util import converters


class CourseGradingModel(object):
    """
    Basically a DAO and Model combo for CRUD operations pertaining to grading policy.
    """
    def __init__(self, course_descriptor):
        self.course_location = course_descriptor.location
        self.graders = [CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course_descriptor.raw_grader)]   # weights transformed to ints [0..100]
        self.grade_cutoffs = course_descriptor.grade_cutoffs
        self.grace_period = CourseGradingModel.convert_set_grace_period(course_descriptor)

    @classmethod
    def fetch(cls, course_location):
        """
        Fetch the course details for the given course from persistence and return a CourseDetails model.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)

        model = cls(descriptor)
        return model

    @staticmethod
    def fetch_grader(course_location, index):
        """
        Fetch the course's nth grader
        Returns an empty dict if there's no such grader.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        # # ??? it would be good if these had the course_location in them so that they stand alone sufficiently
        # # but that would require not using CourseDescriptor's field directly. Opinions?

        index = int(index)
        if len(descriptor.raw_grader) > index:
            return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])

        # return empty model
        else:
            return {
                    "id": index,
                    "type": "",
                    "min_count": 0,
                    "drop_count": 0,
                    "short_label": None,
                    "weight": 0
                }

    @staticmethod
    def fetch_cutoffs(course_location):
        """
        Fetch the course's grade cutoffs.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        return descriptor.grade_cutoffs

    @staticmethod
    def fetch_grace_period(course_location):
        """
        Fetch the course's default grace period.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        return {'grace_period': CourseGradingModel.convert_set_grace_period(descriptor)}

    @staticmethod
    def update_from_json(jsondict):
        """
        Decode the json into CourseGradingModel and save any changes. Returns the modified model.
        Probably not the usual path for updates as it's too coarse grained.
        """
        course_location = jsondict['course_location']
        descriptor = get_modulestore(course_location).get_item(course_location)

        graders_parsed = [CourseGradingModel.parse_grader(jsonele) for jsonele in jsondict['graders']]

        descriptor.raw_grader = graders_parsed
        descriptor.grade_cutoffs = jsondict['grade_cutoffs']

        get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])
        CourseGradingModel.update_grace_period_from_json(course_location, jsondict['grace_period'])

        return CourseGradingModel.fetch(course_location)


    @staticmethod
    def update_grader_from_json(course_location, grader):
        """
        Create or update the grader of the given type (string key) for the given course. Returns the modified
        grader which is a full model on the client but not on the server (just a dict)
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        # # ??? it would be good if these had the course_location in them so that they stand alone sufficiently
        # # but that would require not using CourseDescriptor's field directly. Opinions?

        # parse removes the id; so, grab it before parse
        index = int(grader.get('id', len(descriptor.raw_grader)))
        grader = CourseGradingModel.parse_grader(grader)

        if index < len(descriptor.raw_grader):
            descriptor.raw_grader[index] = grader
        else:
            descriptor.raw_grader.append(grader)

        get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])

        return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])

    @staticmethod
    def update_cutoffs_from_json(course_location, cutoffs):
        """
        Create or update the grade cutoffs for the given course. Returns sent in cutoffs (ie., no extra
        db fetch).
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        descriptor.grade_cutoffs = cutoffs
        get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])

        return cutoffs


    @staticmethod
    def update_grace_period_from_json(course_location, graceperiodjson):
        """
        Update the course's default grace period. Incoming dict is {hours: h, minutes: m} possibly as a
        grace_period entry in an enclosing dict. It is also safe to call this method with a value of
        None for graceperiodjson.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        # Before a graceperiod has ever been created, it will be None (once it has been
        # created, it cannot be set back to None).
        if graceperiodjson is not None:
            if 'grace_period' in graceperiodjson:
                graceperiodjson = graceperiodjson['grace_period']

            # lms requires these to be in a fixed order
            grace_rep = "{0[hours]:d} hours {0[minutes]:d} minutes {0[seconds]:d} seconds".format(graceperiodjson) 

            descriptor = get_modulestore(course_location).get_item(course_location)
            descriptor.metadata['graceperiod'] = grace_rep
            get_modulestore(course_location).update_metadata(course_location, descriptor.metadata)

    @staticmethod
    def delete_grader(course_location, index):
        """
        Delete the grader of the given type from the given course.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        index = int(index)
        if index < len(descriptor.raw_grader):
            del descriptor.raw_grader[index]
            # force propagation to definition
            descriptor.raw_grader = descriptor.raw_grader
            get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])

    # NOTE cannot delete cutoffs. May be useful to reset
    @staticmethod
    def delete_cutoffs(course_location, cutoffs):
        """
        Resets the cutoffs to the defaults
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        descriptor.grade_cutoffs = descriptor.defaut_grading_policy['GRADE_CUTOFFS']
        get_modulestore(course_location).update_item(course_location, descriptor.definition['data'])

        return descriptor.grade_cutoffs

    @staticmethod
    def delete_grace_period(course_location):
        """
        Delete the course's default grace period.
        """
        if not isinstance(course_location, Location):
            course_location = Location(course_location)

        descriptor = get_modulestore(course_location).get_item(course_location)
        if 'graceperiod' in descriptor.metadata: del descriptor.metadata['graceperiod']   
        get_modulestore(course_location).update_metadata(course_location, descriptor.metadata)

    @staticmethod
    def get_section_grader_type(location):
        if not isinstance(location, Location):
            location = Location(location)

        descriptor = get_modulestore(location).get_item(location)
        return {
                "graderType": descriptor.metadata.get('format', u"Not Graded"),
                "location": location,
                "id": 99   # just an arbitrary value to
                }

    @staticmethod
    def update_section_grader_type(location, jsondict):
        if not isinstance(location, Location):
            location = Location(location)

        descriptor = get_modulestore(location).get_item(location)
        if 'graderType' in jsondict and jsondict['graderType'] != u"Not Graded":
            descriptor.metadata['format'] = jsondict.get('graderType')
            descriptor.metadata['graded'] = True
        else:
            if 'format' in descriptor.metadata: del descriptor.metadata['format'] 
            if 'graded' in descriptor.metadata: del descriptor.metadata['graded'] 

        get_modulestore(location).update_metadata(location, descriptor.metadata)


    @staticmethod
    def convert_set_grace_period(descriptor):
        # 5 hours 59 minutes 59 seconds => converted to iso format
        rawgrace = descriptor.metadata.get('graceperiod', None)
        if rawgrace:
            parsedgrace = {str(key): val for (val, key) in re.findall('\s*(\d+)\s*(\w+)', rawgrace)}
            return parsedgrace
        else: return None

    @staticmethod
    def parse_grader(json_grader):
        # manual to clear out kruft
        result = {
                    "type": json_grader["type"],
                    "min_count": int(json_grader.get('min_count', 0)),
                    "drop_count": int(json_grader.get('drop_count', 0)),
                    "short_label": json_grader.get('short_label', None),
                    "weight": float(json_grader.get('weight', 0)) / 100.0
                }

        return result

    @staticmethod
    def jsonize_grader(i, grader):
        grader['id'] = i
        if grader['weight']:
            grader['weight'] *= 100
        if not 'short_label' in grader:
            grader['short_label'] = ""

        return grader

{'fraction_non_alphanumeric': 0.08169300434340634, 'fraction_numerical': 0.0023565289714444137, 'mean_word_length': 2.7804366812227075, 'pattern_counts': {'":': 30, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, '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': '6533454', 'n_tokens_mistral': 5872, 'n_tokens_neox': 5644, 'n_words': 1449}