From 6d50044ec411faa6975f2c8bc21ef6f80a260bb5 Mon Sep 17 00:00:00 2001 From: Sanford Student Date: Tue, 8 Nov 2016 16:45:30 -0500 Subject: [PATCH] adding first attempted timestamp to subsection grades for TNL-5895 --- ...rsistentsubsectiongrade_first_attempted.py | 19 ++++++++++++++++ lms/djangoapps/grades/models.py | 10 ++++++++- lms/djangoapps/grades/tests/test_models.py | 22 +++++++++++++++++-- 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 lms/djangoapps/grades/migrations/0008_persistentsubsectiongrade_first_attempted.py diff --git a/lms/djangoapps/grades/migrations/0008_persistentsubsectiongrade_first_attempted.py b/lms/djangoapps/grades/migrations/0008_persistentsubsectiongrade_first_attempted.py new file mode 100644 index 0000000000..6035ce6566 --- /dev/null +++ b/lms/djangoapps/grades/migrations/0008_persistentsubsectiongrade_first_attempted.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('grades', '0007_add_passed_timestamp_column'), + ] + + operations = [ + migrations.AddField( + model_name='persistentsubsectiongrade', + name='first_attempted', + field=models.DateTimeField(null=True, blank=True), + ), + ] diff --git a/lms/djangoapps/grades/models.py b/lms/djangoapps/grades/models.py index a870942e8f..186ad3930d 100644 --- a/lms/djangoapps/grades/models.py +++ b/lms/djangoapps/grades/models.py @@ -236,6 +236,11 @@ class PersistentSubsectionGrade(TimeStampedModel): earned_graded = models.FloatField(blank=False) possible_graded = models.FloatField(blank=False) + # timestamp for the learner's first attempt at content in + # this subsection. If null, indicates no attempt + # has yet been made. + first_attempted = models.DateTimeField(null=True, blank=True) + # track which blocks were visible at the time of grade calculation visible_blocks = models.ForeignKey(VisibleBlocks, db_column='visible_blocks_hash', to_field='hashed') @@ -253,7 +258,9 @@ class PersistentSubsectionGrade(TimeStampedModel): """ Returns a string representation of this model. """ - return u"{} user: {}, course version: {}, subsection {} ({}). {}/{} graded, {}/{} all".format( + return ( + u"{} user: {}, course version: {}, subsection: {} ({}). {}/{} graded, {}/{} all, first_attempted: {}" + ).format( type(self).__name__, self.user_id, self.course_version, @@ -263,6 +270,7 @@ class PersistentSubsectionGrade(TimeStampedModel): self.possible_graded, self.earned_all, self.possible_all, + self.first_attempted, ) @classmethod diff --git a/lms/djangoapps/grades/tests/test_models.py b/lms/djangoapps/grades/tests/test_models.py index 37aacdefd6..c31f54a4fe 100644 --- a/lms/djangoapps/grades/tests/test_models.py +++ b/lms/djangoapps/grades/tests/test_models.py @@ -210,6 +210,7 @@ class PersistentSubsectionGradeTest(GradesModelTestCase): "earned_graded": 6.0, "possible_graded": 8.0, "visible_blocks": self.block_records, + "first_attempted": "2016-08-01 18:53:24.354741", } def test_create(self): @@ -235,10 +236,27 @@ class PersistentSubsectionGradeTest(GradesModelTestCase): with self.assertRaises(IntegrityError): PersistentSubsectionGrade.create_grade(**self.params) - def test_course_version_is_optional(self): - del self.params["course_version"] + @ddt.data("course_version", "first_attempted") + def test_optional_fields(self, field): + del self.params[field] PersistentSubsectionGrade.create_grade(**self.params) + @ddt.data( + ("user_id", IntegrityError), + ("usage_key", KeyError), + ("subtree_edited_timestamp", IntegrityError), + ("earned_all", IntegrityError), + ("possible_all", IntegrityError), + ("earned_graded", IntegrityError), + ("possible_graded", IntegrityError), + ("visible_blocks", KeyError), + ) + @ddt.unpack + def test_non_optional_fields(self, field, error): + del self.params[field] + with self.assertRaises(error): + PersistentSubsectionGrade.create_grade(**self.params) + @ddt.data(True, False) def test_update_or_create_grade(self, already_created): created_grade = PersistentSubsectionGrade.create_grade(**self.params) if already_created else None