MA-779 Update student dashboard to use CourseOverview
This commit is contained in:
@@ -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']
|
||||
@@ -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='='):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user