course_grading.py 8.86 KB
Newer Older
Calen Pennington committed
1
from datetime import timedelta
Don Mitchell committed
2 3 4
from contentstore.utils import get_modulestore
from xmodule.modulestore.django import loc_mapper
from xblock.fields import Scope
Don Mitchell committed
5 6


7
class CourseGradingModel(object):
Don Mitchell committed
8
    """
Calen Pennington committed
9
    Basically a DAO and Model combo for CRUD operations pertaining to grading policy.
Don Mitchell committed
10
    """
11 12
    # Within this class, allow access to protected members of client classes.
    # This comes up when accessing kvs data and caches during kvs saves and modulestore writes.
Don Mitchell committed
13
    def __init__(self, course_descriptor):
Don Mitchell committed
14 15 16
        self.graders = [
            CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course_descriptor.raw_grader)
        ]  # weights transformed to ints [0..100]
Don Mitchell committed
17 18
        self.grade_cutoffs = course_descriptor.grade_cutoffs
        self.grace_period = CourseGradingModel.convert_set_grace_period(course_descriptor)
Calen Pennington committed
19 20

    @classmethod
21
    def fetch(cls, course_locator):
Don Mitchell committed
22
        """
Don Mitchell committed
23
        Fetch the course grading policy for the given course from persistence and return a CourseGradingModel.
Don Mitchell committed
24
        """
25
        course_old_location = loc_mapper().translate_locator_to_location(course_locator)
Don Mitchell committed
26
        descriptor = get_modulestore(course_old_location).get_item(course_old_location)
Don Mitchell committed
27 28 29

        model = cls(descriptor)
        return model
Calen Pennington committed
30

Don Mitchell committed
31 32 33
    @staticmethod
    def fetch_grader(course_location, index):
        """
Calen Pennington committed
34
        Fetch the course's nth grader
Don Mitchell committed
35 36
        Returns an empty dict if there's no such grader.
        """
Don Mitchell committed
37 38
        course_old_location = loc_mapper().translate_locator_to_location(course_location)
        descriptor = get_modulestore(course_old_location).get_item(course_old_location)
Don Mitchell committed
39

Calen Pennington committed
40 41
        index = int(index)
        if len(descriptor.raw_grader) > index:
Don Mitchell committed
42
            return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])
Calen Pennington committed
43

Don Mitchell committed
44 45
        # return empty model
        else:
Chris Dodge committed
46
            return {"id": index,
Calen Pennington committed
47 48 49 50 51
                    "type": "",
                    "min_count": 0,
                    "drop_count": 0,
                    "short_label": None,
                    "weight": 0
Chris Dodge committed
52
                    }
Calen Pennington committed
53

Don Mitchell committed
54
    @staticmethod
55
    def update_from_json(course_locator, jsondict):
Don Mitchell committed
56 57 58 59
        """
        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.
        """
60
        course_old_location = loc_mapper().translate_locator_to_location(course_locator)
Don Mitchell committed
61 62
        descriptor = get_modulestore(course_old_location).get_item(course_old_location)

Don Mitchell committed
63
        graders_parsed = [CourseGradingModel.parse_grader(jsonele) for jsonele in jsondict['graders']]
Calen Pennington committed
64

Don Mitchell committed
65 66
        descriptor.raw_grader = graders_parsed
        descriptor.grade_cutoffs = jsondict['grade_cutoffs']
Calen Pennington committed
67

68
        get_modulestore(course_old_location).update_item(descriptor, 'course_grading')
69

70
        CourseGradingModel.update_grace_period_from_json(course_locator, jsondict['grace_period'])
Calen Pennington committed
71

72
        return CourseGradingModel.fetch(course_locator)
Calen Pennington committed
73

Don Mitchell committed
74 75 76
    @staticmethod
    def update_grader_from_json(course_location, grader):
        """
Calen Pennington committed
77
        Create or update the grader of the given type (string key) for the given course. Returns the modified
Don Mitchell committed
78 79
        grader which is a full model on the client but not on the server (just a dict)
        """
Don Mitchell committed
80 81
        course_old_location = loc_mapper().translate_locator_to_location(course_location)
        descriptor = get_modulestore(course_old_location).get_item(course_old_location)
Don Mitchell committed
82

Calen Pennington committed
83
        # parse removes the id; so, grab it before parse
Don Mitchell committed
84
        index = int(grader.get('id', len(descriptor.raw_grader)))
Don Mitchell committed
85 86 87 88 89 90
        grader = CourseGradingModel.parse_grader(grader)

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

92
        get_modulestore(course_old_location).update_item(descriptor, 'course_grading')
Calen Pennington committed
93

94
        return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])
Calen Pennington committed
95

Don Mitchell committed
96 97 98 99 100 101
    @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).
        """
Don Mitchell committed
102 103
        course_old_location = loc_mapper().translate_locator_to_location(course_location)
        descriptor = get_modulestore(course_old_location).get_item(course_old_location)
Don Mitchell committed
104
        descriptor.grade_cutoffs = cutoffs
105

106
        get_modulestore(course_old_location).update_item(descriptor, 'course_grading')
Calen Pennington committed
107

Don Mitchell committed
108
        return cutoffs
Calen Pennington committed
109

Don Mitchell committed
110 111 112
    @staticmethod
    def update_grace_period_from_json(course_location, graceperiodjson):
        """
Calen Pennington committed
113
        Update the course's default grace period. Incoming dict is {hours: h, minutes: m} possibly as a
114 115
        grace_period entry in an enclosing dict. It is also safe to call this method with a value of
        None for graceperiodjson.
Don Mitchell committed
116
        """
Don Mitchell committed
117 118
        course_old_location = loc_mapper().translate_locator_to_location(course_location)
        descriptor = get_modulestore(course_old_location).get_item(course_old_location)
119 120 121 122 123 124 125

        # 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']

Calen Pennington committed
126
            grace_timedelta = timedelta(**graceperiodjson)
Calen Pennington committed
127
            descriptor.graceperiod = grace_timedelta
128

129
            get_modulestore(course_old_location).update_item(descriptor, 'update_grace_period')
Calen Pennington committed
130

Don Mitchell committed
131 132 133 134 135
    @staticmethod
    def delete_grader(course_location, index):
        """
        Delete the grader of the given type from the given course.
        """
Don Mitchell committed
136 137
        course_old_location = loc_mapper().translate_locator_to_location(course_location)
        descriptor = get_modulestore(course_old_location).get_item(course_old_location)
Calen Pennington committed
138

Calen Pennington committed
139
        index = int(index)
Don Mitchell committed
140 141
        if index < len(descriptor.raw_grader):
            del descriptor.raw_grader[index]
142
            # force propagation to definition
Don Mitchell committed
143
            descriptor.raw_grader = descriptor.raw_grader
Calen Pennington committed
144

145
        get_modulestore(course_old_location).update_item(descriptor, 'delete_grader')
Calen Pennington committed
146

Don Mitchell committed
147 148 149
    @staticmethod
    def delete_grace_period(course_location):
        """
Don Mitchell committed
150
        Delete the course's grace period.
Don Mitchell committed
151
        """
Don Mitchell committed
152 153
        course_old_location = loc_mapper().translate_locator_to_location(course_location)
        descriptor = get_modulestore(course_old_location).get_item(course_old_location)
Calen Pennington committed
154

Calen Pennington committed
155
        del descriptor.graceperiod
156

157
        get_modulestore(course_old_location).update_item(descriptor, 'delete_grace_period')
Calen Pennington committed
158

Don Mitchell committed
159
    @staticmethod
160
    def get_section_grader_type(location):
Don Mitchell committed
161 162 163
        old_location = loc_mapper().translate_locator_to_location(location)
        descriptor = get_modulestore(old_location).get_item(old_location)
        return {
164
            "graderType": descriptor.format if descriptor.format is not None else 'notgraded',
Don Mitchell committed
165 166
            "location": unicode(location),
        }
Calen Pennington committed
167

168
    @staticmethod
Don Mitchell committed
169
    def update_section_grader_type(descriptor, grader_type):
170
        if grader_type is not None and grader_type != u'notgraded':
Don Mitchell committed
171
            descriptor.format = grader_type
Calen Pennington committed
172
            descriptor.graded = True
173
        else:
Calen Pennington committed
174 175
            del descriptor.format
            del descriptor.graded
Calen Pennington committed
176

177
        get_modulestore(descriptor.location).update_item(descriptor, 'update_grader')
Don Mitchell committed
178
        return {'graderType': grader_type}
Calen Pennington committed
179

180
    @staticmethod
Don Mitchell committed
181 182
    def convert_set_grace_period(descriptor):
        # 5 hours 59 minutes 59 seconds => converted to iso format
Calen Pennington committed
183
        rawgrace = descriptor.graceperiod
Don Mitchell committed
184
        if rawgrace:
185
            hours_from_days = rawgrace.days * 24
186 187
            seconds = rawgrace.seconds
            hours_from_seconds = int(seconds / 3600)
Calen Pennington committed
188
            hours = hours_from_days + hours_from_seconds
189 190 191
            seconds -= hours_from_seconds * 3600
            minutes = int(seconds / 60)
            seconds -= minutes * 60
Calen Pennington committed
192

Calen Pennington committed
193
            graceperiod = {'hours': 0, 'minutes': 0, 'seconds': 0}
Calen Pennington committed
194
            if hours > 0:
Calen Pennington committed
195
                graceperiod['hours'] = hours
Calen Pennington committed
196 197

            if minutes > 0:
Calen Pennington committed
198
                graceperiod['minutes'] = minutes
Calen Pennington committed
199 200

            if seconds > 0:
Calen Pennington committed
201
                graceperiod['seconds'] = seconds
Calen Pennington committed
202 203

            return graceperiod
204 205
        else:
            return None
Don Mitchell committed
206 207 208 209

    @staticmethod
    def parse_grader(json_grader):
        # manual to clear out kruft
Chris Dodge committed
210 211 212 213 214 215
        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
                  }
Calen Pennington committed
216

Don Mitchell committed
217 218 219 220 221 222 223 224
        return result

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

227
        return grader