Newer
Older
Dong Zhuang
committed
comment = models.TextField(blank=True, null=True,
verbose_name = 'Comment')
Dong Zhuang
committed
due_time = models.DateTimeField(default=None, blank=True, null=True,
verbose_name = 'Due time')
Dong Zhuang
committed
creator = models.ForeignKey(User, null=True,
verbose_name = 'Creator')
grade_time = models.DateTimeField(default=now, db_index=True,
verbose_name = 'Grade time')
flow_session = models.ForeignKey(FlowSession, null=True, blank=True,
Dong Zhuang
committed
related_name="grade_changes",
verbose_name = 'Flow session')
Dong Zhuang
committed
verbose_name = "Grade change"
verbose_name_plural = "Grade changes"
ordering = ("opportunity", "participation", "grade_time")
def __unicode__(self):
Dong Zhuang
committed
# Translators: information for GradeChange
return "%(participation)s %(state)s on %(opportunityname)s" % {
'participation':self.participation,
'state':self.state,
'opportunityname':self.opportunity.name}
def clean(self):
if self.opportunity.course != self.participation.course:
raise ValidationError("Participation and opportunity must live "
"in the same course")
if self.max_points is not None and self.points is not None:
return 100*self.points/self.max_points
else:
return None
# }}}
# {{{ grade state machine
class GradeStateMachine(object):
def __init__(self):
self.opportunity = None
self.state = None
self._clear_grades()
self.due_time = None
self.last_report_time = None
# applies to *all* grade changes
self._last_grade_change_time = None
def _clear_grades(self):
self.state = None
self.last_grade_time = None
self.valid_percentages = []
def _consume_grade_change(self, gchange, set_is_superseded):
if self.opportunity is None:
self.opportunity = gchange.opportunity
self.due_time = self.opportunity.due_time
else:
assert self.opportunity.pk == gchange.opportunity.pk
# check that times are increasing
if self._last_grade_change_time is not None:
assert gchange.grade_time > self._last_grade_change_time
self._last_grade_change_time = gchange.grade_time
if gchange.state == grade_state_change_types.graded:
if self.state == grade_state_change_types.unavailable:
raise ValueError("cannot accept grade once opportunity has been "
"marked 'unavailable'")
if self.state == grade_state_change_types.exempt:
raise ValueError("cannot accept grade once opportunity has been "
"marked 'exempt'")
#if self.due_time is not None and gchange.grade_time > self.due_time:
#raise ValueError("cannot accept grade after due date")
self.state = gchange.state
gchange.attempt_id in self.attempt_id_to_gchange):
self.attempt_id_to_gchange[gchange.attempt_id] \
self.attempt_id_to_gchange[gchange.attempt_id] \
= gchange
else:
self.valid_percentages.append(gchange.percentage())
self.last_graded_time = gchange.grade_time
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
elif gchange.state == grade_state_change_types.unavailable:
self._clear_grades()
self.state = gchange.state
elif gchange.state == grade_state_change_types.do_over:
self._clear_grades()
elif gchange.state == grade_state_change_types.exempt:
self._clear_grades()
self.state = gchange.state
elif gchange.state == grade_state_change_types.report_sent:
self.last_report_time = gchange.grade_time
elif gchange.state == grade_state_change_types.extension:
self.due_time = gchange.due_time
elif gchange.state in [
grade_state_change_types.grading_started,
grade_state_change_types.retrieved,
]:
pass
else:
raise RuntimeError("invalid grade change state '%s'" % gchange.state)
def consume(self, iterable, set_is_superseded=False):
for gchange in iterable:
gchange.is_superseded = False
self._consume_grade_change(gchange, set_is_superseded)
self.valid_percentages.extend(
gchange.percentage()
for gchange in self.attempt_id_to_gchange.values()
if gchange.percentage() is not None)
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
return self
def percentage(self):
"""
:return: a percentage of achieved points, or *None*
"""
if self.opportunity is None or not self.valid_percentages:
return None
strategy = self.opportunity.aggregation_strategy
if strategy == grade_aggregation_strategy.max_grade:
return max(self.valid_percentages)
elif strategy == grade_aggregation_strategy.min_grade:
return min(self.valid_percentages)
elif strategy == grade_aggregation_strategy.avg_grade:
return sum(self.valid_percentages)/len(self.valid_percentages)
elif strategy == grade_aggregation_strategy.use_earliest:
return self.valid_percentages[0]
elif strategy == grade_aggregation_strategy.use_latest:
return self.valid_percentages[-1]
else:
raise ValueError("invalid grade aggregation strategy '%s'" % strategy)
def stringify_state(self):
if self.state is None:
elif self.state == grade_state_change_types.exempt:
return "(exempt)"
elif self.state == grade_state_change_types.graded:
if self.valid_percentages:
result = "%.1f%%" % self.percentage()
if len(self.valid_percentages) > 1:
result += " (/%d)" % len(self.valid_percentages)
return result
else:
def stringify_machine_readable_state(self):
if self.state is None:
return u"NONE"
elif self.state == grade_state_change_types.exempt:
return "EXEMPT"
elif self.state == grade_state_change_types.graded:
if self.valid_percentages:
return "%.3f" % self.percentage()
else:
return u"NONE"
else:
return u"OTHER_STATE"
def stringify_percentage(self):
if self.state == grade_state_change_types.graded:
if self.valid_percentages:
return "%.1f" % self.percentage()
else:
return u""
else:
return ""
# }}}
# {{{ flow <-> grading integration
def get_flow_grading_opportunity(course, flow_id, flow_desc, grading_rule):
from course.utils import FlowSessionGradingRule
assert isinstance(grading_rule, FlowSessionGradingRule)
gopp, created = GradingOpportunity.objects.get_or_create(
course=course,
identifier=grading_rule.grade_identifier,
Dong Zhuang
committed
# Translators: name of flow with prefix "Flow"
name="Flow: %(flow_desc_title)s" % {"flow_desc_title":flow_desc.title},
aggregation_strategy=grading_rule.grade_aggregation_strategy,
# {{{ XMPP log
class InstantMessage(models.Model):
Dong Zhuang
committed
participation = models.ForeignKey(Participation,
verbose_name = 'Participation')
text = models.CharField(max_length=200,
verbose_name = 'Text')
time = models.DateTimeField(default=now,
verbose_name = 'Time')
Dong Zhuang
committed
verbose_name = "Instant message"
verbose_name_plural = "Instant messages"
ordering = ("participation__course", "time")
def __unicode__(self):
return "%s: %s" % (self.participation, self.text)
# }}}
# vim: foldmethod=marker