diff --git a/main/c2g/migrations/0013_auto__add_field_exam_hourly_late_penalty.py b/main/c2g/migrations/0013_auto__add_field_exam_daily_late_penalty.py similarity index 99% rename from main/c2g/migrations/0013_auto__add_field_exam_hourly_late_penalty.py rename to main/c2g/migrations/0013_auto__add_field_exam_daily_late_penalty.py index bba18a11..e958a060 100644 --- a/main/c2g/migrations/0013_auto__add_field_exam_hourly_late_penalty.py +++ b/main/c2g/migrations/0013_auto__add_field_exam_daily_late_penalty.py @@ -8,15 +8,15 @@ class Migration(SchemaMigration): def forwards(self, orm): - # Adding field 'Exam.hourly_late_penalty' - db.add_column('c2g_exam', 'hourly_late_penalty', + # Adding field 'Exam.daily_late_penalty' + db.add_column('c2g_exam', 'daily_late_penalty', self.gf('django.db.models.fields.FloatField')(default=0.0, null=True, blank=True), keep_default=False) def backwards(self, orm): - # Deleting field 'Exam.hourly_late_penalty' - db.delete_column('c2g_exam', 'hourly_late_penalty') + # Deleting field 'Exam.daily_late_penalty' + db.delete_column('c2g_exam', 'daily_late_penalty') models = { @@ -217,6 +217,7 @@ def backwards(self, orm): 'assessment_type': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}), 'autograde': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'course': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['c2g.Course']"}), + 'daily_late_penalty': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'null': 'True', 'blank': 'True'}), 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 'display_single': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'due_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), @@ -224,7 +225,6 @@ def backwards(self, orm): 'grace_period': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 'grade_single': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'hide_grades': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'hourly_late_penalty': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'null': 'True', 'blank': 'True'}), 'html_content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'image': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['c2g.Exam']"}), diff --git a/main/c2g/models.py b/main/c2g/models.py index 60aecc04..449a5fab 100644 --- a/main/c2g/models.py +++ b/main/c2g/models.py @@ -1922,7 +1922,7 @@ class Exam(TimestampMixin, Deletable, Stageable, Sortable, models.Model): grace_period = models.DateTimeField(null=True, blank=True) partial_credit_deadline = models.DateTimeField(null=True, blank=True) late_penalty = models.IntegerField(default=0, null=True, blank=True) - hourly_late_penalty = models.FloatField(default=0.0, null=True, blank=True) + daily_late_penalty = models.FloatField(default=0.0, null=True, blank=True) submissions_permitted = models.IntegerField(default=999, null=True, blank=True) resubmission_penalty = models.IntegerField(default=0, null=True, blank=True) autograde = models.BooleanField(default=False) @@ -2103,7 +2103,7 @@ def create_ready_instance(self): xml_imported = self.xml_imported, partial_credit_deadline = self.partial_credit_deadline, late_penalty = self.late_penalty, - hourly_late_penalty = self.hourly_late_penalty, + daily_late_penalty = self.daily_late_penalty, submissions_permitted = self.submissions_permitted, resubmission_penalty = self.resubmission_penalty, autograde = self.autograde, @@ -2157,8 +2157,8 @@ def commit(self, clone_fields = None): ready_instance.partial_credit_deadline = self.partial_credit_deadline if not clone_fields or 'late_penalty' in clone_fields: ready_instance.late_penalty = self.late_penalty - if not clone_fields or 'hourly_late_penalty' in clone_fields: - ready_instance.hourly_late_penalty = self.hourly_late_penalty + if not clone_fields or 'daily_late_penalty' in clone_fields: + ready_instance.daily_late_penalty = self.daily_late_penalty if not clone_fields or 'submissions_permitted' in clone_fields: ready_instance.submissions_permitted = self.submissions_permitted if not clone_fields or 'resubmission_penalty' in clone_fields: @@ -2220,8 +2220,8 @@ def revert(self, clone_fields = None): self.partial_credit_deadline = ready_instance.partial_credit_deadline if not clone_fields or 'late_penalty' in clone_fields: self.late_penalty = ready_instance.late_penalty - if not clone_fields or 'hourly_late_penalty' in clone_fields: - self.hourly_late_penalty = ready_instance.hourly_late_penalty + if not clone_fields or 'daily_late_penalty' in clone_fields: + self.daily_late_penalty = ready_instance.daily_late_penalty if not clone_fields or 'submissions_permitted' in clone_fields: self.submissions_permitted = ready_instance.submissions_permitted if not clone_fields or 'resubmission_penalty' in clone_fields: @@ -2283,7 +2283,7 @@ def is_synced(self): return False if self.late_penalty != self.image.late_penalty: return False - if self.hourly_late_penalty != self.image.hourly_late_penalty: + if self.daily_late_penalty != self.image.daily_late_penalty: return False if self.submissions_permitted != self.image.submissions_permitted: return False diff --git a/main/courses/exams/tests.py b/main/courses/exams/tests.py index 75ce2680..c6fb3204 100644 --- a/main/courses/exams/tests.py +++ b/main/courses/exams/tests.py @@ -429,17 +429,17 @@ def test_resubmission_and_late_penalty(self): self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 15, True, 150), 0)) self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 150, True, 50), 0)) - def test_hourly_penalty(self): + def test_daily_penalty(self): """Unit test for the discount function """ - #Only hourly penalty and late penalty - self.assertTrue(self.float_compare(compute_penalties(100, 1, 0, False, 0, late_hours=0, hourly_late_penalty=0), 100)) - self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 0, late_hours=3, hourly_late_penalty=10), 100*.9*.9*.9)) - self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 50, late_hours=3, hourly_late_penalty=10), 100*.5*.9*.9*.9)) - self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, False, 50, late_hours=3, hourly_late_penalty=10), 100)) - self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 0, late_hours=3, hourly_late_penalty=110), 0)) + #Only daily penalty and late penalty + self.assertTrue(self.float_compare(compute_penalties(100, 1, 0, False, 0, late_days=0, daily_late_penalty=0), 100)) + self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 0, late_days=3, daily_late_penalty=10), 100*.9*.9*.9)) + self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 50, late_days=3, daily_late_penalty=10), 100*.5*.9*.9*.9)) + self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, False, 50, late_days=3, daily_late_penalty=10), 100)) + self.assertTrue(self.float_compare(compute_penalties(100.0, 1, 0, True, 0, late_days=3, daily_late_penalty=110), 0)) #all penalties - self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 15, True, 50, late_hours=3, hourly_late_penalty=10), 100*.85*.85*.5*.9*.9*.9)) + self.assertTrue(self.float_compare(compute_penalties(100.0, 3, 15, True, 50, late_days=3, daily_late_penalty=10), 100*.85*.85*.5*.9*.9*.9)) def test_regex_metadata_errors(self): """ diff --git a/main/courses/exams/views.py b/main/courses/exams/views.py index 9ddd55da..6fbafa17 100644 --- a/main/courses/exams/views.py +++ b/main/courses/exams/views.py @@ -615,8 +615,8 @@ def collect_data(request, course_prefix, course_suffix, exam_slug): #apply penalties late_timedelta = max(datetime.timedelta(0), record.time_created - exam.grace_period) - hours_late = math.ceil(late_timedelta.total_seconds() / 3600) - record.score = compute_penalties(total_score, attempt_number, exam.resubmission_penalty, record.late, exam.late_penalty, late_hours=hours_late, hourly_late_penalty = exam.hourly_late_penalty) + days_late = math.ceil(late_timedelta.total_seconds() / (3600.0*24.0)) + record.score = compute_penalties(total_score, attempt_number, exam.resubmission_penalty, record.late, exam.late_penalty, late_days=days_late, daily_late_penalty = exam.daily_late_penalty) record.save() #Set ExamScore.score to max of ExamRecord.score for that student, exam. @@ -629,7 +629,7 @@ def collect_data(request, course_prefix, course_suffix, exam_slug): return HttpResponse("Submission has been saved.") -def compute_penalties(raw_score, attempt_number, resubmission_penalty, is_late, late_penalty, late_hours=0, hourly_late_penalty=0): +def compute_penalties(raw_score, attempt_number, resubmission_penalty, is_late, late_penalty, late_days=0, daily_late_penalty=0): """Helper function to factor out resubmission and late penalty calculations, so I can write a few unit tests for it """ @@ -638,9 +638,9 @@ def compute_penalties(raw_score, attempt_number, resubmission_penalty, is_late, late_discount = max(0.0, 100.0 - late_penalty)/100.0 if is_late: score *= late_discount - if late_hours: - hourly_discount = pow(max(0.0, (100.0 - hourly_late_penalty)/100.0), late_hours) - score *= hourly_discount + if late_days: + daily_discount = pow(max(0.0, (100.0 - daily_late_penalty)/100.0), late_days) + score *= daily_discount return max(score, 0.0) @require_POST @@ -670,7 +670,7 @@ def save_exam_ajax(request, course_prefix, course_suffix, create_or_edit="create grace_period = request.POST.get('grace_period', '') partial_credit_deadline = request.POST.get('partial_credit_deadline', '') late_penalty = request.POST.get('late_penalty', '') - hourly_late_penalty = request.POST.get('hourly_late_penalty', '') + daily_late_penalty = request.POST.get('daily_late_penalty', '') num_subs_permitted = request.POST.get('num_subs_permitted','') resubmission_penalty = request.POST.get('resubmission_penalty','') assessment_type = request.POST.get('assessment_type','') @@ -779,13 +779,13 @@ def save_exam_ajax(request, course_prefix, course_suffix, create_or_edit="create except ValueError: return HttpResponseBadRequest("A non-numeric late penalty (" + late_penalty + ") was provided") - if not hourly_late_penalty: - hlp = 0 + if not daily_late_penalty: + dlp = 0 else: try: - hlp = int(hourly_late_penalty) + dlp = int(daily_late_penalty) except ValueError: - return HttpResponseBadRequest("A non-numeric hourly late penalty (" + hourly_late_penalty + ") was provided") + return HttpResponseBadRequest("A non-numeric daily late penalty (" + daily_late_penalty + ") was provided") if not num_subs_permitted: @@ -808,7 +808,7 @@ def save_exam_ajax(request, course_prefix, course_suffix, create_or_edit="create if create_or_edit == "create": exam_obj = Exam(course=course, slug=slug, title=title, description=description, html_content=htmlContent, xml_metadata=metaXMLContent, due_date=dd, assessment_type=assessment_type, mode="draft", total_score=total_score, grade_single=grade_single, - grace_period=gp, partial_credit_deadline=pcd, late_penalty=lp, hourly_late_penalty=hlp, submissions_permitted=sp, + grace_period=gp, partial_credit_deadline=pcd, late_penalty=lp, daily_late_penalty=dlp, submissions_permitted=sp, resubmission_penalty=rp, exam_type=exam_type, autograde=autograde, display_single=display_single, invideo=invideo, section=contentsection, xml_imported=xmlImported, quizdown=quizdown, hide_grades=hide_grades @@ -854,7 +854,7 @@ def save_exam_ajax(request, course_prefix, course_suffix, create_or_edit="create exam_obj.grace_period=gp exam_obj.partial_credit_deadline=pcd exam_obj.late_penalty=lp - exam_obj.hourly_late_penalty=hlp + exam_obj.daily_late_penalty=dlp exam_obj.submissions_permitted=sp exam_obj.resubmission_penalty=rp exam_obj.exam_type=exam_type @@ -942,7 +942,7 @@ def edit_exam(request, course_prefix, course_suffix, exam_slug): 'assessment_type':exam.assessment_type, 'late_penalty':exam.late_penalty, 'num_subs_permitted':exam.submissions_permitted, 'resubmission_penalty':exam.resubmission_penalty, 'description':exam.description, 'section':exam.section.id,'invideo':exam.invideo, 'metadata':exam.xml_metadata, 'htmlContent':exam.html_content, 'xmlImported':exam.xml_imported, 'quizdown':exam.quizdown, - 'hide_grades':exam.hide_grades, 'hourly_late_penalty':exam.hourly_late_penalty} + 'hide_grades':exam.hide_grades, 'daily_late_penalty':exam.daily_late_penalty} groupable_exam = exam if exam.mode != 'ready': diff --git a/main/templates/exams/create_exam.html b/main/templates/exams/create_exam.html index 8206f3db..00f4eb40 100644 --- a/main/templates/exams/create_exam.html +++ b/main/templates/exams/create_exam.html @@ -155,8 +155,8 @@

- - + + @@ -238,6 +238,7 @@

{% trans 'Create Exam' %}

grace-period: 12/29/2013 00:00 hard-deadline: 12/29/2013 00:00 late-penalty: 10 +daily-late-penalty: 0 num-subs-permitted: 9999 resubmission-penalty: 10 section: Test Videos @@ -531,7 +532,7 @@

This is the problem set description. - +

@@ -995,7 +996,7 @@

partial_credit_deadline:$('input#hard_deadline').val(), assessment_type:$('select#assessment_type').val(), late_penalty:$('input#late_penalty').val(), - hourly_late_penalty:$('input#hourly_late_penalty').val(), + daily_late_penalty:$('input#daily_late_penalty').val(), num_subs_permitted:$('input#num_subs_permitted').val(), resubmission_penalty:$('input#resubmission_penalty').val(), description:$('textarea#description').val(), @@ -1073,7 +1074,7 @@

$('input#hard_deadline').val(prepop.partial_credit_deadline); $('select#assessment_type').val(prepop.assessment_type); $('input#late_penalty').val(prepop.late_penalty); - $('input#hourly_late_penalty').val(prepop.hourly_late_penalty); + $('input#daily_late_penalty').val(prepop.daily_late_penalty); $('input#num_subs_permitted').val(prepop.num_subs_permitted); $('input#resubmission_penalty').val(prepop.resubmission_penalty); $('textarea#description').val(prepop.description); diff --git a/main/templates/exams/quizdown.js b/main/templates/exams/quizdown.js index 64344151..39370634 100644 --- a/main/templates/exams/quizdown.js +++ b/main/templates/exams/quizdown.js @@ -72,7 +72,7 @@ c2gXMLParse.importMeta = function (meta) { k == "late_penalty" || kslug == "late_penalty" || k == "num_subs_permitted" || kslug == "num_subs_permitted" || k == "resubmission_penalty" || kslug == "resubmission_penalty" || - k == "description") { + k == "description" || kslug == "daily_late_penalty") { $('#'+kslug).val(value); } else if (k == "invideo" && value.toLowerCase() != "false") { diff --git a/main/templates/exams/ready/exam_list_item.html b/main/templates/exams/ready/exam_list_item.html index 22fd1cb5..520ba611 100644 --- a/main/templates/exams/ready/exam_list_item.html +++ b/main/templates/exams/ready/exam_list_item.html @@ -43,7 +43,12 @@

{% if record.late or record.attempt_number > 1 and exam.resubmission_penalty > 0 %} ( {% trans 'Raw score' %}: {{ record.examrecordscore.raw_score }} with {% if record.late %} - {{exam.late_penalty}}% {% trans 'late penalty' %} + {% if exam.late_penalty %} + {{exam.late_penalty}}% {% trans 'late penalty' %} + {% endif %} + {% if exam.daily_late_penalty %} + {{exam.daily_late_penalty}}% {% trans 'late penalty (per day)' %} + {% endif %} {% if record.attempt_number > 1 and exam.resubmission_penalty > 0 %} {% trans 'and' %} {% endif %} diff --git a/main/templates/exams/renderPreview.js b/main/templates/exams/renderPreview.js index 886a70e1..c88fa5d5 100644 --- a/main/templates/exams/renderPreview.js +++ b/main/templates/exams/renderPreview.js @@ -584,6 +584,7 @@ var c2gXMLParse = (function() { if (gradingDOM.length) { setValIfDef($('input#late_penalty'), $(gradingDOM).attr('late-penalty')); + setValIfDef($('input#daily_late_penalty'), $(gradingDOM).attr('daily-late-penalty')); setValIfDef($('input#num_subs_permitted'), $(gradingDOM).attr('num-submissions')); setValIfDef($('input#resubmission_penalty'), $(gradingDOM).attr('resubmission-penalty'));