Commit 3be2737f by Peter Pinch

Merge pull request #11356 from mitocw/gdm_ccx_course_modules_#170

Added extra field to CCX model for Course Models
parents c913734a 0c637cdc
......@@ -17,6 +17,7 @@ class CCXCourseSerializer(serializers.ModelSerializer):
start = serializers.CharField(allow_blank=True)
due = serializers.CharField(allow_blank=True)
max_students_allowed = serializers.IntegerField(source='max_student_enrollments_allowed')
course_modules = serializers.SerializerMethodField()
class Meta(object):
model = CustomCourseForEdX
......@@ -28,6 +29,7 @@ class CCXCourseSerializer(serializers.ModelSerializer):
"start",
"due",
"max_students_allowed",
"course_modules",
)
read_only_fields = (
"ccx_course_id",
......@@ -42,3 +44,10 @@ class CCXCourseSerializer(serializers.ModelSerializer):
Getter for the CCX Course ID
"""
return unicode(CCXLocator.from_course_locator(obj.course.id, obj.id))
@staticmethod
def get_course_modules(obj):
"""
Getter for the Course Modules. The list is stored in a compressed field.
"""
return obj.structure or []
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ccx', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='customcourseforedx',
name='structure_json',
field=models.TextField(null=True, verbose_name=b'Structure JSON', blank=True),
),
]
"""
Models for the custom course feature
"""
from datetime import datetime
import json
import logging
from datetime import datetime
from django.contrib.auth.models import User
from django.db import models
......@@ -24,6 +25,9 @@ class CustomCourseForEdX(models.Model):
course_id = CourseKeyField(max_length=255, db_index=True)
display_name = models.CharField(max_length=255)
coach = models.ForeignKey(User, db_index=True)
# if not empty, this field contains a json serialized list of
# the master course modules
structure_json = models.TextField(verbose_name='Structure JSON', blank=True, null=True)
class Meta(object):
app_label = 'ccx'
......@@ -107,6 +111,15 @@ class CustomCourseForEdX(models.Model):
value += u' UTC'
return value
@property
def structure(self):
"""
Deserializes a course structure JSON object
"""
if self.structure_json:
return json.loads(self.structure_json)
return None
class CcxFieldOverride(models.Model):
"""
......
"""
tests for the models
"""
import json
from datetime import datetime, timedelta
from django.utils.timezone import UTC
from mock import patch
......@@ -30,11 +31,11 @@ class TestCCX(ModuleStoreTestCase):
def setUp(self):
"""common setup for all tests"""
super(TestCCX, self).setUp()
self.course = course = CourseFactory.create()
coach = AdminFactory.create()
role = CourseCcxCoachRole(course.id)
role.add_users(coach)
self.ccx = CcxFactory(course_id=course.id, coach=coach)
self.course = CourseFactory.create()
self.coach = AdminFactory.create()
role = CourseCcxCoachRole(self.course.id)
role.add_users(self.coach)
self.ccx = CcxFactory(course_id=self.course.id, coach=self.coach)
def set_ccx_override(self, field, value):
"""Create a field override for the test CCX on <field> with <value>"""
......@@ -209,3 +210,28 @@ class TestCCX(ModuleStoreTestCase):
self.set_ccx_override('max_student_enrollments_allowed', expected)
actual = self.ccx.max_student_enrollments_allowed # pylint: disable=no-member
self.assertEqual(expected, actual)
def test_structure_json_default_empty(self):
"""
By default structure_json does not contain anything
"""
self.assertEqual(self.ccx.structure_json, None) # pylint: disable=no-member
self.assertEqual(self.ccx.structure, None) # pylint: disable=no-member
def test_structure_json(self):
"""
Test a json stored in the structure_json
"""
dummy_struct = [
"block-v1:Organization+CN101+CR-FALL15+type@chapter+block@Unit_4",
"block-v1:Organization+CN101+CR-FALL15+type@chapter+block@Unit_5",
"block-v1:Organization+CN101+CR-FALL15+type@chapter+block@Unit_11"
]
json_struct = json.dumps(dummy_struct)
ccx = CcxFactory(
course_id=self.course.id,
coach=self.coach,
structure_json=json_struct
)
self.assertEqual(ccx.structure_json, json_struct) # pylint: disable=no-member
self.assertEqual(ccx.structure, dummy_struct) # pylint: disable=no-member
"""
test utils
"""
import mock
from nose.plugins.attrib import attr
from lms.djangoapps.ccx.tests.factories import CcxFactory
from student.roles import CourseCcxCoachRole
from student.tests.factories import (
AdminFactory,
......@@ -12,7 +12,11 @@ from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
TEST_DATA_SPLIT_MODULESTORE)
from xmodule.modulestore.tests.factories import CourseFactory
from opaque_keys.edx.keys import CourseKey
from lms.djangoapps.ccx import utils
from lms.djangoapps.ccx.tests.factories import CcxFactory
from lms.djangoapps.ccx.tests.utils import CcxTestCase
from ccx_keys.locator import CCXLocator
......@@ -47,3 +51,45 @@ class TestGetCCXFromCCXLocator(ModuleStoreTestCase):
course_key = CCXLocator.from_course_locator(self.course.id, ccx.id)
result = self.call_fut(course_key)
self.assertEqual(result, ccx)
@attr('shard_1')
class TestGetCourseChapters(CcxTestCase):
"""
Tests for the `get_course_chapters` util function
"""
def setUp(self):
"""
Set up tests
"""
super(TestGetCourseChapters, self).setUp()
self.course_key = self.course.location.course_key
def test_get_structure_non_existing_key(self):
"""
Test to get the course structure
"""
self.assertEqual(utils.get_course_chapters(None), None)
# build a fake key
fake_course_key = CourseKey.from_string('course-v1:FakeOrg+CN1+CR-FALLNEVER1')
self.assertEqual(utils.get_course_chapters(fake_course_key), None)
@mock.patch('openedx.core.djangoapps.content.course_structures.models.CourseStructure.structure',
new_callable=mock.PropertyMock)
def test_wrong_course_structure(self, mocked_attr):
"""
Test the case where the course has an unexpected structure.
"""
mocked_attr.return_value = {'foo': 'bar'}
self.assertEqual(utils.get_course_chapters(self.course_key), [])
def test_get_chapters(self):
"""
Happy path
"""
course_chapters = utils.get_course_chapters(self.course_key)
self.assertEqual(len(course_chapters), 2)
self.assertEqual(
sorted(course_chapters),
sorted([unicode(child) for child in self.course.children])
)
......@@ -23,6 +23,7 @@ from instructor.enrollment import (
from instructor.access import allow_access
from instructor.views.tools import get_student_from_identifier
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.content.course_structures.models import CourseStructure
from student.models import CourseEnrollment
from student.roles import CourseCcxCoachRole
......@@ -284,3 +285,29 @@ def is_email(identifier):
except ValidationError:
return False
return True
def get_course_chapters(course_key):
"""
Extracts the chapters from a course structure.
If the course does not exist returns None.
If the structure does not contain 1st level children,
it returns an empty list.
Args:
course_key (CourseLocator): the course key
Returns:
list (string): a list of string representing the chapters modules
of the course
"""
if course_key is None:
return
try:
course_obj = CourseStructure.objects.get(course_id=course_key)
except CourseStructure.DoesNotExist:
return
course_struct = course_obj.structure
try:
return course_struct['blocks'][course_struct['root']].get('children', [])
except KeyError:
return []
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment