fixup: Error instead of correcting on invalid inputs.

This commit is contained in:
J. Cliff Dyer
2016-11-22 11:48:33 -05:00
parent b6305a1553
commit 86c12e67cc
4 changed files with 39 additions and 57 deletions

View File

@@ -15,6 +15,7 @@ import json
from lazy import lazy
import logging
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.timezone import now
from model_utils.models import TimeStampedModel
@@ -251,27 +252,13 @@ class PersistentSubsectionGrade(TimeStampedModel):
"""
return self.first_attempted is None and any(field != 0.0 for field in (self.earned_all, self.earned_graded))
def enforce_unattempted(self, save=True):
def clean(self):
"""
If an grade has not been attempted, but was given a non-zero score,
reset the score to 0.0.
Params:
save (bool, default: True):
By default, this method saves the model if and only if there was an
inconsistency. If the caller needs to save the model regardless of
the result, or will be saving the model later after making other
changes, this may be an unwanted database request. It can be
disabled by passing ``save=False``.
Return value: None
raise a ValidationError.
"""
if self._is_unattempted_with_score():
self.earned_all = 0.0
self.earned_graded = 0.0
if save:
self.save()
raise ValidationError("Unattempted problems cannot have a non-zero score.")
@property
def full_usage_key(self):
@@ -343,7 +330,6 @@ class PersistentSubsectionGrade(TimeStampedModel):
user_id = kwargs.pop('user_id')
usage_key = kwargs.pop('usage_key')
attempted = kwargs.pop('attempted')
grade, _ = cls.objects.update_or_create(
user_id=user_id,
course_id=usage_key.course_key,
@@ -352,9 +338,8 @@ class PersistentSubsectionGrade(TimeStampedModel):
)
if attempted and not grade.first_attempted:
grade.first_attempted = now()
grade.save()
else:
grade.enforce_unattempted()
grade.full_clean()
grade.save()
return grade
@classmethod
@@ -369,7 +354,7 @@ class PersistentSubsectionGrade(TimeStampedModel):
grade = cls(**kwargs)
if attempted:
grade.first_attempted = now()
grade.enforce_unattempted(save=False)
grade.full_clean()
grade.save()
return grade
@@ -389,6 +374,8 @@ class PersistentSubsectionGrade(TimeStampedModel):
for params in grade_params_iter:
if params.pop('attempted'):
params['first_attempted'] = first_attempt_timestamp
elif params['earned_all'] != 0.0 or params['earned_graded'] != 0.0:
raise ValidationError("Unattempted problems cannot have a non-zero score.")
return cls.objects.bulk_create([PersistentSubsectionGrade(**params) for params in grade_params_iter])
@classmethod
@@ -423,14 +410,6 @@ class PersistentSubsectionGrade(TimeStampedModel):
params['visible_blocks_id'] = params['visible_blocks'].hash_value
del params['visible_blocks']
def remove_attempts(self):
"""
Explicitly mark a subsection as unattempted
"""
self.first_attempted = None
self.enforce_unattempted(save=False)
self.save()
class PersistentCourseGrade(TimeStampedModel):
"""

View File

@@ -8,6 +8,7 @@ import ddt
from hashlib import sha1
import json
from django.core.exceptions import ValidationError
from django.db.utils import IntegrityError
from django.test import TestCase
from django.utils.timezone import now
@@ -225,7 +226,7 @@ class PersistentSubsectionGradeTest(GradesModelTestCase):
)
self.assertEqual(created_grade, read_grade)
self.assertEqual(read_grade.visible_blocks.blocks, self.block_records)
with self.assertRaises(IntegrityError):
with self.assertRaises(ValidationError):
PersistentSubsectionGrade.create_grade(**self.params)
def test_optional_fields(self):
@@ -233,13 +234,13 @@ class PersistentSubsectionGradeTest(GradesModelTestCase):
PersistentSubsectionGrade.create_grade(**self.params)
@ddt.data(
("user_id", IntegrityError),
("user_id", ValidationError),
("usage_key", KeyError),
("subtree_edited_timestamp", IntegrityError),
("earned_all", IntegrityError),
("possible_all", IntegrityError),
("earned_graded", IntegrityError),
("possible_graded", IntegrityError),
("subtree_edited_timestamp", ValidationError),
("earned_all", ValidationError),
("possible_all", ValidationError),
("earned_graded", ValidationError),
("possible_graded", ValidationError),
("visible_blocks", KeyError),
("attempted", KeyError),
)
@@ -260,20 +261,30 @@ class PersistentSubsectionGradeTest(GradesModelTestCase):
self.assertEqual(created_grade.id, updated_grade.id)
self.assertEqual(created_grade.earned_all, 6)
def test_update_or_create_with_implicit_attempted(self):
def test_update_or_create_attempted(self):
grade = PersistentSubsectionGrade.update_or_create_grade(**self.params)
self.assertIsInstance(grade.first_attempted, datetime)
def test_unattempted(self):
self.params['attempted'] = False
self.params['earned_all'] = 0.0
self.params['earned_graded'] = 0.0
grade = PersistentSubsectionGrade.create_grade(**self.params)
self.assertIsNone(grade.first_attempted)
self.assertEqual(grade.earned_all, 0.0)
self.assertEqual(grade.earned_graded, 0.0)
def test_create_inconsistent_unattempted(self):
self.params['attempted'] = False
grade = PersistentSubsectionGrade.create_grade(**self.params)
self.assertEqual(grade.earned_all, 0.0)
with self.assertRaises(ValidationError):
PersistentSubsectionGrade.create_grade(**self.params)
def test_update_inconsistent_unattempted(self):
def test_update_or_create_inconsistent_unattempted(self):
self.params['attempted'] = False
PersistentSubsectionGrade.create_grade(**self.params)
grade = PersistentSubsectionGrade.update_or_create_grade(**self.params)
self.assertEqual(grade.earned_all, 0.0)
self.params['earned_all'] = 1.0
self.params['earned_graded'] = 1.0
with self.assertRaises(ValidationError):
PersistentSubsectionGrade.update_or_create_grade(**self.params)
def test_first_attempted_not_changed_on_update(self):
PersistentSubsectionGrade.create_grade(**self.params)
@@ -283,19 +294,11 @@ class PersistentSubsectionGradeTest(GradesModelTestCase):
def test_unattempted_save_does_not_remove_attempt(self):
PersistentSubsectionGrade.create_grade(**self.params)
self.params['unattempted'] = False
self.params['attempted'] = False
grade = PersistentSubsectionGrade.update_or_create_grade(**self.params)
self.assertIsInstance(grade.first_attempted, datetime)
self.assertEqual(grade.earned_all, 6.0)
def test_explicitly_remove_attempts(self):
grade = PersistentSubsectionGrade.create_grade(**self.params)
self.assertIsInstance(grade.first_attempted, datetime)
self.assertEqual(grade.earned_all, 6.0)
grade.remove_attempts()
self.assertIsNone(grade.first_attempted)
self.assertEqual(grade.earned_all, 0.0)
@ddt.ddt
class PersistentCourseGradesTest(GradesModelTestCase):

View File

@@ -214,7 +214,7 @@ class TestSubsectionGradeFactory(ProblemSubmissionTestMixin, GradeTestBase):
'lms.djangoapps.grades.new.subsection_grade.SubsectionGradeFactory._get_bulk_cached_grade',
wraps=self.subsection_grade_factory._get_bulk_cached_grade
) as mock_get_bulk_cached_grade:
with self.assertNumQueries(12):
with self.assertNumQueries(14):
grade_a = self.subsection_grade_factory.create(self.sequence)
self.assertTrue(mock_get_bulk_cached_grade.called)
self.assertTrue(mock_create_grade.called)

View File

@@ -113,7 +113,7 @@ class RecalculateSubsectionGradeTest(ModuleStoreTestCase):
with self.store.default_store(default_store):
self.set_up_course()
self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
with check_mongo_calls(2) and self.assertNumQueries(20 + added_queries):
with check_mongo_calls(2) and self.assertNumQueries(23 + added_queries):
self._apply_recalculate_subsection_grade()
@patch('lms.djangoapps.grades.signals.signals.SUBSECTION_SCORE_CHANGED.send')
@@ -161,7 +161,7 @@ class RecalculateSubsectionGradeTest(ModuleStoreTestCase):
self.assertTrue(PersistentGradesEnabledFlag.feature_enabled(self.course.id))
ItemFactory.create(parent=self.sequential, category='problem', display_name='problem2')
ItemFactory.create(parent=self.sequential, category='problem', display_name='problem3')
with check_mongo_calls(2) and self.assertNumQueries(20 + added_queries):
with check_mongo_calls(2) and self.assertNumQueries(23 + added_queries):
self._apply_recalculate_subsection_grade()
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
@@ -172,7 +172,7 @@ class RecalculateSubsectionGradeTest(ModuleStoreTestCase):
with check_mongo_calls(2) and self.assertNumQueries(0):
self._apply_recalculate_subsection_grade()
@skip("Pending completion of TNL-5089")
#@skip("Pending completion of TNL-5089")
@ddt.data(
(ModuleStoreEnum.Type.mongo, True),
(ModuleStoreEnum.Type.split, True),