course_grading.py 7.82 KB
Newer Older
Calen Pennington committed
1
from datetime import timedelta
2
from xmodule.modulestore.django import modulestore
Don Mitchell committed
3 4


5
class CourseGradingModel(object):
Don Mitchell committed
6
    """
Calen Pennington committed
7
    Basically a DAO and Model combo for CRUD operations pertaining to grading policy.
Don Mitchell committed
8
    """
9 10
    # 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
11
    def __init__(self, course_descriptor):
Don Mitchell committed
12 13 14
        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
15 16
        self.grade_cutoffs = course_descriptor.grade_cutoffs
        self.grace_period = CourseGradingModel.convert_set_grace_period(course_descriptor)
Calen Pennington committed
17 18

    @classmethod
19
    def fetch(cls, course_key):
Don Mitchell committed
20
        """
Don Mitchell committed
21
        Fetch the course grading policy for the given course from persistence and return a CourseGradingModel.
Don Mitchell committed
22
        """
23
        descriptor = modulestore().get_course(course_key)
Don Mitchell committed
24 25
        model = cls(descriptor)
        return model
Calen Pennington committed
26

Don Mitchell committed
27
    @staticmethod
28
    def fetch_grader(course_key, index):
Don Mitchell committed
29
        """
Calen Pennington committed
30
        Fetch the course's nth grader
Don Mitchell committed
31 32
        Returns an empty dict if there's no such grader.
        """
33
        descriptor = modulestore().get_course(course_key)
Calen Pennington committed
34 35
        index = int(index)
        if len(descriptor.raw_grader) > index:
Don Mitchell committed
36
            return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])
Calen Pennington committed
37

Don Mitchell committed
38 39
        # return empty model
        else:
Chris Dodge committed
40
            return {"id": index,
Calen Pennington committed
41 42 43 44 45
                    "type": "",
                    "min_count": 0,
                    "drop_count": 0,
                    "short_label": None,
                    "weight": 0
Chris Dodge committed
46
                    }
Calen Pennington committed
47

Don Mitchell committed
48
    @staticmethod
49
    def update_from_json(course_key, jsondict, user):
Don Mitchell committed
50 51 52 53
        """
        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.
        """
54
        descriptor = modulestore().get_course(course_key)
Don Mitchell committed
55

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

Don Mitchell committed
58 59
        descriptor.raw_grader = graders_parsed
        descriptor.grade_cutoffs = jsondict['grade_cutoffs']
Calen Pennington committed
60

61
        modulestore().update_item(descriptor, user.id)
62

63
        CourseGradingModel.update_grace_period_from_json(course_key, jsondict['grace_period'], user)
Calen Pennington committed
64

65
        return CourseGradingModel.fetch(course_key)
Calen Pennington committed
66

Don Mitchell committed
67
    @staticmethod
68
    def update_grader_from_json(course_key, grader, user):
Don Mitchell committed
69
        """
Calen Pennington committed
70
        Create or update the grader of the given type (string key) for the given course. Returns the modified
Don Mitchell committed
71 72
        grader which is a full model on the client but not on the server (just a dict)
        """
73
        descriptor = modulestore().get_course(course_key)
Don Mitchell committed
74

Calen Pennington committed
75
        # parse removes the id; so, grab it before parse
Don Mitchell committed
76
        index = int(grader.get('id', len(descriptor.raw_grader)))
Don Mitchell committed
77 78 79 80 81 82
        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
83

84
        modulestore().update_item(descriptor, user.id)
Calen Pennington committed
85

86
        return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])
Calen Pennington committed
87

Don Mitchell committed
88
    @staticmethod
89
    def update_cutoffs_from_json(course_key, cutoffs, user):
Don Mitchell committed
90 91 92 93
        """
        Create or update the grade cutoffs for the given course. Returns sent in cutoffs (ie., no extra
        db fetch).
        """
94
        descriptor = modulestore().get_course(course_key)
Don Mitchell committed
95
        descriptor.grade_cutoffs = cutoffs
96

97
        modulestore().update_item(descriptor, user.id)
Calen Pennington committed
98

Don Mitchell committed
99
        return cutoffs
Calen Pennington committed
100

Don Mitchell committed
101
    @staticmethod
102
    def update_grace_period_from_json(course_key, graceperiodjson, user):
Don Mitchell committed
103
        """
Calen Pennington committed
104
        Update the course's default grace period. Incoming dict is {hours: h, minutes: m} possibly as a
105 106
        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
107
        """
108
        descriptor = modulestore().get_course(course_key)
109 110 111 112 113 114 115

        # 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
116
            grace_timedelta = timedelta(**graceperiodjson)
Calen Pennington committed
117
            descriptor.graceperiod = grace_timedelta
118

119
            modulestore().update_item(descriptor, user.id)
Calen Pennington committed
120

Don Mitchell committed
121
    @staticmethod
122
    def delete_grader(course_key, index, user):
Don Mitchell committed
123 124 125
        """
        Delete the grader of the given type from the given course.
        """
126
        descriptor = modulestore().get_course(course_key)
Calen Pennington committed
127

Calen Pennington committed
128
        index = int(index)
Don Mitchell committed
129 130
        if index < len(descriptor.raw_grader):
            del descriptor.raw_grader[index]
131
            # force propagation to definition
Don Mitchell committed
132
            descriptor.raw_grader = descriptor.raw_grader
Calen Pennington committed
133

134
        modulestore().update_item(descriptor, user.id)
Calen Pennington committed
135

Don Mitchell committed
136
    @staticmethod
137
    def delete_grace_period(course_key, user):
Don Mitchell committed
138
        """
Don Mitchell committed
139
        Delete the course's grace period.
Don Mitchell committed
140
        """
141
        descriptor = modulestore().get_course(course_key)
Calen Pennington committed
142

Calen Pennington committed
143
        del descriptor.graceperiod
144

145
        modulestore().update_item(descriptor, user.id)
Calen Pennington committed
146

Don Mitchell committed
147
    @staticmethod
148
    def get_section_grader_type(location):
149
        descriptor = modulestore().get_item(location)
Don Mitchell committed
150
        return {
151
            "graderType": descriptor.format if descriptor.format is not None else 'notgraded',
Don Mitchell committed
152 153
            "location": unicode(location),
        }
Calen Pennington committed
154

155
    @staticmethod
156
    def update_section_grader_type(descriptor, grader_type, user):
157
        if grader_type is not None and grader_type != u'notgraded':
Don Mitchell committed
158
            descriptor.format = grader_type
Calen Pennington committed
159
            descriptor.graded = True
160
        else:
Calen Pennington committed
161 162
            del descriptor.format
            del descriptor.graded
Calen Pennington committed
163

164
        modulestore().update_item(descriptor, user.id)
Don Mitchell committed
165
        return {'graderType': grader_type}
Calen Pennington committed
166

167
    @staticmethod
Don Mitchell committed
168 169
    def convert_set_grace_period(descriptor):
        # 5 hours 59 minutes 59 seconds => converted to iso format
Calen Pennington committed
170
        rawgrace = descriptor.graceperiod
Don Mitchell committed
171
        if rawgrace:
172
            hours_from_days = rawgrace.days * 24
173 174
            seconds = rawgrace.seconds
            hours_from_seconds = int(seconds / 3600)
Calen Pennington committed
175
            hours = hours_from_days + hours_from_seconds
176 177 178
            seconds -= hours_from_seconds * 3600
            minutes = int(seconds / 60)
            seconds -= minutes * 60
Calen Pennington committed
179

Calen Pennington committed
180
            graceperiod = {'hours': 0, 'minutes': 0, 'seconds': 0}
Calen Pennington committed
181
            if hours > 0:
Calen Pennington committed
182
                graceperiod['hours'] = hours
Calen Pennington committed
183 184

            if minutes > 0:
Calen Pennington committed
185
                graceperiod['minutes'] = minutes
Calen Pennington committed
186 187

            if seconds > 0:
Calen Pennington committed
188
                graceperiod['seconds'] = seconds
Calen Pennington committed
189 190

            return graceperiod
191 192
        else:
            return None
Don Mitchell committed
193 194 195 196

    @staticmethod
    def parse_grader(json_grader):
        # manual to clear out kruft
Chris Dodge committed
197 198 199 200 201 202
        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
203

Don Mitchell committed
204 205 206 207
        return result

    @staticmethod
    def jsonize_grader(i, grader):
208 209 210
        # Warning: converting weight to integer might give unwanted results due
        # to the reason how floating point arithmetic works
        # e.g, "0.29 * 100 = 28.999999999999996"
211 212 213 214 215 216 217 218
        return {
            "id": i,
            "type": grader["type"],
            "min_count": grader.get('min_count', 0),
            "drop_count": grader.get('drop_count', 0),
            "short_label": grader.get('short_label', ""),
            "weight": grader.get('weight', 0) * 100,
        }