MA-779 Update student dashboard to use CourseOverview

This commit is contained in:
Kyle McCormick
2015-07-07 11:29:37 -04:00
parent 5b630a7704
commit 1dfe9ed9e8
15 changed files with 471 additions and 245 deletions

View File

@@ -0,0 +1,58 @@
# -*- 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):
# Adding field 'CourseOverview.cert_html_view_enabled'
# The default value for the cert_html_view_eanbled column is False.
# However, for courses in the table for which cert_html_view_enabled
# should be True, this would be invalid. So, we must clear the
# table before adding the new column.
db.clear_table('course_overviews_courseoverview')
db.add_column('course_overviews_courseoverview', 'cert_html_view_enabled',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
def backwards(self, orm):
# Deleting field 'CourseOverview.cert_html_view_enabled'
db.delete_column('course_overviews_courseoverview', 'cert_html_view_enabled')
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', [], {'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']

View File

@@ -10,6 +10,8 @@ from django.utils.translation import ugettext
from util.date_utils import strftime_localized
from xmodule import course_metadata_utils
from xmodule.course_module import CourseDescriptor
from xmodule.error_module import ErrorDescriptor
from xmodule.modulestore.django import modulestore
from xmodule_django.models import CourseKeyField, UsageKeyField
@@ -44,6 +46,7 @@ class CourseOverview(django.db.models.Model):
# Certification data
certificates_display_behavior = TextField(null=True)
certificates_show_before_end = BooleanField()
cert_html_view_enabled = BooleanField()
has_any_active_web_certificate = BooleanField()
cert_name_short = TextField()
cert_name_long = TextField()
@@ -91,6 +94,7 @@ class CourseOverview(django.db.models.Model):
certificates_display_behavior=course.certificates_display_behavior,
certificates_show_before_end=course.certificates_show_before_end,
cert_html_view_enabled=course.cert_html_view_enabled,
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,
@@ -114,10 +118,17 @@ class CourseOverview(django.db.models.Model):
future use.
Arguments:
course_id (CourseKey): the ID of the course overview to be loaded
course_id (CourseKey): the ID of the course overview to be loaded.
Returns:
CourseOverview: overview of the requested course
CourseOverview: overview of the requested course. If loading course
from the module store failed, returns None.
Raises:
- CourseOverview.DoesNotExist if the course specified by course_id
was not found.
- IOError if some other error occurs while trying to load the
course from the module store.
"""
course_overview = None
try:
@@ -126,9 +137,17 @@ class CourseOverview(django.db.models.Model):
store = modulestore()
with store.bulk_operations(course_id):
course = store.get_course(course_id)
if course:
if isinstance(course, CourseDescriptor):
course_overview = CourseOverview._create_from_course(course)
course_overview.save() # Save new overview to the cache
course_overview.save()
elif course is not None:
raise IOError(
"Error while loading course {} from the module store: {}",
unicode(course_id),
course.error_msg if isinstance(course, ErrorDescriptor) else unicode(course)
)
else:
raise CourseOverview.DoesNotExist()
return course_overview
def clean_id(self, padding_char='='):

View File

@@ -6,13 +6,16 @@ import ddt
import itertools
import pytz
import math
import mock
from django.utils import timezone
from lms.djangoapps.certificates.api import get_active_web_certificate
from lms.djangoapps.courseware.courses import course_image_url
from xmodule.course_metadata_utils import DEFAULT_START_DATE
from xmodule.error_module import ErrorDescriptor
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls, check_mongo_calls_range
@@ -41,12 +44,18 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
- the CourseDescriptor itself
- a CourseOverview that was newly constructed from _create_from_course
- a CourseOverview that was loaded from the MySQL database
Arguments:
course (CourseDescriptor): the course to be checked.
"""
def get_seconds_since_epoch(date_time):
"""
Returns the number of seconds between the Unix Epoch and the given
datetime. If the given datetime is None, return None.
Arguments:
date_time (datetime): the datetime in question.
"""
if date_time is None:
return None
@@ -189,18 +198,14 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
by comparing pairs of them given a variety of scenarios.
Arguments:
course_kwargs (dict): kwargs to be passed to course constructor
modulestore_type (ModuleStoreEnum.Type)
is_user_enrolled (bool)
course_kwargs (dict): kwargs to be passed to course constructor.
modulestore_type (ModuleStoreEnum.Type): type of store to create the
course in.
"""
course = CourseFactory.create(
course="TEST101",
org="edX",
run="Run1",
default_store=modulestore_type,
**course_kwargs
)
# Note: We specify a value for 'run' here because, for some reason,
# .create raises an InvalidKeyError if we don't (even though my
# other test functions don't specify a run but work fine).
course = CourseFactory.create(default_store=modulestore_type, run="TestRun", **course_kwargs)
self.check_course_overview_against_course(course)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
@@ -208,17 +213,15 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
"""
Tests that when a course is published, the corresponding
course_overview is removed from the cache.
Arguments:
modulestore_type (ModuleStoreEnum.Type): type of store to create the
course in.
"""
with self.store.default_store(modulestore_type):
# Create a course where mobile_available is True.
course = CourseFactory.create(
course="TEST101",
org="edX",
run="Run1",
mobile_available=True,
default_store=modulestore_type
)
course = CourseFactory.create(mobile_available=True, default_store=modulestore_type)
course_overview_1 = CourseOverview.get_from_id(course.id)
self.assertTrue(course_overview_1.mobile_available)
@@ -238,14 +241,16 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
def test_course_overview_caching(self, modulestore_type, min_mongo_calls, max_mongo_calls):
"""
Tests that CourseOverview structures are actually getting cached.
Arguments:
modulestore_type (ModuleStoreEnum.Type): type of store to create the
course in.
min_mongo_calls (int): minimum number of MongoDB queries we expect
to be made.
max_mongo_calls (int): maximum number of MongoDB queries we expect
to be made.
"""
course = CourseFactory.create(
course="TEST101",
org="edX",
run="Run1",
mobile_available=True,
default_store=modulestore_type
)
course = CourseFactory.create(default_store=modulestore_type)
# The first time we load a CourseOverview, it will be a cache miss, so
# we expect the modulestore to be queried.
@@ -256,3 +261,36 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
# we expect no modulestore queries to be made.
with check_mongo_calls(0):
_course_overview_2 = CourseOverview.get_from_id(course.id)
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
def test_get_non_existent_course(self, modulestore_type):
"""
Tests that requesting a non-existent course from get_from_id raises
CourseOverview.DoesNotExist.
Arguments:
modulestore_type (ModuleStoreEnum.Type): type of store to create the
course in.
"""
store = modulestore()._get_modulestore_by_type(modulestore_type) # pylint: disable=protected-access
with self.assertRaises(CourseOverview.DoesNotExist):
CourseOverview.get_from_id(store.make_course_key('Non', 'Existent', 'Course'))
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
def test_get_errored_course(self, modulestore_type):
"""
Test that getting an ErrorDescriptor back from the module store causes
get_from_id to raise an IOError.
Arguments:
modulestore_type (ModuleStoreEnum.Type): type of store to create the
course in.
"""
course = CourseFactory.create(default_store=modulestore_type)
mock_get_course = mock.Mock(return_value=ErrorDescriptor)
with mock.patch('xmodule.modulestore.mixed.MixedModuleStore.get_course', mock_get_course):
# This mock makes it so when the module store tries to load course data,
# an exception is thrown, which causes get_course to return an ErrorDescriptor,
# which causes get_from_id to raise an IOError.
with self.assertRaises(IOError):
CourseOverview.get_from_id(course.id)