Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
0c637cdc
Commit
0c637cdc
authored
Jan 11, 2016
by
Giovanni Di Milia
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added extra field to CCX model for Course Models
REST APIs modified
parent
e3ddb02c
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
250 additions
and
16 deletions
+250
-16
lms/djangoapps/ccx/api/v0/serializers.py
+9
-0
lms/djangoapps/ccx/api/v0/tests/test_views.py
+0
-0
lms/djangoapps/ccx/api/v0/views.py
+103
-9
lms/djangoapps/ccx/migrations/0002_customcourseforedx_structure_json.py
+19
-0
lms/djangoapps/ccx/models.py
+14
-1
lms/djangoapps/ccx/tests/test_models.py
+31
-5
lms/djangoapps/ccx/tests/test_utils.py
+47
-1
lms/djangoapps/ccx/utils.py
+27
-0
No files found.
lms/djangoapps/ccx/api/v0/serializers.py
View file @
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
[]
lms/djangoapps/ccx/api/v0/tests/test_views.py
View file @
0c637cdc
This diff is collapsed.
Click to expand it.
lms/djangoapps/ccx/api/v0/views.py
View file @
0c637cdc
This diff is collapsed.
Click to expand it.
lms/djangoapps/ccx/migrations/0002_customcourseforedx_structure_json.py
0 → 100644
View file @
0c637cdc
# -*- 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
),
),
]
lms/djangoapps/ccx/models.py
View file @
0c637cdc
"""
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
):
"""
...
...
lms/djangoapps/ccx/tests/test_models.py
View file @
0c637cdc
"""
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
lms/djangoapps/ccx/tests/test_utils.py
View file @
0c637cdc
"""
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
])
)
lms/djangoapps/ccx/utils.py
View file @
0c637cdc
...
...
@@ -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
[]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment