diff --git a/openedx/core/djangoapps/content/course_overviews/migrations/0004_default_lowest_passing_grade_to_None.py b/openedx/core/djangoapps/content/course_overviews/migrations/0004_default_lowest_passing_grade_to_None.py new file mode 100644 index 0000000000..0876dbe747 --- /dev/null +++ b/openedx/core/djangoapps/content/course_overviews/migrations/0004_default_lowest_passing_grade_to_None.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'CourseOverview.lowest_passing_grade' + db.alter_column('course_overviews_courseoverview', 'lowest_passing_grade', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=5, decimal_places=2)) + + def backwards(self, orm): + + # Changing field 'CourseOverview.lowest_passing_grade' + db.alter_column('course_overviews_courseoverview', 'lowest_passing_grade', self.gf('django.db.models.fields.DecimalField')(default=0.5, max_digits=5, decimal_places=2)) + + models = { + 'course_overviews.courseoverview': { + 'Meta': {'object_name': 'CourseOverview'}, + '_location': ('xmodule_django.models.UsageKeyField', [], {'max_length': '255'}), + '_pre_requisite_courses_json': ('django.db.models.fields.TextField', [], {}), + 'advertised_start': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'cert_html_view_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'cert_name_long': ('django.db.models.fields.TextField', [], {}), + 'cert_name_short': ('django.db.models.fields.TextField', [], {}), + 'certificates_display_behavior': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'certificates_show_before_end': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'course_image_url': ('django.db.models.fields.TextField', [], {}), + 'days_early_for_beta': ('django.db.models.fields.FloatField', [], {'null': 'True'}), + 'display_name': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'display_number_with_default': ('django.db.models.fields.TextField', [], {}), + 'display_org_with_default': ('django.db.models.fields.TextField', [], {}), + 'end': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'end_of_course_survey_url': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'facebook_url': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'has_any_active_web_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'primary_key': 'True', 'db_index': 'True'}), + 'lowest_passing_grade': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '5', 'decimal_places': '2'}), + 'mobile_available': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'social_sharing_url': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'start': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'visible_to_staff_only': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + } + } + + complete_apps = ['course_overviews'] \ No newline at end of file diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py index 14387a8d96..4b8429f9cc 100644 --- a/openedx/core/djangoapps/content/course_overviews/models.py +++ b/openedx/core/djangoapps/content/course_overviews/models.py @@ -52,7 +52,7 @@ class CourseOverview(django.db.models.Model): cert_name_long = TextField() # Grading - lowest_passing_grade = DecimalField(max_digits=5, decimal_places=2) + lowest_passing_grade = DecimalField(max_digits=5, decimal_places=2, null=True) # Access parameters days_early_for_beta = FloatField(null=True) @@ -77,6 +77,16 @@ class CourseOverview(django.db.models.Model): from lms.djangoapps.certificates.api import get_active_web_certificate from lms.djangoapps.courseware.courses import course_image_url + # Workaround for a problem discovered in https://openedx.atlassian.net/browse/TNL-2806. + # If the course has a malformed grading policy such that + # course._grading_policy['GRADE_CUTOFFS'] = {}, then + # course.lowest_passing_grade will raise a ValueError. + # Work around this for now by defaulting to None. + try: + lowest_passing_grade = course.lowest_passing_grade + except ValueError: + lowest_passing_grade = None + return CourseOverview( id=course.id, _location=course.location, @@ -98,7 +108,7 @@ class CourseOverview(django.db.models.Model): has_any_active_web_certificate=(get_active_web_certificate(course) is not None), cert_name_short=course.cert_name_short, cert_name_long=course.cert_name_long, - lowest_passing_grade=course.lowest_passing_grade, + lowest_passing_grade=lowest_passing_grade, end_of_course_survey_url=course.end_of_course_survey_url, days_early_for_beta=course.days_early_for_beta, diff --git a/openedx/core/djangoapps/content/course_overviews/tests.py b/openedx/core/djangoapps/content/course_overviews/tests.py index dc87856ae6..8a56bb5172 100644 --- a/openedx/core/djangoapps/content/course_overviews/tests.py +++ b/openedx/core/djangoapps/content/course_overviews/tests.py @@ -294,3 +294,18 @@ class CourseOverviewTestCase(ModuleStoreTestCase): # which causes get_from_id to raise an IOError. with self.assertRaises(IOError): CourseOverview.get_from_id(course.id) + + def test_malformed_grading_policy(self): + """ + Test that CourseOverview handles courses with a malformed grading policy + such that course._grading_policy['GRADE_CUTOFFS'] = {} by defaulting + .lowest_passing_grade to None. + + Created in response to https://openedx.atlassian.net/browse/TNL-2806. + """ + course = CourseFactory.create() + course._grading_policy['GRADE_CUTOFFS'] = {} # pylint: disable=protected-access + with self.assertRaises(ValueError): + __ = course.lowest_passing_grade + course_overview = CourseOverview._create_from_course(course) # pylint: disable=protected-access + self.assertEqual(course_overview.lowest_passing_grade, None)