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
bdf1b1b0
Commit
bdf1b1b0
authored
Mar 12, 2013
by
cahrens
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature/alex/poll-merged' into feature/studio/advanced-settings-revamp-merge
parents
99f55266
21135416
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
438 additions
and
163 deletions
+438
-163
.ruby-version
+1
-2
cms/djangoapps/contentstore/tests/test_contentstore.py
+95
-69
cms/djangoapps/contentstore/views.py
+35
-2
common/lib/capa/capa/calc.py
+1
-1
common/lib/capa/capa/inputtypes.py
+6
-0
common/lib/capa/capa/templates/choicegroup.html
+5
-2
common/lib/capa/capa/tests/test_inputtypes.py
+2
-0
common/lib/capa/capa/tests/test_responsetypes.py
+32
-23
common/lib/xmodule/xmodule/course_module.py
+12
-11
common/lib/xmodule/xmodule/foldit_module.py
+4
-1
common/lib/xmodule/xmodule/modulestore/tests/factories.py
+4
-4
common/lib/xmodule/xmodule/modulestore/tests/test_location.py
+11
-11
common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py
+2
-2
jenkins/test.sh
+4
-1
lms/djangoapps/courseware/tabs.py
+12
-0
lms/djangoapps/django_comment_client/utils.py
+2
-26
lms/djangoapps/foldit/models.py
+1
-1
lms/djangoapps/foldit/tests.py
+10
-5
lms/djangoapps/staticbook/views.py
+41
-1
lms/static/sass/course/_textbook.scss
+13
-0
lms/templates/static_htmlbook.html
+135
-0
lms/urls.py
+9
-0
local-requirements.txt
+1
-1
No files found.
.ruby-version
View file @
bdf1b1b0
1.9.3-p374
1.9.3-p374
\ No newline at end of file
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
bdf1b1b0
...
@@ -10,10 +10,8 @@ from datetime import timedelta
...
@@ -10,10 +10,8 @@ from datetime import timedelta
import
json
import
json
from
fs.osfs
import
OSFS
from
fs.osfs
import
OSFS
import
copy
import
copy
from
mock
import
Mock
from
json
import
loads
from
json
import
dumps
,
loads
from
student.models
import
Registration
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
cms.djangoapps.contentstore.utils
import
get_modulestore
from
cms.djangoapps.contentstore.utils
import
get_modulestore
...
@@ -23,13 +21,12 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
...
@@ -23,13 +21,12 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from
xmodule.modulestore
import
Location
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.store_utilities
import
clone_course
from
xmodule.modulestore.store_utilities
import
clone_course
from
xmodule.modulestore.store_utilities
import
delete_course
from
xmodule.modulestore.store_utilities
import
delete_course
from
xmodule.modulestore.django
import
modulestore
,
_MODULESTORES
from
xmodule.modulestore.django
import
modulestore
from
xmodule.contentstore.django
import
contentstore
from
xmodule.contentstore.django
import
contentstore
from
xmodule.templates
import
update_templates
from
xmodule.templates
import
update_templates
from
xmodule.modulestore.xml_exporter
import
export_to_xml
from
xmodule.modulestore.xml_exporter
import
export_to_xml
from
xmodule.modulestore.xml_importer
import
import_from_xml
from
xmodule.modulestore.xml_importer
import
import_from_xml
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.templates
import
update_templates
from
xmodule.capa_module
import
CapaDescriptor
from
xmodule.capa_module
import
CapaDescriptor
from
xmodule.course_module
import
CourseDescriptor
from
xmodule.course_module
import
CourseDescriptor
...
@@ -65,7 +62,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -65,7 +62,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
client
=
Client
()
self
.
client
=
Client
()
self
.
client
.
login
(
username
=
uname
,
password
=
password
)
self
.
client
.
login
(
username
=
uname
,
password
=
password
)
def
check_edit_unit
(
self
,
test_course_name
):
def
check_edit_unit
(
self
,
test_course_name
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
test_course_name
])
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
test_course_name
])
...
@@ -84,8 +80,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -84,8 +80,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
def
test_static_tab_reordering
(
self
):
def
test_static_tab_reordering
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
course
=
m
s
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
course
=
m
odule_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
# reverse the ordering
# reverse the ordering
reverse_tabs
=
[]
reverse_tabs
=
[]
...
@@ -93,9 +89,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -93,9 +89,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
if
tab
[
'type'
]
==
'static_tab'
:
if
tab
[
'type'
]
==
'static_tab'
:
reverse_tabs
.
insert
(
0
,
'i4x://edX/full/static_tab/{0}'
.
format
(
tab
[
'url_slug'
]))
reverse_tabs
.
insert
(
0
,
'i4x://edX/full/static_tab/{0}'
.
format
(
tab
[
'url_slug'
]))
resp
=
self
.
client
.
post
(
reverse
(
'reorder_static_tabs'
),
json
.
dumps
({
'tabs'
:
reverse_tabs
}),
"application/json"
)
self
.
client
.
post
(
reverse
(
'reorder_static_tabs'
),
json
.
dumps
({
'tabs'
:
reverse_tabs
}),
"application/json"
)
course
=
m
s
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
course
=
m
odule_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
# compare to make sure that the tabs information is in the expected order after the server call
# compare to make sure that the tabs information is in the expected order after the server call
course_tabs
=
[]
course_tabs
=
[]
...
@@ -105,28 +101,60 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -105,28 +101,60 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertEqual
(
reverse_tabs
,
course_tabs
)
self
.
assertEqual
(
reverse_tabs
,
course_tabs
)
def
test_delete
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
module_store
=
modulestore
(
'direct'
)
sequential
=
module_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'sequential'
,
'Administrivia_and_Circuit_Elements'
,
None
]))
chapter
=
module_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'chapter'
,
'Week_1'
,
None
]))
# make sure the parent no longer points to the child object which was deleted
self
.
assertTrue
(
sequential
.
location
.
url
()
in
chapter
.
definition
[
'children'
])
self
.
client
.
post
(
reverse
(
'delete_item'
),
json
.
dumps
({
'id'
:
sequential
.
location
.
url
(),
'delete_children'
:
'true'
}),
"application/json"
)
found
=
False
try
:
module_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'sequential'
,
'Administrivia_and_Circuit_Elements'
,
None
]))
found
=
True
except
ItemNotFoundError
:
pass
self
.
assertFalse
(
found
)
chapter
=
module_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'chapter'
,
'Week_1'
,
None
]))
# make sure the parent no longer points to the child object which was deleted
self
.
assertFalse
(
sequential
.
location
.
url
()
in
chapter
.
definition
[
'children'
])
def
test_about_overrides
(
self
):
def
test_about_overrides
(
self
):
'''
'''
This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html
This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html
while there is a base definition in /about/effort.html
while there is a base definition in /about/effort.html
'''
'''
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
effort
=
m
s
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'about'
,
'effort'
,
None
]))
effort
=
m
odule_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'about'
,
'effort'
,
None
]))
self
.
assertEqual
(
effort
.
data
,
'6 hours'
)
self
.
assertEqual
(
effort
.
data
,
'6 hours'
)
# this one should be in a non-override folder
# this one should be in a non-override folder
effort
=
m
s
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'about'
,
'end_date'
,
None
]))
effort
=
m
odule_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'about'
,
'end_date'
,
None
]))
self
.
assertEqual
(
effort
.
data
,
'TBD'
)
self
.
assertEqual
(
effort
.
data
,
'TBD'
)
def
test_remove_hide_progress_tab
(
self
):
def
test_remove_hide_progress_tab
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
c
ontent_store
=
contentstore
()
source_location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
source_location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
course
=
m
s
.
get_item
(
source_location
)
course
=
m
odule_store
.
get_item
(
source_location
)
self
.
assertFalse
(
course
.
hide_progress_tab
)
self
.
assertFalse
(
course
.
hide_progress_tab
)
def
test_clone_course
(
self
):
def
test_clone_course
(
self
):
...
@@ -145,19 +173,19 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -145,19 +173,19 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
data
=
parse_json
(
resp
)
data
=
parse_json
(
resp
)
self
.
assertEqual
(
data
[
'id'
],
'i4x://MITx/999/course/Robot_Super_Course'
)
self
.
assertEqual
(
data
[
'id'
],
'i4x://MITx/999/course/Robot_Super_Course'
)
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
c
ontent_store
=
contentstore
()
source_location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
source_location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
dest_location
=
CourseDescriptor
.
id_to_location
(
'MITx/999/Robot_Super_Course'
)
dest_location
=
CourseDescriptor
.
id_to_location
(
'MITx/999/Robot_Super_Course'
)
clone_course
(
m
s
,
cs
,
source_location
,
dest_location
)
clone_course
(
m
odule_store
,
content_store
,
source_location
,
dest_location
)
# now loop through all the units in the course and verify that the clone can render them, which
# now loop through all the units in the course and verify that the clone can render them, which
# means the objects are at least present
# means the objects are at least present
items
=
m
s
.
get_items
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'vertical'
,
None
]))
items
=
m
odule_store
.
get_items
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'vertical'
,
None
]))
self
.
assertGreater
(
len
(
items
),
0
)
self
.
assertGreater
(
len
(
items
),
0
)
clone_items
=
m
s
.
get_items
(
Location
([
'i4x'
,
'MITx'
,
'999'
,
'vertical'
,
None
]))
clone_items
=
m
odule_store
.
get_items
(
Location
([
'i4x'
,
'MITx'
,
'999'
,
'vertical'
,
None
]))
self
.
assertGreater
(
len
(
clone_items
),
0
)
self
.
assertGreater
(
len
(
clone_items
),
0
)
for
descriptor
in
items
:
for
descriptor
in
items
:
new_loc
=
descriptor
.
location
.
_replace
(
org
=
'MITx'
,
course
=
'999'
)
new_loc
=
descriptor
.
location
.
_replace
(
org
=
'MITx'
,
course
=
'999'
)
...
@@ -168,14 +196,14 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -168,14 +196,14 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
def
test_delete_course
(
self
):
def
test_delete_course
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
c
ontent_store
=
contentstore
()
location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
delete_course
(
m
s
,
cs
,
location
,
commit
=
True
)
delete_course
(
m
odule_store
,
content_store
,
location
,
commit
=
True
)
items
=
m
s
.
get_items
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'vertical'
,
None
]))
items
=
m
odule_store
.
get_items
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'vertical'
,
None
]))
self
.
assertEqual
(
len
(
items
),
0
)
self
.
assertEqual
(
len
(
items
),
0
)
def
verify_content_existence
(
self
,
modulestore
,
root_dir
,
location
,
dirname
,
category_name
,
filename_suffix
=
''
):
def
verify_content_existence
(
self
,
modulestore
,
root_dir
,
location
,
dirname
,
category_name
,
filename_suffix
=
''
):
...
@@ -190,10 +218,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -190,10 +218,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertTrue
(
fs
.
exists
(
item
.
location
.
name
+
filename_suffix
))
self
.
assertTrue
(
fs
.
exists
(
item
.
location
.
name
+
filename_suffix
))
def
test_export_course
(
self
):
def
test_export_course
(
self
):
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
c
ontent_store
=
contentstore
()
import_from_xml
(
m
s
,
'common/test/data/'
,
[
'full'
])
import_from_xml
(
m
odule_store
,
'common/test/data/'
,
[
'full'
])
location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
root_dir
=
path
(
mkdtemp_clean
())
root_dir
=
path
(
mkdtemp_clean
())
...
@@ -201,24 +229,24 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -201,24 +229,24 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
print
'Exporting to tempdir = {0}'
.
format
(
root_dir
)
print
'Exporting to tempdir = {0}'
.
format
(
root_dir
)
# export out to a tempdir
# export out to a tempdir
export_to_xml
(
m
s
,
cs
,
location
,
root_dir
,
'test_export'
)
export_to_xml
(
m
odule_store
,
content_store
,
location
,
root_dir
,
'test_export'
)
# check for static tabs
# check for static tabs
self
.
verify_content_existence
(
m
s
,
root_dir
,
location
,
'tabs'
,
'static_tab'
,
'.html'
)
self
.
verify_content_existence
(
m
odule_store
,
root_dir
,
location
,
'tabs'
,
'static_tab'
,
'.html'
)
# check for custom_tags
# check for custom_tags
self
.
verify_content_existence
(
m
s
,
root_dir
,
location
,
'info'
,
'course_info'
,
'.html'
)
self
.
verify_content_existence
(
m
odule_store
,
root_dir
,
location
,
'info'
,
'course_info'
,
'.html'
)
# check for custom_tags
# check for custom_tags
self
.
verify_content_existence
(
m
s
,
root_dir
,
location
,
'custom_tags'
,
'custom_tag_template'
)
self
.
verify_content_existence
(
m
odule_store
,
root_dir
,
location
,
'custom_tags'
,
'custom_tag_template'
)
# check for graiding_policy.json
# check for graiding_policy.json
fs
=
OSFS
(
root_dir
/
'test_export/policies/6.002_Spring_2012'
)
fs
=
OSFS
(
root_dir
/
'test_export/policies/6.002_Spring_2012'
)
self
.
assertTrue
(
fs
.
exists
(
'grading_policy.json'
))
self
.
assertTrue
(
fs
.
exists
(
'grading_policy.json'
))
course
=
m
s
.
get_item
(
location
)
course
=
m
odule_store
.
get_item
(
location
)
# compare what's on disk compared to what we have in our course
# compare what's on disk compared to what we have in our course
with
fs
.
open
(
'grading_policy.json'
,
'r'
)
as
grading_policy
:
with
fs
.
open
(
'grading_policy.json'
,
'r'
)
as
grading_policy
:
on_disk
=
loads
(
grading_policy
.
read
())
on_disk
=
loads
(
grading_policy
.
read
())
self
.
assertEqual
(
on_disk
,
course
.
grading_policy
)
self
.
assertEqual
(
on_disk
,
course
.
grading_policy
)
...
@@ -226,18 +254,18 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -226,18 +254,18 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertTrue
(
fs
.
exists
(
'policy.json'
))
self
.
assertTrue
(
fs
.
exists
(
'policy.json'
))
# compare what's on disk to what we have in the course module
# compare what's on disk to what we have in the course module
with
fs
.
open
(
'policy.json'
,
'r'
)
as
course_policy
:
with
fs
.
open
(
'policy.json'
,
'r'
)
as
course_policy
:
on_disk
=
loads
(
course_policy
.
read
())
on_disk
=
loads
(
course_policy
.
read
())
self
.
assertIn
(
'course/6.002_Spring_2012'
,
on_disk
)
self
.
assertIn
(
'course/6.002_Spring_2012'
,
on_disk
)
self
.
assertEqual
(
on_disk
[
'course/6.002_Spring_2012'
],
own_metadata
(
course
))
self
.
assertEqual
(
on_disk
[
'course/6.002_Spring_2012'
],
own_metadata
(
course
))
# remove old course
# remove old course
delete_course
(
m
s
,
cs
,
location
)
delete_course
(
m
odule_store
,
content_store
,
location
)
# reimport
# reimport
import_from_xml
(
m
s
,
root_dir
,
[
'test_export'
])
import_from_xml
(
m
odule_store
,
root_dir
,
[
'test_export'
])
items
=
m
s
.
get_items
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'vertical'
,
None
]))
items
=
m
odule_store
.
get_items
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'vertical'
,
None
]))
self
.
assertGreater
(
len
(
items
),
0
)
self
.
assertGreater
(
len
(
items
),
0
)
for
descriptor
in
items
:
for
descriptor
in
items
:
print
"Checking {0}...."
.
format
(
descriptor
.
location
.
url
())
print
"Checking {0}...."
.
format
(
descriptor
.
location
.
url
())
...
@@ -247,11 +275,11 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -247,11 +275,11 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
shutil
.
rmtree
(
root_dir
)
shutil
.
rmtree
(
root_dir
)
def
test_course_handouts_rewrites
(
self
):
def
test_course_handouts_rewrites
(
self
):
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
c
ontent_store
=
contentstore
()
# import a test course
# import a test course
import_from_xml
(
m
s
,
'common/test/data/'
,
[
'full'
])
import_from_xml
(
m
odule_store
,
'common/test/data/'
,
[
'full'
])
handout_location
=
Location
([
'i4x'
,
'edX'
,
'full'
,
'course_info'
,
'handouts'
])
handout_location
=
Location
([
'i4x'
,
'edX'
,
'full'
,
'course_info'
,
'handouts'
])
...
@@ -266,33 +294,33 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
...
@@ -266,33 +294,33 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertContains
(
resp
,
'/c4x/edX/full/asset/handouts_schematic_tutorial.pdf'
)
self
.
assertContains
(
resp
,
'/c4x/edX/full/asset/handouts_schematic_tutorial.pdf'
)
def
test_export_course_with_unknown_metadata
(
self
):
def
test_export_course_with_unknown_metadata
(
self
):
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
c
ontent_store
=
contentstore
()
import_from_xml
(
m
s
,
'common/test/data/'
,
[
'full'
])
import_from_xml
(
m
odule_store
,
'common/test/data/'
,
[
'full'
])
location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
root_dir
=
path
(
mkdtemp_clean
())
root_dir
=
path
(
mkdtemp_clean
())
course
=
m
s
.
get_item
(
location
)
course
=
m
odule_store
.
get_item
(
location
)
metadata
=
own_metadata
(
course
)
metadata
=
own_metadata
(
course
)
# add a bool piece of unknown metadata so we can verify we don't throw an exception
# add a bool piece of unknown metadata so we can verify we don't throw an exception
metadata
[
'new_metadata'
]
=
True
metadata
[
'new_metadata'
]
=
True
m
s
.
update_metadata
(
location
,
metadata
)
m
odule_store
.
update_metadata
(
location
,
metadata
)
print
'Exporting to tempdir = {0}'
.
format
(
root_dir
)
print
'Exporting to tempdir = {0}'
.
format
(
root_dir
)
# export out to a tempdir
# export out to a tempdir
bE
xported
=
False
e
xported
=
False
try
:
try
:
export_to_xml
(
m
s
,
cs
,
location
,
root_dir
,
'test_export'
)
export_to_xml
(
m
odule_store
,
content_store
,
location
,
root_dir
,
'test_export'
)
bE
xported
=
True
e
xported
=
True
except
Exception
:
except
Exception
:
pass
pass
self
.
assertTrue
(
bE
xported
)
self
.
assertTrue
(
e
xported
)
class
ContentStoreTest
(
ModuleStoreTestCase
):
class
ContentStoreTest
(
ModuleStoreTestCase
):
"""
"""
...
@@ -431,7 +459,7 @@ class ContentStoreTest(ModuleStoreTestCase):
...
@@ -431,7 +459,7 @@ class ContentStoreTest(ModuleStoreTestCase):
def
test_capa_module
(
self
):
def
test_capa_module
(
self
):
"""Test that a problem treats markdown specially."""
"""Test that a problem treats markdown specially."""
course
=
CourseFactory
.
create
(
org
=
'MITx'
,
course
=
'999'
,
display_name
=
'Robot Super Course'
)
CourseFactory
.
create
(
org
=
'MITx'
,
course
=
'999'
,
display_name
=
'Robot Super Course'
)
problem_data
=
{
problem_data
=
{
'parent_location'
:
'i4x://MITx/999/course/Robot_Super_Course'
,
'parent_location'
:
'i4x://MITx/999/course/Robot_Super_Course'
,
...
@@ -452,10 +480,10 @@ class ContentStoreTest(ModuleStoreTestCase):
...
@@ -452,10 +480,10 @@ class ContentStoreTest(ModuleStoreTestCase):
def
test_import_metadata_with_attempts_empty_string
(
self
):
def
test_import_metadata_with_attempts_empty_string
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'simple'
])
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'simple'
])
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
did_load_item
=
False
did_load_item
=
False
try
:
try
:
m
s
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'simple'
,
'problem'
,
'ps01-simple'
,
None
]))
m
odule_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'simple'
,
'problem'
,
'ps01-simple'
,
None
]))
did_load_item
=
True
did_load_item
=
True
except
ItemNotFoundError
:
except
ItemNotFoundError
:
pass
pass
...
@@ -466,10 +494,10 @@ class ContentStoreTest(ModuleStoreTestCase):
...
@@ -466,10 +494,10 @@ class ContentStoreTest(ModuleStoreTestCase):
def
test_metadata_inheritance
(
self
):
def
test_metadata_inheritance
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
course
=
m
s
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
course
=
m
odule_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
verticals
=
m
s
.
get_items
([
'i4x'
,
'edX'
,
'full'
,
'vertical'
,
None
,
None
])
verticals
=
m
odule_store
.
get_items
([
'i4x'
,
'edX'
,
'full'
,
'vertical'
,
None
,
None
])
# let's assert on the metadata_inheritance on an existing vertical
# let's assert on the metadata_inheritance on an existing vertical
for
vertical
in
verticals
:
for
vertical
in
verticals
:
...
@@ -481,13 +509,13 @@ class ContentStoreTest(ModuleStoreTestCase):
...
@@ -481,13 +509,13 @@ class ContentStoreTest(ModuleStoreTestCase):
source_template_location
=
Location
(
'i4x'
,
'edx'
,
'templates'
,
'html'
,
'Blank_HTML_Page'
)
source_template_location
=
Location
(
'i4x'
,
'edx'
,
'templates'
,
'html'
,
'Blank_HTML_Page'
)
# crate a new module and add it as a child to a vertical
# crate a new module and add it as a child to a vertical
m
s
.
clone_item
(
source_template_location
,
new_component_location
)
m
odule_store
.
clone_item
(
source_template_location
,
new_component_location
)
parent
=
verticals
[
0
]
parent
=
verticals
[
0
]
m
s
.
update_children
(
parent
.
location
,
parent
.
children
+
[
new_component_location
.
url
()])
m
odule_store
.
update_children
(
parent
.
location
,
parent
.
children
+
[
new_component_location
.
url
()])
# flush the cache
# flush the cache
m
s
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
m
odule_store
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
new_module
=
m
s
.
get_item
(
new_component_location
)
new_module
=
m
odule_store
.
get_item
(
new_component_location
)
# check for grace period definition which should be defined at the course level
# check for grace period definition which should be defined at the course level
self
.
assertEqual
(
parent
.
lms
.
graceperiod
,
new_module
.
lms
.
graceperiod
)
self
.
assertEqual
(
parent
.
lms
.
graceperiod
,
new_module
.
lms
.
graceperiod
)
...
@@ -498,11 +526,11 @@ class ContentStoreTest(ModuleStoreTestCase):
...
@@ -498,11 +526,11 @@ class ContentStoreTest(ModuleStoreTestCase):
# now let's define an override at the leaf node level
# now let's define an override at the leaf node level
#
#
new_module
.
lms
.
graceperiod
=
timedelta
(
1
)
new_module
.
lms
.
graceperiod
=
timedelta
(
1
)
m
s
.
update_metadata
(
new_module
.
location
,
own_metadata
(
new_module
))
m
odule_store
.
update_metadata
(
new_module
.
location
,
own_metadata
(
new_module
))
# flush the cache and refetch
# flush the cache and refetch
m
s
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
m
odule_store
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
new_module
=
m
s
.
get_item
(
new_component_location
)
new_module
=
m
odule_store
.
get_item
(
new_component_location
)
self
.
assertEqual
(
timedelta
(
1
),
new_module
.
lms
.
graceperiod
)
self
.
assertEqual
(
timedelta
(
1
),
new_module
.
lms
.
graceperiod
)
...
@@ -510,15 +538,15 @@ class ContentStoreTest(ModuleStoreTestCase):
...
@@ -510,15 +538,15 @@ class ContentStoreTest(ModuleStoreTestCase):
class
TemplateTestCase
(
ModuleStoreTestCase
):
class
TemplateTestCase
(
ModuleStoreTestCase
):
def
test_template_cleanup
(
self
):
def
test_template_cleanup
(
self
):
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
# insert a bogus template in the store
# insert a bogus template in the store
bogus_template_location
=
Location
(
'i4x'
,
'edx'
,
'templates'
,
'html'
,
'bogus'
)
bogus_template_location
=
Location
(
'i4x'
,
'edx'
,
'templates'
,
'html'
,
'bogus'
)
source_template_location
=
Location
(
'i4x'
,
'edx'
,
'templates'
,
'html'
,
'Blank_HTML_Page'
)
source_template_location
=
Location
(
'i4x'
,
'edx'
,
'templates'
,
'html'
,
'Blank_HTML_Page'
)
m
s
.
clone_item
(
source_template_location
,
bogus_template_location
)
m
odule_store
.
clone_item
(
source_template_location
,
bogus_template_location
)
verify_create
=
m
s
.
get_item
(
bogus_template_location
)
verify_create
=
m
odule_store
.
get_item
(
bogus_template_location
)
self
.
assertIsNotNone
(
verify_create
)
self
.
assertIsNotNone
(
verify_create
)
# now run cleanup
# now run cleanup
...
@@ -527,10 +555,8 @@ class TemplateTestCase(ModuleStoreTestCase):
...
@@ -527,10 +555,8 @@ class TemplateTestCase(ModuleStoreTestCase):
# now try to find dangling template, it should not be in DB any longer
# now try to find dangling template, it should not be in DB any longer
asserted
=
False
asserted
=
False
try
:
try
:
verify_create
=
m
s
.
get_item
(
bogus_template_location
)
verify_create
=
m
odule_store
.
get_item
(
bogus_template_location
)
except
ItemNotFoundError
:
except
ItemNotFoundError
:
asserted
=
True
asserted
=
True
self
.
assertTrue
(
asserted
)
self
.
assertTrue
(
asserted
)
cms/djangoapps/contentstore/views.py
View file @
bdf1b1b0
...
@@ -90,12 +90,14 @@ def signup(request):
...
@@ -90,12 +90,14 @@ def signup(request):
csrf_token
=
csrf
(
request
)[
'csrf_token'
]
csrf_token
=
csrf
(
request
)[
'csrf_token'
]
return
render_to_response
(
'signup.html'
,
{
'csrf'
:
csrf_token
})
return
render_to_response
(
'signup.html'
,
{
'csrf'
:
csrf_token
})
def
old_login_redirect
(
request
):
def
old_login_redirect
(
request
):
'''
'''
Redirect to the active login url.
Redirect to the active login url.
'''
'''
return
redirect
(
'login'
,
permanent
=
True
)
return
redirect
(
'login'
,
permanent
=
True
)
@ssl_login_shortcut
@ssl_login_shortcut
@ensure_csrf_cookie
@ensure_csrf_cookie
def
login_page
(
request
):
def
login_page
(
request
):
...
@@ -108,6 +110,7 @@ def login_page(request):
...
@@ -108,6 +110,7 @@ def login_page(request):
'forgot_password_link'
:
"//{base}/#forgot-password-modal"
.
format
(
base
=
settings
.
LMS_BASE
),
'forgot_password_link'
:
"//{base}/#forgot-password-modal"
.
format
(
base
=
settings
.
LMS_BASE
),
})
})
def
howitworks
(
request
):
def
howitworks
(
request
):
if
request
.
user
.
is_authenticated
():
if
request
.
user
.
is_authenticated
():
return
index
(
request
)
return
index
(
request
)
...
@@ -116,6 +119,7 @@ def howitworks(request):
...
@@ -116,6 +119,7 @@ def howitworks(request):
# ==== Views for any logged-in user ==================================
# ==== Views for any logged-in user ==================================
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
def
index
(
request
):
def
index
(
request
):
...
@@ -149,6 +153,7 @@ def index(request):
...
@@ -149,6 +153,7 @@ def index(request):
# ==== Views with per-item permissions================================
# ==== Views with per-item permissions================================
def
has_access
(
user
,
location
,
role
=
STAFF_ROLE_NAME
):
def
has_access
(
user
,
location
,
role
=
STAFF_ROLE_NAME
):
'''
'''
Return True if user allowed to access this piece of data
Return True if user allowed to access this piece of data
...
@@ -396,6 +401,7 @@ def preview_component(request, location):
...
@@ -396,6 +401,7 @@ def preview_component(request, location):
'editor'
:
wrap_xmodule
(
component
.
get_html
,
component
,
'xmodule_edit.html'
)(),
'editor'
:
wrap_xmodule
(
component
.
get_html
,
component
,
'xmodule_edit.html'
)(),
})
})
@expect_json
@expect_json
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
...
@@ -636,6 +642,17 @@ def delete_item(request):
...
@@ -636,6 +642,17 @@ def delete_item(request):
if
item
.
location
.
revision
is
None
and
item
.
location
.
category
==
'vertical'
and
delete_all_versions
:
if
item
.
location
.
revision
is
None
and
item
.
location
.
category
==
'vertical'
and
delete_all_versions
:
modulestore
(
'direct'
)
.
delete_item
(
item
.
location
)
modulestore
(
'direct'
)
.
delete_item
(
item
.
location
)
# cdodge: we need to remove our parent's pointer to us so that it is no longer dangling
parent_locs
=
modulestore
(
'direct'
)
.
get_parent_locations
(
item_loc
,
None
)
for
parent_loc
in
parent_locs
:
parent
=
modulestore
(
'direct'
)
.
get_item
(
parent_loc
)
item_url
=
item_loc
.
url
()
if
item_url
in
parent
.
definition
[
"children"
]:
parent
.
definition
[
"children"
]
.
remove
(
item_url
)
modulestore
(
'direct'
)
.
update_children
(
parent
.
location
,
parent
.
definition
[
"children"
])
return
HttpResponse
()
return
HttpResponse
()
...
@@ -709,6 +726,7 @@ def create_draft(request):
...
@@ -709,6 +726,7 @@ def create_draft(request):
return
HttpResponse
()
return
HttpResponse
()
@login_required
@login_required
@expect_json
@expect_json
def
publish_draft
(
request
):
def
publish_draft
(
request
):
...
@@ -738,6 +756,7 @@ def unpublish_unit(request):
...
@@ -738,6 +756,7 @@ def unpublish_unit(request):
return
HttpResponse
()
return
HttpResponse
()
@login_required
@login_required
@expect_json
@expect_json
def
clone_item
(
request
):
def
clone_item
(
request
):
...
@@ -765,8 +784,7 @@ def clone_item(request):
...
@@ -765,8 +784,7 @@ def clone_item(request):
return
HttpResponse
(
json
.
dumps
({
'id'
:
dest_location
.
url
()}))
return
HttpResponse
(
json
.
dumps
({
'id'
:
dest_location
.
url
()}))
#@login_required
#@ensure_csrf_cookie
def
upload_asset
(
request
,
org
,
course
,
coursename
):
def
upload_asset
(
request
,
org
,
course
,
coursename
):
'''
'''
cdodge: this method allows for POST uploading of files into the course asset library, which will
cdodge: this method allows for POST uploading of files into the course asset library, which will
...
@@ -828,6 +846,7 @@ def upload_asset(request, org, course, coursename):
...
@@ -828,6 +846,7 @@ def upload_asset(request, org, course, coursename):
response
[
'asset_url'
]
=
StaticContent
.
get_url_path_from_location
(
content
.
location
)
response
[
'asset_url'
]
=
StaticContent
.
get_url_path_from_location
(
content
.
location
)
return
response
return
response
'''
'''
This view will return all CMS users who are editors for the specified course
This view will return all CMS users who are editors for the specified course
'''
'''
...
@@ -860,6 +879,7 @@ def create_json_response(errmsg = None):
...
@@ -860,6 +879,7 @@ def create_json_response(errmsg = None):
return
resp
return
resp
'''
'''
This POST-back view will add a user - specified by email - to the list of editors for
This POST-back view will add a user - specified by email - to the list of editors for
the specified course
the specified course
...
@@ -892,6 +912,7 @@ def add_user(request, location):
...
@@ -892,6 +912,7 @@ def add_user(request, location):
return
create_json_response
()
return
create_json_response
()
'''
'''
This POST-back view will remove a user - specified by email - from the list of editors for
This POST-back view will remove a user - specified by email - from the list of editors for
the specified course
the specified course
...
@@ -923,6 +944,7 @@ def remove_user(request, location):
...
@@ -923,6 +944,7 @@ def remove_user(request, location):
def
landing
(
request
,
org
,
course
,
coursename
):
def
landing
(
request
,
org
,
course
,
coursename
):
return
render_to_response
(
'temp-course-landing.html'
,
{})
return
render_to_response
(
'temp-course-landing.html'
,
{})
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
def
static_pages
(
request
,
org
,
course
,
coursename
):
def
static_pages
(
request
,
org
,
course
,
coursename
):
...
@@ -1026,6 +1048,7 @@ def edit_tabs(request, org, course, coursename):
...
@@ -1026,6 +1048,7 @@ def edit_tabs(request, org, course, coursename):
'components'
:
components
'components'
:
components
})
})
def
not_found
(
request
):
def
not_found
(
request
):
return
render_to_response
(
'error.html'
,
{
'error'
:
'404'
})
return
render_to_response
(
'error.html'
,
{
'error'
:
'404'
})
...
@@ -1061,6 +1084,7 @@ def course_info(request, org, course, name, provided_id=None):
...
@@ -1061,6 +1084,7 @@ def course_info(request, org, course, name, provided_id=None):
'handouts_location'
:
Location
([
'i4x'
,
org
,
course
,
'course_info'
,
'handouts'
])
.
url
()
'handouts_location'
:
Location
([
'i4x'
,
org
,
course
,
'course_info'
,
'handouts'
])
.
url
()
})
})
@expect_json
@expect_json
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
...
@@ -1158,6 +1182,7 @@ def get_course_settings(request, org, course, name):
...
@@ -1158,6 +1182,7 @@ def get_course_settings(request, org, course, name):
"section"
:
"details"
})
"section"
:
"details"
})
})
})
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
def
course_config_graders_page
(
request
,
org
,
course
,
name
):
def
course_config_graders_page
(
request
,
org
,
course
,
name
):
...
@@ -1181,6 +1206,7 @@ def course_config_graders_page(request, org, course, name):
...
@@ -1181,6 +1206,7 @@ def course_config_graders_page(request, org, course, name):
'course_details'
:
json
.
dumps
(
course_details
,
cls
=
CourseSettingsEncoder
)
'course_details'
:
json
.
dumps
(
course_details
,
cls
=
CourseSettingsEncoder
)
})
})
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
def
course_config_advanced_page
(
request
,
org
,
course
,
name
):
def
course_config_advanced_page
(
request
,
org
,
course
,
name
):
...
@@ -1203,6 +1229,7 @@ def course_config_advanced_page(request, org, course, name):
...
@@ -1203,6 +1229,7 @@ def course_config_advanced_page(request, org, course, name):
'advanced_dict'
:
json
.
dumps
(
CourseMetadata
.
fetch
(
location
)),
'advanced_dict'
:
json
.
dumps
(
CourseMetadata
.
fetch
(
location
)),
})
})
@expect_json
@expect_json
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
...
@@ -1234,6 +1261,7 @@ def course_settings_updates(request, org, course, name, section):
...
@@ -1234,6 +1261,7 @@ def course_settings_updates(request, org, course, name, section):
return
HttpResponse
(
json
.
dumps
(
manager
.
update_from_json
(
request
.
POST
),
cls
=
CourseSettingsEncoder
),
return
HttpResponse
(
json
.
dumps
(
manager
.
update_from_json
(
request
.
POST
),
cls
=
CourseSettingsEncoder
),
mimetype
=
"application/json"
)
mimetype
=
"application/json"
)
@expect_json
@expect_json
@login_required
@login_required
@ensure_csrf_cookie
@ensure_csrf_cookie
...
@@ -1359,6 +1387,7 @@ def asset_index(request, org, course, name):
...
@@ -1359,6 +1387,7 @@ def asset_index(request, org, course, name):
def
edge
(
request
):
def
edge
(
request
):
return
render_to_response
(
'university_profiles/edge.html'
,
{})
return
render_to_response
(
'university_profiles/edge.html'
,
{})
@login_required
@login_required
@expect_json
@expect_json
def
create_new_course
(
request
):
def
create_new_course
(
request
):
...
@@ -1411,6 +1440,7 @@ def create_new_course(request):
...
@@ -1411,6 +1440,7 @@ def create_new_course(request):
return
HttpResponse
(
json
.
dumps
({
'id'
:
new_course
.
location
.
url
()}))
return
HttpResponse
(
json
.
dumps
({
'id'
:
new_course
.
location
.
url
()}))
def
initialize_course_tabs
(
course
):
def
initialize_course_tabs
(
course
):
# set up the default tabs
# set up the default tabs
# I've added this because when we add static tabs, the LMS either expects a None for the tabs list or
# I've added this because when we add static tabs, the LMS either expects a None for the tabs list or
...
@@ -1428,6 +1458,7 @@ def initialize_course_tabs(course):
...
@@ -1428,6 +1458,7 @@ def initialize_course_tabs(course):
modulestore
(
'direct'
)
.
update_metadata
(
course
.
location
.
url
(),
own_metadata
(
course
))
modulestore
(
'direct'
)
.
update_metadata
(
course
.
location
.
url
(),
own_metadata
(
course
))
@ensure_csrf_cookie
@ensure_csrf_cookie
@login_required
@login_required
def
import_course
(
request
,
org
,
course
,
name
):
def
import_course
(
request
,
org
,
course
,
name
):
...
@@ -1505,6 +1536,7 @@ def import_course(request, org, course, name):
...
@@ -1505,6 +1536,7 @@ def import_course(request, org, course, name):
course_module
.
location
.
name
])
course_module
.
location
.
name
])
})
})
@ensure_csrf_cookie
@ensure_csrf_cookie
@login_required
@login_required
def
generate_export_course
(
request
,
org
,
course
,
name
):
def
generate_export_course
(
request
,
org
,
course
,
name
):
...
@@ -1556,6 +1588,7 @@ def export_course(request, org, course, name):
...
@@ -1556,6 +1588,7 @@ def export_course(request, org, course, name):
'successful_import_redirect_url'
:
''
'successful_import_redirect_url'
:
''
})
})
def
event
(
request
):
def
event
(
request
):
'''
'''
A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at
A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at
...
...
common/lib/capa/capa/calc.py
View file @
bdf1b1b0
...
@@ -183,7 +183,7 @@ def evaluator(variables, functions, string, cs=False):
...
@@ -183,7 +183,7 @@ def evaluator(variables, functions, string, cs=False):
# 0.33k or -17
# 0.33k or -17
number
=
(
Optional
(
minus
|
plus
)
+
inner_number
number
=
(
Optional
(
minus
|
plus
)
+
inner_number
+
Optional
(
CaselessLiteral
(
"E"
)
+
Optional
(
"-"
)
+
number_part
)
+
Optional
(
CaselessLiteral
(
"E"
)
+
Optional
(
(
plus
|
minus
)
)
+
number_part
)
+
Optional
(
number_suffix
))
+
Optional
(
number_suffix
))
number
=
number
.
setParseAction
(
number_parse_action
)
# Convert to number
number
=
number
.
setParseAction
(
number_parse_action
)
# Convert to number
...
...
common/lib/capa/capa/inputtypes.py
View file @
bdf1b1b0
...
@@ -366,6 +366,12 @@ class ChoiceGroup(InputTypeBase):
...
@@ -366,6 +366,12 @@ class ChoiceGroup(InputTypeBase):
self
.
choices
=
self
.
extract_choices
(
self
.
xml
)
self
.
choices
=
self
.
extract_choices
(
self
.
xml
)
@classmethod
def
get_attributes
(
cls
):
return
[
Attribute
(
"show_correctness"
,
"always"
),
Attribute
(
"submitted_message"
,
"Answer received."
)]
def
_extra_context
(
self
):
def
_extra_context
(
self
):
return
{
'input_type'
:
self
.
html_input_type
,
return
{
'input_type'
:
self
.
html_input_type
,
'choices'
:
self
.
choices
,
'choices'
:
self
.
choices
,
...
...
common/lib/capa/capa/templates/choicegroup.html
View file @
bdf1b1b0
<form
class=
"choicegroup capa_inputtype"
id=
"inputtype_${id}"
>
<form
class=
"choicegroup capa_inputtype"
id=
"inputtype_${id}"
>
<div
class=
"indicator_container"
>
<div
class=
"indicator_container"
>
% if input_type == 'checkbox' or not value:
% if input_type == 'checkbox' or not value:
% if status == 'unsubmitted':
% if status == 'unsubmitted'
or show_correctness == 'never'
:
<span
class=
"unanswered"
style=
"display:inline-block;"
id=
"status_${id}"
></span>
<span
class=
"unanswered"
style=
"display:inline-block;"
id=
"status_${id}"
></span>
% elif status == 'correct':
% elif status == 'correct':
<span
class=
"correct"
id=
"status_${id}"
></span>
<span
class=
"correct"
id=
"status_${id}"
></span>
...
@@ -26,7 +26,7 @@
...
@@ -26,7 +26,7 @@
else:
else:
correctness =
None
correctness =
None
%
>
%
>
% if correctness:
% if correctness
and not show_correctness=='never'
:
class="choicegroup_${correctness}"
class="choicegroup_${correctness}"
% endif
% endif
% endif
% endif
...
@@ -41,4 +41,7 @@
...
@@ -41,4 +41,7 @@
<span
id=
"answer_${id}"
></span>
<span
id=
"answer_${id}"
></span>
</fieldset>
</fieldset>
% if show_correctness == "never" and (value or status not in ['unsubmitted']):
<div
class=
"capa_alert"
>
${submitted_message}
</div>
%endif
</form>
</form>
common/lib/capa/capa/tests/test_inputtypes.py
View file @
bdf1b1b0
...
@@ -102,6 +102,8 @@ class ChoiceGroupTest(unittest.TestCase):
...
@@ -102,6 +102,8 @@ class ChoiceGroupTest(unittest.TestCase):
'choices'
:
[(
'foil1'
,
'<text>This is foil One.</text>'
),
'choices'
:
[(
'foil1'
,
'<text>This is foil One.</text>'
),
(
'foil2'
,
'<text>This is foil Two.</text>'
),
(
'foil2'
,
'<text>This is foil Two.</text>'
),
(
'foil3'
,
'This is foil Three.'
),
],
(
'foil3'
,
'This is foil Three.'
),
],
'show_correctness'
:
'always'
,
'submitted_message'
:
'Answer received.'
,
'name_array_suffix'
:
expected_suffix
,
# what is this for??
'name_array_suffix'
:
expected_suffix
,
# what is this for??
}
}
...
...
common/lib/capa/capa/tests/test_responsetypes.py
View file @
bdf1b1b0
...
@@ -19,7 +19,7 @@ from capa.xqueue_interface import dateformat
...
@@ -19,7 +19,7 @@ from capa.xqueue_interface import dateformat
class
ResponseTest
(
unittest
.
TestCase
):
class
ResponseTest
(
unittest
.
TestCase
):
""" Base class for tests of capa responses."""
""" Base class for tests of capa responses."""
xml_factory_class
=
None
xml_factory_class
=
None
def
setUp
(
self
):
def
setUp
(
self
):
...
@@ -43,7 +43,7 @@ class ResponseTest(unittest.TestCase):
...
@@ -43,7 +43,7 @@ class ResponseTest(unittest.TestCase):
for
input_str
in
incorrect_answers
:
for
input_str
in
incorrect_answers
:
result
=
problem
.
grade_answers
({
'1_2_1'
:
input_str
})
.
get_correctness
(
'1_2_1'
)
result
=
problem
.
grade_answers
({
'1_2_1'
:
input_str
})
.
get_correctness
(
'1_2_1'
)
self
.
assertEqual
(
result
,
'incorrect'
,
self
.
assertEqual
(
result
,
'incorrect'
,
msg
=
"
%
s should be marked incorrect"
%
str
(
input_str
))
msg
=
"
%
s should be marked incorrect"
%
str
(
input_str
))
class
MultiChoiceResponseTest
(
ResponseTest
):
class
MultiChoiceResponseTest
(
ResponseTest
):
...
@@ -61,7 +61,7 @@ class MultiChoiceResponseTest(ResponseTest):
...
@@ -61,7 +61,7 @@ class MultiChoiceResponseTest(ResponseTest):
def
test_named_multiple_choice_grade
(
self
):
def
test_named_multiple_choice_grade
(
self
):
problem
=
self
.
build_problem
(
choices
=
[
False
,
True
,
False
],
problem
=
self
.
build_problem
(
choices
=
[
False
,
True
,
False
],
choice_names
=
[
"foil_1"
,
"foil_2"
,
"foil_3"
])
choice_names
=
[
"foil_1"
,
"foil_2"
,
"foil_3"
])
# Ensure that we get the expected grades
# Ensure that we get the expected grades
self
.
assert_grade
(
problem
,
'choice_foil_1'
,
'incorrect'
)
self
.
assert_grade
(
problem
,
'choice_foil_1'
,
'incorrect'
)
self
.
assert_grade
(
problem
,
'choice_foil_2'
,
'correct'
)
self
.
assert_grade
(
problem
,
'choice_foil_2'
,
'correct'
)
...
@@ -117,7 +117,7 @@ class ImageResponseTest(ResponseTest):
...
@@ -117,7 +117,7 @@ class ImageResponseTest(ResponseTest):
# Anything inside the rectangle (and along the borders) is correct
# Anything inside the rectangle (and along the borders) is correct
# Everything else is incorrect
# Everything else is incorrect
correct_inputs
=
[
"[12,19]"
,
"[10,10]"
,
"[20,20]"
,
correct_inputs
=
[
"[12,19]"
,
"[10,10]"
,
"[20,20]"
,
"[10,15]"
,
"[20,15]"
,
"[15,10]"
,
"[15,20]"
]
"[10,15]"
,
"[20,15]"
,
"[15,10]"
,
"[15,20]"
]
incorrect_inputs
=
[
"[4,6]"
,
"[25,15]"
,
"[15,40]"
,
"[15,4]"
]
incorrect_inputs
=
[
"[4,6]"
,
"[25,15]"
,
"[15,40]"
,
"[15,4]"
]
self
.
assert_multiple_grade
(
problem
,
correct_inputs
,
incorrect_inputs
)
self
.
assert_multiple_grade
(
problem
,
correct_inputs
,
incorrect_inputs
)
...
@@ -259,7 +259,7 @@ class OptionResponseTest(ResponseTest):
...
@@ -259,7 +259,7 @@ class OptionResponseTest(ResponseTest):
xml_factory_class
=
OptionResponseXMLFactory
xml_factory_class
=
OptionResponseXMLFactory
def
test_grade
(
self
):
def
test_grade
(
self
):
problem
=
self
.
build_problem
(
options
=
[
"first"
,
"second"
,
"third"
],
problem
=
self
.
build_problem
(
options
=
[
"first"
,
"second"
,
"third"
],
correct_option
=
"second"
)
correct_option
=
"second"
)
# Assert that we get the expected grades
# Assert that we get the expected grades
...
@@ -374,8 +374,8 @@ class StringResponseTest(ResponseTest):
...
@@ -374,8 +374,8 @@ class StringResponseTest(ResponseTest):
hints
=
[(
"wisconsin"
,
"wisc"
,
"The state capital of Wisconsin is Madison"
),
hints
=
[(
"wisconsin"
,
"wisc"
,
"The state capital of Wisconsin is Madison"
),
(
"minnesota"
,
"minn"
,
"The state capital of Minnesota is St. Paul"
)]
(
"minnesota"
,
"minn"
,
"The state capital of Minnesota is St. Paul"
)]
problem
=
self
.
build_problem
(
answer
=
"Michigan"
,
problem
=
self
.
build_problem
(
answer
=
"Michigan"
,
case_sensitive
=
False
,
case_sensitive
=
False
,
hints
=
hints
)
hints
=
hints
)
# We should get a hint for Wisconsin
# We should get a hint for Wisconsin
...
@@ -543,7 +543,7 @@ class ChoiceResponseTest(ResponseTest):
...
@@ -543,7 +543,7 @@ class ChoiceResponseTest(ResponseTest):
xml_factory_class
=
ChoiceResponseXMLFactory
xml_factory_class
=
ChoiceResponseXMLFactory
def
test_radio_group_grade
(
self
):
def
test_radio_group_grade
(
self
):
problem
=
self
.
build_problem
(
choice_type
=
'radio'
,
problem
=
self
.
build_problem
(
choice_type
=
'radio'
,
choices
=
[
False
,
True
,
False
])
choices
=
[
False
,
True
,
False
])
# Check that we get the expected results
# Check that we get the expected results
...
@@ -601,17 +601,17 @@ class NumericalResponseTest(ResponseTest):
...
@@ -601,17 +601,17 @@ class NumericalResponseTest(ResponseTest):
correct_responses
=
[
"4"
,
"4.0"
,
"4.00"
]
correct_responses
=
[
"4"
,
"4.0"
,
"4.00"
]
incorrect_responses
=
[
""
,
"3.9"
,
"4.1"
,
"0"
]
incorrect_responses
=
[
""
,
"3.9"
,
"4.1"
,
"0"
]
self
.
assert_multiple_grade
(
problem
,
correct_responses
,
incorrect_responses
)
self
.
assert_multiple_grade
(
problem
,
correct_responses
,
incorrect_responses
)
def
test_grade_decimal_tolerance
(
self
):
def
test_grade_decimal_tolerance
(
self
):
problem
=
self
.
build_problem
(
question_text
=
"What is 2 + 2 approximately?"
,
problem
=
self
.
build_problem
(
question_text
=
"What is 2 + 2 approximately?"
,
explanation
=
"The answer is 4"
,
explanation
=
"The answer is 4"
,
answer
=
4
,
answer
=
4
,
tolerance
=
0.1
)
tolerance
=
0.1
)
correct_responses
=
[
"4.0"
,
"4.00"
,
"4.09"
,
"3.91"
]
correct_responses
=
[
"4.0"
,
"4.00"
,
"4.09"
,
"3.91"
]
incorrect_responses
=
[
""
,
"4.11"
,
"3.89"
,
"0"
]
incorrect_responses
=
[
""
,
"4.11"
,
"3.89"
,
"0"
]
self
.
assert_multiple_grade
(
problem
,
correct_responses
,
incorrect_responses
)
self
.
assert_multiple_grade
(
problem
,
correct_responses
,
incorrect_responses
)
def
test_grade_percent_tolerance
(
self
):
def
test_grade_percent_tolerance
(
self
):
problem
=
self
.
build_problem
(
question_text
=
"What is 2 + 2 approximately?"
,
problem
=
self
.
build_problem
(
question_text
=
"What is 2 + 2 approximately?"
,
explanation
=
"The answer is 4"
,
explanation
=
"The answer is 4"
,
...
@@ -642,6 +642,15 @@ class NumericalResponseTest(ResponseTest):
...
@@ -642,6 +642,15 @@ class NumericalResponseTest(ResponseTest):
incorrect_responses
=
[
""
,
"2.11"
,
"1.89"
,
"0"
]
incorrect_responses
=
[
""
,
"2.11"
,
"1.89"
,
"0"
]
self
.
assert_multiple_grade
(
problem
,
correct_responses
,
incorrect_responses
)
self
.
assert_multiple_grade
(
problem
,
correct_responses
,
incorrect_responses
)
def
test_exponential_answer
(
self
):
problem
=
self
.
build_problem
(
question_text
=
"What 5 * 10?"
,
explanation
=
"The answer is 50"
,
answer
=
"5e+1"
)
correct_responses
=
[
"50"
,
"50.0"
,
"5e1"
,
"5e+1"
,
"50e0"
,
"500e-1"
]
incorrect_responses
=
[
""
,
"3.9"
,
"4.1"
,
"0"
,
"5.01e1"
]
self
.
assert_multiple_grade
(
problem
,
correct_responses
,
incorrect_responses
)
class
CustomResponseTest
(
ResponseTest
):
class
CustomResponseTest
(
ResponseTest
):
from
response_xml_factory
import
CustomResponseXMLFactory
from
response_xml_factory
import
CustomResponseXMLFactory
...
@@ -667,7 +676,7 @@ class CustomResponseTest(ResponseTest):
...
@@ -667,7 +676,7 @@ class CustomResponseTest(ResponseTest):
# The code can also set the global overall_message (str)
# The code can also set the global overall_message (str)
# to pass a message that applies to the whole response
# to pass a message that applies to the whole response
inline_script
=
textwrap
.
dedent
(
"""
inline_script
=
textwrap
.
dedent
(
"""
messages[0] = "Test Message"
messages[0] = "Test Message"
overall_message = "Overall message"
overall_message = "Overall message"
"""
)
"""
)
problem
=
self
.
build_problem
(
answer
=
inline_script
)
problem
=
self
.
build_problem
(
answer
=
inline_script
)
...
@@ -687,14 +696,14 @@ class CustomResponseTest(ResponseTest):
...
@@ -687,14 +696,14 @@ class CustomResponseTest(ResponseTest):
def
test_function_code_single_input
(
self
):
def
test_function_code_single_input
(
self
):
# For function code, we pass in these arguments:
# For function code, we pass in these arguments:
#
#
# 'expect' is the expect attribute of the <customresponse>
# 'expect' is the expect attribute of the <customresponse>
#
#
# 'answer_given' is the answer the student gave (if there is just one input)
# 'answer_given' is the answer the student gave (if there is just one input)
# or an ordered list of answers (if there are multiple inputs)
# or an ordered list of answers (if there are multiple inputs)
#
#
#
# The function should return a dict of the form
#
# The function should return a dict of the form
# { 'ok': BOOL, 'msg': STRING }
# { 'ok': BOOL, 'msg': STRING }
#
#
script
=
textwrap
.
dedent
(
"""
script
=
textwrap
.
dedent
(
"""
...
@@ -727,7 +736,7 @@ class CustomResponseTest(ResponseTest):
...
@@ -727,7 +736,7 @@ class CustomResponseTest(ResponseTest):
def
test_function_code_multiple_input_no_msg
(
self
):
def
test_function_code_multiple_input_no_msg
(
self
):
# Check functions also have the option of returning
# Check functions also have the option of returning
# a single boolean value
# a single boolean value
# If true, mark all the inputs correct
# If true, mark all the inputs correct
# If false, mark all the inputs incorrect
# If false, mark all the inputs incorrect
script
=
textwrap
.
dedent
(
"""
script
=
textwrap
.
dedent
(
"""
...
@@ -736,7 +745,7 @@ class CustomResponseTest(ResponseTest):
...
@@ -736,7 +745,7 @@ class CustomResponseTest(ResponseTest):
answer_given[1] == expect)
answer_given[1] == expect)
"""
)
"""
)
problem
=
self
.
build_problem
(
script
=
script
,
cfn
=
"check_func"
,
problem
=
self
.
build_problem
(
script
=
script
,
cfn
=
"check_func"
,
expect
=
"42"
,
num_inputs
=
2
)
expect
=
"42"
,
num_inputs
=
2
)
# Correct answer -- expect both inputs marked correct
# Correct answer -- expect both inputs marked correct
...
@@ -764,10 +773,10 @@ class CustomResponseTest(ResponseTest):
...
@@ -764,10 +773,10 @@ class CustomResponseTest(ResponseTest):
# If the <customresponse> has multiple inputs associated with it,
# If the <customresponse> has multiple inputs associated with it,
# the check function can return a dict of the form:
# the check function can return a dict of the form:
#
#
# {'overall_message': STRING,
# {'overall_message': STRING,
# 'input_list': [{'ok': BOOL, 'msg': STRING}, ...] }
# 'input_list': [{'ok': BOOL, 'msg': STRING}, ...] }
#
#
# 'overall_message' is displayed at the end of the response
# 'overall_message' is displayed at the end of the response
#
#
# 'input_list' contains dictionaries representing the correctness
# 'input_list' contains dictionaries representing the correctness
...
@@ -784,7 +793,7 @@ class CustomResponseTest(ResponseTest):
...
@@ -784,7 +793,7 @@ class CustomResponseTest(ResponseTest):
{'ok': check3, 'msg': 'Feedback 3'} ] }
{'ok': check3, 'msg': 'Feedback 3'} ] }
"""
)
"""
)
problem
=
self
.
build_problem
(
script
=
script
,
problem
=
self
.
build_problem
(
script
=
script
,
cfn
=
"check_func"
,
num_inputs
=
3
)
cfn
=
"check_func"
,
num_inputs
=
3
)
# Grade the inputs (one input incorrect)
# Grade the inputs (one input incorrect)
...
@@ -821,11 +830,11 @@ class CustomResponseTest(ResponseTest):
...
@@ -821,11 +830,11 @@ class CustomResponseTest(ResponseTest):
check1 = (int(answer_given[0]) == 1)
check1 = (int(answer_given[0]) == 1)
check2 = (int(answer_given[1]) == 2)
check2 = (int(answer_given[1]) == 2)
check3 = (int(answer_given[2]) == 3)
check3 = (int(answer_given[2]) == 3)
return {'ok': (check1 and check2 and check3),
return {'ok': (check1 and check2 and check3),
'msg': 'Message text'}
'msg': 'Message text'}
"""
)
"""
)
problem
=
self
.
build_problem
(
script
=
script
,
problem
=
self
.
build_problem
(
script
=
script
,
cfn
=
"check_func"
,
num_inputs
=
3
)
cfn
=
"check_func"
,
num_inputs
=
3
)
# Grade the inputs (one input incorrect)
# Grade the inputs (one input incorrect)
...
@@ -862,7 +871,7 @@ class CustomResponseTest(ResponseTest):
...
@@ -862,7 +871,7 @@ class CustomResponseTest(ResponseTest):
# Expect that an exception gets raised when we check the answer
# Expect that an exception gets raised when we check the answer
with
self
.
assertRaises
(
Exception
):
with
self
.
assertRaises
(
Exception
):
problem
.
grade_answers
({
'1_2_1'
:
'42'
})
problem
.
grade_answers
({
'1_2_1'
:
'42'
})
def
test_invalid_dict_exception
(
self
):
def
test_invalid_dict_exception
(
self
):
# Construct a script that passes back an invalid dict format
# Construct a script that passes back an invalid dict format
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
bdf1b1b0
...
@@ -121,7 +121,7 @@ class Textbook(object):
...
@@ -121,7 +121,7 @@ class Textbook(object):
return
table_of_contents
return
table_of_contents
class
TextbookList
(
ModelType
):
class
TextbookList
(
List
):
def
from_json
(
self
,
values
):
def
from_json
(
self
,
values
):
textbooks
=
[]
textbooks
=
[]
for
title
,
book_url
in
values
:
for
title
,
book_url
in
values
:
...
@@ -150,19 +150,19 @@ class TextbookList(ModelType):
...
@@ -150,19 +150,19 @@ class TextbookList(ModelType):
class
CourseDescriptor
(
SequenceDescriptor
):
class
CourseDescriptor
(
SequenceDescriptor
):
module_class
=
SequenceModule
module_class
=
SequenceModule
textbooks
=
TextbookList
(
help
=
"List of pairs of (title, url) for textbooks used in this course"
,
default
=
[],
scope
=
Scope
.
content
)
textbooks
=
TextbookList
(
help
=
"List of pairs of (title, url) for textbooks used in this course"
,
scope
=
Scope
.
content
)
wiki_slug
=
String
(
help
=
"Slug that points to the wiki for this course"
,
scope
=
Scope
.
content
)
wiki_slug
=
String
(
help
=
"Slug that points to the wiki for this course"
,
scope
=
Scope
.
content
)
enrollment_start
=
Date
(
help
=
"Date that enrollment for this class is opened"
,
scope
=
Scope
.
settings
)
enrollment_start
=
Date
(
help
=
"Date that enrollment for this class is opened"
,
scope
=
Scope
.
settings
)
enrollment_end
=
Date
(
help
=
"Date that enrollment for this class is closed"
,
scope
=
Scope
.
settings
)
enrollment_end
=
Date
(
help
=
"Date that enrollment for this class is closed"
,
scope
=
Scope
.
settings
)
start
=
Date
(
help
=
"Start time when this module is visible"
,
scope
=
Scope
.
settings
)
start
=
Date
(
help
=
"Start time when this module is visible"
,
scope
=
Scope
.
settings
)
end
=
Date
(
help
=
"Date that this class ends"
,
scope
=
Scope
.
settings
)
end
=
Date
(
help
=
"Date that this class ends"
,
scope
=
Scope
.
settings
)
advertised_start
=
StringOrDate
(
help
=
"Date that this course is advertised to start"
,
scope
=
Scope
.
settings
)
advertised_start
=
StringOrDate
(
help
=
"Date that this course is advertised to start"
,
scope
=
Scope
.
settings
)
grading_policy
=
Object
(
help
=
"Grading policy definition for this class"
,
scope
=
Scope
.
content
,
default
=
{}
)
grading_policy
=
Object
(
help
=
"Grading policy definition for this class"
,
scope
=
Scope
.
content
)
show_calculator
=
Boolean
(
help
=
"Whether to show the calculator in this course"
,
default
=
False
,
scope
=
Scope
.
settings
)
show_calculator
=
Boolean
(
help
=
"Whether to show the calculator in this course"
,
default
=
False
,
scope
=
Scope
.
settings
)
display_name
=
String
(
help
=
"Display name for this module"
,
scope
=
Scope
.
settings
)
display_name
=
String
(
help
=
"Display name for this module"
,
scope
=
Scope
.
settings
)
tabs
=
List
(
help
=
"List of tabs to enable in this course"
,
scope
=
Scope
.
settings
)
tabs
=
List
(
help
=
"List of tabs to enable in this course"
,
scope
=
Scope
.
settings
)
end_of_course_survey_url
=
String
(
help
=
"Url for the end-of-course survey"
,
scope
=
Scope
.
settings
)
end_of_course_survey_url
=
String
(
help
=
"Url for the end-of-course survey"
,
scope
=
Scope
.
settings
)
discussion_blackouts
=
List
(
help
=
"List of pairs of start/end dates for discussion blackouts"
,
scope
=
Scope
.
settings
,
default
=
[]
)
discussion_blackouts
=
List
(
help
=
"List of pairs of start/end dates for discussion blackouts"
,
scope
=
Scope
.
settings
)
discussion_topics
=
Object
(
discussion_topics
=
Object
(
help
=
"Map of topics names to ids"
,
help
=
"Map of topics names to ids"
,
scope
=
Scope
.
settings
,
scope
=
Scope
.
settings
,
...
@@ -174,11 +174,12 @@ class CourseDescriptor(SequenceDescriptor):
...
@@ -174,11 +174,12 @@ class CourseDescriptor(SequenceDescriptor):
is_new
=
Boolean
(
help
=
"Whether this course should be flagged as new"
,
scope
=
Scope
.
settings
)
is_new
=
Boolean
(
help
=
"Whether this course should be flagged as new"
,
scope
=
Scope
.
settings
)
no_grade
=
Boolean
(
help
=
"True if this course isn't graded"
,
default
=
False
,
scope
=
Scope
.
settings
)
no_grade
=
Boolean
(
help
=
"True if this course isn't graded"
,
default
=
False
,
scope
=
Scope
.
settings
)
disable_progress_graph
=
Boolean
(
help
=
"True if this course shouldn't display the progress graph"
,
default
=
False
,
scope
=
Scope
.
settings
)
disable_progress_graph
=
Boolean
(
help
=
"True if this course shouldn't display the progress graph"
,
default
=
False
,
scope
=
Scope
.
settings
)
pdf_textbooks
=
List
(
help
=
"List of dictionaries containing pdf_textbook configuration"
,
default
=
None
,
scope
=
Scope
.
settings
)
pdf_textbooks
=
List
(
help
=
"List of dictionaries containing pdf_textbook configuration"
,
scope
=
Scope
.
settings
)
remote_gradebook
=
Object
(
scope
=
Scope
.
settings
,
default
=
{})
html_textbooks
=
List
(
help
=
"List of dictionaries containing html_textbook configuration"
,
scope
=
Scope
.
settings
)
remote_gradebook
=
Object
(
scope
=
Scope
.
settings
)
allow_anonymous
=
Boolean
(
scope
=
Scope
.
settings
,
default
=
True
)
allow_anonymous
=
Boolean
(
scope
=
Scope
.
settings
,
default
=
True
)
allow_anonymous_to_peers
=
Boolean
(
scope
=
Scope
.
settings
,
default
=
False
)
allow_anonymous_to_peers
=
Boolean
(
scope
=
Scope
.
settings
,
default
=
False
)
advanced_modules
=
List
(
help
=
"Beta modules used in your course"
,
default
=
[],
scope
=
Scope
.
settings
)
advanced_modules
=
List
(
help
=
"Beta modules used in your course"
,
scope
=
Scope
.
settings
)
has_children
=
True
has_children
=
True
info_sidebar_name
=
String
(
scope
=
Scope
.
settings
,
default
=
'Course Handouts'
)
info_sidebar_name
=
String
(
scope
=
Scope
.
settings
,
default
=
'Course Handouts'
)
...
@@ -256,27 +257,27 @@ class CourseDescriptor(SequenceDescriptor):
...
@@ -256,27 +257,27 @@ class CourseDescriptor(SequenceDescriptor):
"min_count"
:
12
,
"min_count"
:
12
,
"drop_count"
:
2
,
"drop_count"
:
2
,
"short_label"
:
"HW"
,
"short_label"
:
"HW"
,
"weight"
:
15
"weight"
:
0.
15
},
},
{
{
"type"
:
"Lab"
,
"type"
:
"Lab"
,
"min_count"
:
12
,
"min_count"
:
12
,
"drop_count"
:
2
,
"drop_count"
:
2
,
"weight"
:
15
"weight"
:
0.
15
},
},
{
{
"type"
:
"Midterm Exam"
,
"type"
:
"Midterm Exam"
,
"short_label"
:
"Midterm"
,
"short_label"
:
"Midterm"
,
"min_count"
:
1
,
"min_count"
:
1
,
"drop_count"
:
0
,
"drop_count"
:
0
,
"weight"
:
30
"weight"
:
0.3
},
},
{
{
"type"
:
"Final Exam"
,
"type"
:
"Final Exam"
,
"short_label"
:
"Final"
,
"short_label"
:
"Final"
,
"min_count"
:
1
,
"min_count"
:
1
,
"drop_count"
:
0
,
"drop_count"
:
0
,
"weight"
:
40
"weight"
:
0.4
}
}
],
],
"GRADE_CUTOFFS"
:
{
"GRADE_CUTOFFS"
:
{
...
...
common/lib/xmodule/xmodule/foldit_module.py
View file @
bdf1b1b0
...
@@ -85,7 +85,10 @@ class FolditModule(XModule):
...
@@ -85,7 +85,10 @@ class FolditModule(XModule):
"""
"""
from
foldit.models
import
Score
from
foldit.models
import
Score
return
[(
e
[
'username'
],
e
[
'score'
])
for
e
in
Score
.
get_tops_n
(
10
)]
leaders
=
[(
e
[
'username'
],
e
[
'score'
])
for
e
in
Score
.
get_tops_n
(
10
)]
leaders
.
sort
(
key
=
lambda
x
:
x
[
1
])
return
leaders
def
get_html
(
self
):
def
get_html
(
self
):
"""
"""
...
...
common/lib/xmodule/xmodule/modulestore/tests/factories.py
View file @
bdf1b1b0
...
@@ -46,10 +46,10 @@ class XModuleCourseFactory(Factory):
...
@@ -46,10 +46,10 @@ class XModuleCourseFactory(Factory):
new_course
.
start
=
gmtime
()
new_course
.
start
=
gmtime
()
new_course
.
tabs
=
[{
"type"
:
"courseware"
},
new_course
.
tabs
=
[{
"type"
:
"courseware"
},
{
"type"
:
"course_info"
,
"name"
:
"Course Info"
},
{
"type"
:
"course_info"
,
"name"
:
"Course Info"
},
{
"type"
:
"discussion"
,
"name"
:
"Discussion"
},
{
"type"
:
"discussion"
,
"name"
:
"Discussion"
},
{
"type"
:
"wiki"
,
"name"
:
"Wiki"
},
{
"type"
:
"wiki"
,
"name"
:
"Wiki"
},
{
"type"
:
"progress"
,
"name"
:
"Progress"
}]
{
"type"
:
"progress"
,
"name"
:
"Progress"
}]
# Update the data in the mongo datastore
# Update the data in the mongo datastore
store
.
update_metadata
(
new_course
.
location
.
url
(),
own_metadata
(
new_course
))
store
.
update_metadata
(
new_course
.
location
.
url
(),
own_metadata
(
new_course
))
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_location.py
View file @
bdf1b1b0
...
@@ -119,11 +119,11 @@ def test_equality():
...
@@ -119,11 +119,11 @@ def test_equality():
# All the cleaning functions should do the same thing with these
# All the cleaning functions should do the same thing with these
general_pairs
=
[(
''
,
''
),
general_pairs
=
[(
''
,
''
),
(
' '
,
'_'
),
(
' '
,
'_'
),
(
'abc,'
,
'abc_'
),
(
'abc,'
,
'abc_'
),
(
'ab fg!@//
\\
aj'
,
'ab_fg_aj'
),
(
'ab fg!@//
\\
aj'
,
'ab_fg_aj'
),
(
u"ab
\xA9
"
,
"ab_"
),
# no unicode allowed for now
(
u"ab
\xA9
"
,
"ab_"
),
# no unicode allowed for now
]
]
def
test_clean
():
def
test_clean
():
...
@@ -131,7 +131,7 @@ def test_clean():
...
@@ -131,7 +131,7 @@ def test_clean():
(
'a:b'
,
'a_b'
),
# no colons in non-name components
(
'a:b'
,
'a_b'
),
# no colons in non-name components
(
'a-b'
,
'a-b'
),
# dashes ok
(
'a-b'
,
'a-b'
),
# dashes ok
(
'a.b'
,
'a.b'
),
# dot ok
(
'a.b'
,
'a.b'
),
# dot ok
]
]
for
input
,
output
in
pairs
:
for
input
,
output
in
pairs
:
assert_equals
(
Location
.
clean
(
input
),
output
)
assert_equals
(
Location
.
clean
(
input
),
output
)
...
@@ -141,17 +141,17 @@ def test_clean_for_url_name():
...
@@ -141,17 +141,17 @@ def test_clean_for_url_name():
(
'a:b'
,
'a:b'
),
# colons ok in names
(
'a:b'
,
'a:b'
),
# colons ok in names
(
'a-b'
,
'a-b'
),
# dashes ok in names
(
'a-b'
,
'a-b'
),
# dashes ok in names
(
'a.b'
,
'a.b'
),
# dot ok in names
(
'a.b'
,
'a.b'
),
# dot ok in names
]
]
for
input
,
output
in
pairs
:
for
input
,
output
in
pairs
:
assert_equals
(
Location
.
clean_for_url_name
(
input
),
output
)
assert_equals
(
Location
.
clean_for_url_name
(
input
),
output
)
def
test_clean_for_html
():
def
test_clean_for_html
():
pairs
=
general_pairs
+
[
pairs
=
general_pairs
+
[
(
"a:b"
,
"a_b"
),
# no colons for html use
(
"a:b"
,
"a_b"
),
# no colons for html use
(
"a-b"
,
"a-b"
),
# dashes ok (though need to be replaced in various use locations. ugh.)
(
"a-b"
,
"a-b"
),
# dashes ok (though need to be replaced in various use locations. ugh.)
(
'a.b'
,
'a_b'
),
# no dots.
(
'a.b'
,
'a_b'
),
# no dots.
]
]
for
input
,
output
in
pairs
:
for
input
,
output
in
pairs
:
assert_equals
(
Location
.
clean_for_html
(
input
),
output
)
assert_equals
(
Location
.
clean_for_html
(
input
),
output
)
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py
View file @
bdf1b1b0
...
@@ -12,7 +12,7 @@ def check_path_to_location(modulestore):
...
@@ -12,7 +12,7 @@ def check_path_to_location(modulestore):
(
"edX/toy/2012_Fall"
,
"Overview"
,
"Welcome"
,
None
)),
(
"edX/toy/2012_Fall"
,
"Overview"
,
"Welcome"
,
None
)),
(
"i4x://edX/toy/chapter/Overview"
,
(
"i4x://edX/toy/chapter/Overview"
,
(
"edX/toy/2012_Fall"
,
"Overview"
,
None
,
None
)),
(
"edX/toy/2012_Fall"
,
"Overview"
,
None
,
None
)),
)
)
course_id
=
"edX/toy/2012_Fall"
course_id
=
"edX/toy/2012_Fall"
for
location
,
expected
in
should_work
:
for
location
,
expected
in
should_work
:
...
@@ -20,6 +20,6 @@ def check_path_to_location(modulestore):
...
@@ -20,6 +20,6 @@ def check_path_to_location(modulestore):
not_found
=
(
not_found
=
(
"i4x://edX/toy/video/WelcomeX"
,
"i4x://edX/toy/course/NotHome"
"i4x://edX/toy/video/WelcomeX"
,
"i4x://edX/toy/course/NotHome"
)
)
for
location
in
not_found
:
for
location
in
not_found
:
assert_raises
(
ItemNotFoundError
,
path_to_location
,
modulestore
,
course_id
,
location
)
assert_raises
(
ItemNotFoundError
,
path_to_location
,
modulestore
,
course_id
,
location
)
jenkins/test.sh
View file @
bdf1b1b0
...
@@ -38,12 +38,15 @@ pip install -q -r test-requirements.txt
...
@@ -38,12 +38,15 @@ pip install -q -r test-requirements.txt
yes w | pip install
-q
-r
requirements.txt
yes w | pip install
-q
-r
requirements.txt
rake clobber
rake clobber
rake pep8
rake pylint
TESTS_FAILED
=
0
TESTS_FAILED
=
0
rake test_cms[false]
||
TESTS_FAILED
=
1
rake test_cms[false]
||
TESTS_FAILED
=
1
rake test_lms[false]
||
TESTS_FAILED
=
1
rake test_lms[false]
||
TESTS_FAILED
=
1
rake test_common/lib/capa
||
TESTS_FAILED
=
1
rake test_common/lib/capa
||
TESTS_FAILED
=
1
rake test_common/lib/xmodule
||
TESTS_FAILED
=
1
rake test_common/lib/xmodule
||
TESTS_FAILED
=
1
# Don't run the lms jasmine tests for now because
# Don't run the lms jasmine tests for now because
# they mostly all fail anyhow
# they mostly all fail anyhow
# rake phantomjs_jasmine_lms || true
# rake phantomjs_jasmine_lms || true
rake phantomjs_jasmine_cms
||
TESTS_FAILED
=
1
rake phantomjs_jasmine_cms
||
TESTS_FAILED
=
1
...
...
lms/djangoapps/courseware/tabs.py
View file @
bdf1b1b0
...
@@ -131,6 +131,17 @@ def _pdf_textbooks(tab, user, course, active_page):
...
@@ -131,6 +131,17 @@ def _pdf_textbooks(tab, user, course, active_page):
for
index
,
textbook
in
enumerate
(
course
.
pdf_textbooks
)]
for
index
,
textbook
in
enumerate
(
course
.
pdf_textbooks
)]
return
[]
return
[]
def
_html_textbooks
(
tab
,
user
,
course
,
active_page
):
"""
Generates one tab per textbook. Only displays if user is authenticated.
"""
if
user
.
is_authenticated
():
# since there can be more than one textbook, active_page is e.g. "book/0".
return
[
CourseTab
(
textbook
[
'tab_title'
],
reverse
(
'html_book'
,
args
=
[
course
.
id
,
index
]),
active_page
==
"htmltextbook/{0}"
.
format
(
index
))
for
index
,
textbook
in
enumerate
(
course
.
html_textbooks
)]
return
[]
def
_staff_grading
(
tab
,
user
,
course
,
active_page
):
def
_staff_grading
(
tab
,
user
,
course
,
active_page
):
if
has_access
(
user
,
course
,
'staff'
):
if
has_access
(
user
,
course
,
'staff'
):
link
=
reverse
(
'staff_grading'
,
args
=
[
course
.
id
])
link
=
reverse
(
'staff_grading'
,
args
=
[
course
.
id
])
...
@@ -210,6 +221,7 @@ VALID_TAB_TYPES = {
...
@@ -210,6 +221,7 @@ VALID_TAB_TYPES = {
'external_link'
:
TabImpl
(
key_checker
([
'name'
,
'link'
]),
_external_link
),
'external_link'
:
TabImpl
(
key_checker
([
'name'
,
'link'
]),
_external_link
),
'textbooks'
:
TabImpl
(
null_validator
,
_textbooks
),
'textbooks'
:
TabImpl
(
null_validator
,
_textbooks
),
'pdf_textbooks'
:
TabImpl
(
null_validator
,
_pdf_textbooks
),
'pdf_textbooks'
:
TabImpl
(
null_validator
,
_pdf_textbooks
),
'html_textbooks'
:
TabImpl
(
null_validator
,
_html_textbooks
),
'progress'
:
TabImpl
(
need_name
,
_progress
),
'progress'
:
TabImpl
(
need_name
,
_progress
),
'static_tab'
:
TabImpl
(
key_checker
([
'name'
,
'url_slug'
]),
_static_tab
),
'static_tab'
:
TabImpl
(
key_checker
([
'name'
,
'url_slug'
]),
_static_tab
),
'peer_grading'
:
TabImpl
(
null_validator
,
_peer_grading
),
'peer_grading'
:
TabImpl
(
null_validator
,
_peer_grading
),
...
...
lms/djangoapps/django_comment_client/utils.py
View file @
bdf1b1b0
...
@@ -22,7 +22,6 @@ import pystache_custom as pystache
...
@@ -22,7 +22,6 @@ import pystache_custom as pystache
from
xmodule.modulestore
import
Location
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.search
import
path_to_location
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -170,7 +169,6 @@ def initialize_discussion_info(course):
...
@@ -170,7 +169,6 @@ def initialize_discussion_info(course):
# get all discussion models within this course_id
# get all discussion models within this course_id
all_modules
=
modulestore
()
.
get_items
([
'i4x'
,
course
.
location
.
org
,
course
.
location
.
course
,
'discussion'
,
None
],
course_id
=
course_id
)
all_modules
=
modulestore
()
.
get_items
([
'i4x'
,
course
.
location
.
org
,
course
.
location
.
course
,
'discussion'
,
None
],
course_id
=
course_id
)
path_to_locations
=
{}
for
module
in
all_modules
:
for
module
in
all_modules
:
skip_module
=
False
skip_module
=
False
for
key
in
(
'discussion_id'
,
'discussion_category'
,
'discussion_target'
):
for
key
in
(
'discussion_id'
,
'discussion_category'
,
'discussion_target'
):
...
@@ -178,14 +176,6 @@ def initialize_discussion_info(course):
...
@@ -178,14 +176,6 @@ def initialize_discussion_info(course):
log
.
warning
(
"Required key '
%
s' not in discussion
%
s, leaving out of category map"
%
(
key
,
module
.
location
))
log
.
warning
(
"Required key '
%
s' not in discussion
%
s, leaving out of category map"
%
(
key
,
module
.
location
))
skip_module
=
True
skip_module
=
True
# cdodge: pre-compute the path_to_location. Note this can throw an exception for any
# dangling discussion modules
try
:
path_to_locations
[
module
.
location
]
=
path_to_location
(
modulestore
(),
course
.
id
,
module
.
location
)
except
NoPathToItem
:
log
.
warning
(
"Could not compute path_to_location for {0}. Perhaps this is an orphaned discussion module?!? Skipping..."
.
format
(
module
.
location
))
skip_module
=
True
if
skip_module
:
if
skip_module
:
continue
continue
...
@@ -248,7 +238,6 @@ def initialize_discussion_info(course):
...
@@ -248,7 +238,6 @@ def initialize_discussion_info(course):
_DISCUSSIONINFO
[
course
.
id
][
'id_map'
]
=
discussion_id_map
_DISCUSSIONINFO
[
course
.
id
][
'id_map'
]
=
discussion_id_map
_DISCUSSIONINFO
[
course
.
id
][
'category_map'
]
=
category_map
_DISCUSSIONINFO
[
course
.
id
][
'category_map'
]
=
category_map
_DISCUSSIONINFO
[
course
.
id
][
'timestamp'
]
=
datetime
.
now
()
_DISCUSSIONINFO
[
course
.
id
][
'timestamp'
]
=
datetime
.
now
()
_DISCUSSIONINFO
[
course
.
id
][
'path_to_location'
]
=
path_to_locations
class
JsonResponse
(
HttpResponse
):
class
JsonResponse
(
HttpResponse
):
...
@@ -405,21 +394,8 @@ def get_courseware_context(content, course):
...
@@ -405,21 +394,8 @@ def get_courseware_context(content, course):
location
=
id_map
[
id
][
"location"
]
.
url
()
location
=
id_map
[
id
][
"location"
]
.
url
()
title
=
id_map
[
id
][
"title"
]
title
=
id_map
[
id
][
"title"
]
# cdodge: did we pre-compute, if so, then let's use that rather than recomputing
url
=
reverse
(
'jump_to'
,
kwargs
=
{
"course_id"
:
course
.
location
.
course_id
,
if
'path_to_location'
in
_DISCUSSIONINFO
[
course
.
id
]
and
location
in
_DISCUSSIONINFO
[
course
.
id
][
'path_to_location'
]:
"location"
:
location
})
(
course_id
,
chapter
,
section
,
position
)
=
_DISCUSSIONINFO
[
course
.
id
][
'path_to_location'
][
location
]
else
:
try
:
(
course_id
,
chapter
,
section
,
position
)
=
path_to_location
(
modulestore
(),
course
.
id
,
location
)
except
NoPathToItem
:
# Object is not in the graph any longer, let's just get path to the base of the course
# so that we can at least return something to the caller
(
course_id
,
chapter
,
section
,
position
)
=
path_to_location
(
modulestore
(),
course
.
id
,
course
.
location
)
url
=
reverse
(
'courseware_position'
,
kwargs
=
{
"course_id"
:
course_id
,
"chapter"
:
chapter
,
"section"
:
section
,
"position"
:
position
})
content_info
=
{
"courseware_url"
:
url
,
"courseware_title"
:
title
}
content_info
=
{
"courseware_url"
:
url
,
"courseware_title"
:
title
}
return
content_info
return
content_info
...
...
lms/djangoapps/foldit/models.py
View file @
bdf1b1b0
...
@@ -59,7 +59,7 @@ class Score(models.Model):
...
@@ -59,7 +59,7 @@ class Score(models.Model):
scores
=
Score
.
objects
\
scores
=
Score
.
objects
\
.
filter
(
puzzle_id__in
=
puzzles
)
\
.
filter
(
puzzle_id__in
=
puzzles
)
\
.
annotate
(
total_score
=
models
.
Sum
(
'best_score'
))
\
.
annotate
(
total_score
=
models
.
Sum
(
'best_score'
))
\
.
order_by
(
'
-
total_score'
)[:
n
]
.
order_by
(
'total_score'
)[:
n
]
num
=
len
(
puzzles
)
num
=
len
(
puzzles
)
return
[{
'username'
:
s
.
user
.
username
,
return
[{
'username'
:
s
.
user
.
username
,
...
...
lms/djangoapps/foldit/tests.py
View file @
bdf1b1b0
...
@@ -143,11 +143,12 @@ class FolditTestCase(TestCase):
...
@@ -143,11 +143,12 @@ class FolditTestCase(TestCase):
def
test_SetPlayerPuzzleScores_manyplayers
(
self
):
def
test_SetPlayerPuzzleScores_manyplayers
(
self
):
"""
"""
Check that when we send scores from multiple users, the correct order
Check that when we send scores from multiple users, the correct order
of scores is displayed.
of scores is displayed. Note that, before being processed by
display_score, lower scores are better.
"""
"""
puzzle_id
=
[
'1'
]
puzzle_id
=
[
'1'
]
player1_score
=
0.0
7
player1_score
=
0.0
8
player2_score
=
0.0
8
player2_score
=
0.0
2
response1
=
self
.
make_puzzle_score_request
(
puzzle_id
,
player1_score
,
response1
=
self
.
make_puzzle_score_request
(
puzzle_id
,
player1_score
,
self
.
user
)
self
.
user
)
...
@@ -164,8 +165,12 @@ class FolditTestCase(TestCase):
...
@@ -164,8 +165,12 @@ class FolditTestCase(TestCase):
self
.
assertEqual
(
len
(
top_10
),
2
)
self
.
assertEqual
(
len
(
top_10
),
2
)
# Top score should be player2_score. Second should be player1_score
# Top score should be player2_score. Second should be player1_score
self
.
assertEqual
(
top_10
[
0
][
'score'
],
Score
.
display_score
(
player2_score
))
self
.
assertAlmostEqual
(
top_10
[
0
][
'score'
],
self
.
assertEqual
(
top_10
[
1
][
'score'
],
Score
.
display_score
(
player1_score
))
Score
.
display_score
(
player2_score
),
delta
=
0.5
)
self
.
assertAlmostEqual
(
top_10
[
1
][
'score'
],
Score
.
display_score
(
player1_score
),
delta
=
0.5
)
# Top score user should be self.user2.username
# Top score user should be self.user2.username
self
.
assertEqual
(
top_10
[
0
][
'username'
],
self
.
user2
.
username
)
self
.
assertEqual
(
top_10
[
0
][
'username'
],
self
.
user2
.
username
)
...
...
lms/djangoapps/staticbook/views.py
View file @
bdf1b1b0
from
lxml
import
etree
from
lxml
import
etree
# from django.conf import settings
from
django.contrib.auth.decorators
import
login_required
from
django.contrib.auth.decorators
import
login_required
from
django.http
import
Http404
from
mitxmako.shortcuts
import
render_to_response
from
mitxmako.shortcuts
import
render_to_response
from
courseware.access
import
has_access
from
courseware.access
import
has_access
...
@@ -15,6 +15,8 @@ def index(request, course_id, book_index, page=None):
...
@@ -15,6 +15,8 @@ def index(request, course_id, book_index, page=None):
staff_access
=
has_access
(
request
.
user
,
course
,
'staff'
)
staff_access
=
has_access
(
request
.
user
,
course
,
'staff'
)
book_index
=
int
(
book_index
)
book_index
=
int
(
book_index
)
if
book_index
<
0
or
book_index
>=
len
(
course
.
textbooks
):
raise
Http404
(
"Invalid book index value: {0}"
.
format
(
book_index
))
textbook
=
course
.
textbooks
[
book_index
]
textbook
=
course
.
textbooks
[
book_index
]
table_of_contents
=
textbook
.
table_of_contents
table_of_contents
=
textbook
.
table_of_contents
...
@@ -40,6 +42,8 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None):
...
@@ -40,6 +42,8 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None):
staff_access
=
has_access
(
request
.
user
,
course
,
'staff'
)
staff_access
=
has_access
(
request
.
user
,
course
,
'staff'
)
book_index
=
int
(
book_index
)
book_index
=
int
(
book_index
)
if
book_index
<
0
or
book_index
>=
len
(
course
.
pdf_textbooks
):
raise
Http404
(
"Invalid book index value: {0}"
.
format
(
book_index
))
textbook
=
course
.
pdf_textbooks
[
book_index
]
textbook
=
course
.
pdf_textbooks
[
book_index
]
def
remap_static_url
(
original_url
,
course
):
def
remap_static_url
(
original_url
,
course
):
...
@@ -67,3 +71,39 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None):
...
@@ -67,3 +71,39 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None):
'chapter'
:
chapter
,
'chapter'
:
chapter
,
'page'
:
page
,
'page'
:
page
,
'staff_access'
:
staff_access
})
'staff_access'
:
staff_access
})
@login_required
def
html_index
(
request
,
course_id
,
book_index
,
chapter
=
None
,
anchor_id
=
None
):
course
=
get_course_with_access
(
request
.
user
,
course_id
,
'load'
)
staff_access
=
has_access
(
request
.
user
,
course
,
'staff'
)
book_index
=
int
(
book_index
)
if
book_index
<
0
or
book_index
>=
len
(
course
.
html_textbooks
):
raise
Http404
(
"Invalid book index value: {0}"
.
format
(
book_index
))
textbook
=
course
.
html_textbooks
[
book_index
]
def
remap_static_url
(
original_url
,
course
):
input_url
=
"'"
+
original_url
+
"'"
output_url
=
replace_static_urls
(
input_url
,
course
.
metadata
[
'data_dir'
],
course_namespace
=
course
.
location
)
# strip off the quotes again...
return
output_url
[
1
:
-
1
]
if
'url'
in
textbook
:
textbook
[
'url'
]
=
remap_static_url
(
textbook
[
'url'
],
course
)
# then remap all the chapter URLs as well, if they are provided.
if
'chapters'
in
textbook
:
for
entry
in
textbook
[
'chapters'
]:
entry
[
'url'
]
=
remap_static_url
(
entry
[
'url'
],
course
)
return
render_to_response
(
'static_htmlbook.html'
,
{
'book_index'
:
book_index
,
'course'
:
course
,
'textbook'
:
textbook
,
'chapter'
:
chapter
,
'anchor_id'
:
anchor_id
,
'staff_access'
:
staff_access
})
lms/static/sass/course/_textbook.scss
View file @
bdf1b1b0
...
@@ -158,6 +158,19 @@ div.book-wrapper {
...
@@ -158,6 +158,19 @@ div.book-wrapper {
img
{
img
{
max-width
:
100%
;
max-width
:
100%
;
}
}
div
{
text-align
:
left
;
line-height
:
1
.6em
;
margin-left
:
5px
;
margin-right
:
5px
;
margin-top
:
5px
;
margin-bottom
:
5px
;
.Paragraph
,
h2
{
margin-top
:
10px
;
}
}
}
}
}
}
...
...
lms/templates/static_htmlbook.html
0 → 100644
View file @
bdf1b1b0
<
%
inherit
file=
"main.html"
/>
<
%
namespace
name=
'static'
file=
'static_content.html'
/>
<
%
block
name=
"title"
><title>
${course.number} Textbook
</title>
</
%
block>
<
%
block
name=
"headextra"
>
<
%
static:css
group=
'course'
/>
<
%
static:js
group=
'courseware'
/>
</
%
block>
<
%
block
name=
"js_extra"
>
<script
type=
"text/javascript"
>
(
function
(
$
)
{
$
.
fn
.
myHTMLViewer
=
function
(
options
)
{
var
urlToLoad
=
null
;
if
(
options
.
url
)
{
urlToLoad
=
options
.
url
;
}
var
chapterUrls
=
null
;
if
(
options
.
chapters
)
{
chapterUrls
=
options
.
chapters
;
}
var
chapterToLoad
=
1
;
if
(
options
.
chapterNum
)
{
// TODO: this should only be specified if there are
// chapters, and it should be in-bounds.
chapterToLoad
=
options
.
chapterNum
;
}
var
anchorToLoad
=
null
;
if
(
options
.
chapters
)
{
anchorToLoad
=
options
.
anchor_id
;
}
loadUrl
=
function
htmlViewLoadUrl
(
url
,
anchorId
)
{
// clear out previous load, if any:
parentElement
=
document
.
getElementById
(
'bookpage'
);
while
(
parentElement
.
hasChildNodes
())
parentElement
.
removeChild
(
parentElement
.
lastChild
);
// load new URL in:
$
(
'#bookpage'
).
load
(
url
);
// if there is an anchor set, then go to that location:
if
(
anchorId
!=
null
)
{
// TODO: add implementation....
}
};
loadChapterUrl
=
function
htmlViewLoadChapterUrl
(
chapterNum
,
anchorId
)
{
if
(
chapterNum
<
1
||
chapterNum
>
chapterUrls
.
length
)
{
return
;
}
var
chapterUrl
=
chapterUrls
[
chapterNum
-
1
];
loadUrl
(
chapterUrl
,
anchorId
);
};
// define navigation links for chapters:
if
(
chapterUrls
!=
null
)
{
var
loadChapterUrlHelper
=
function
(
i
)
{
return
function
(
event
)
{
// when opening a new chapter, always open to the top:
loadChapterUrl
(
i
,
null
);
};
};
for
(
var
index
=
1
;
index
<=
chapterUrls
.
length
;
index
+=
1
)
{
$
(
"#htmlchapter-"
+
index
).
click
(
loadChapterUrlHelper
(
index
));
}
}
// finally, load the appropriate url/page
if
(
urlToLoad
!=
null
)
{
loadUrl
(
urlToLoad
,
anchorToLoad
);
}
else
{
loadChapterUrl
(
chapterToLoad
,
anchorToLoad
);
}
}
})(
jQuery
);
$
(
document
).
ready
(
function
()
{
var
options
=
{};
%
if
'url'
in
textbook
:
options
.
url
=
"${textbook['url']}"
;
%
endif
%
if
'chapters'
in
textbook
:
var
chptrs
=
[];
%
for
chap
in
textbook
[
'chapters'
]:
chptrs
.
push
(
"${chap['url']}"
);
%
endfor
options
.
chapters
=
chptrs
;
%
endif
%
if
chapter
is
not
None
:
options
.
chapterNum
=
$
{
chapter
};
%
endif
%
if
anchor_id
is
not
None
:
options
.
anchor_id
=
$
{
anchor_id
};
%
endif
$
(
'#outerContainer'
).
myHTMLViewer
(
options
);
});
</script>
</
%
block>
<
%
include
file=
"/courseware/course_navigation.html"
args=
"active_page='htmltextbook/{0}'.format(book_index)"
/>
<div
id=
"outerContainer"
>
<div
id=
"mainContainer"
class=
"book-wrapper"
>
%if 'chapters' in textbook:
<section
aria-label=
"Textbook Navigation"
class=
"book-sidebar"
>
<ul
id=
"booknav"
class=
"treeview-booknav"
>
<
%
def
name=
"print_entry(entry, index_value)"
>
<li
id=
"htmlchapter-${index_value}"
>
<a
class=
"chapter"
>
${entry.get('title')}
</a>
</li>
</
%
def>
%for (index, entry) in enumerate(textbook['chapters']):
${print_entry(entry, index+1)}
% endfor
</ul>
</section>
%endif
<section
id=
"viewerContainer"
class=
"book"
>
<section
class=
"page"
>
<div
id=
"bookpage"
/>
</section>
</section>
</div>
</div>
lms/urls.py
View file @
bdf1b1b0
...
@@ -280,6 +280,15 @@ if settings.COURSEWARE_ENABLED:
...
@@ -280,6 +280,15 @@ if settings.COURSEWARE_ENABLED:
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/pdfbook/(?P<book_index>[^/]*)/chapter/(?P<chapter>[^/]*)/(?P<page>[^/]*)$'
,
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/pdfbook/(?P<book_index>[^/]*)/chapter/(?P<chapter>[^/]*)/(?P<page>[^/]*)$'
,
'staticbook.views.pdf_index'
),
'staticbook.views.pdf_index'
),
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/htmlbook/(?P<book_index>[^/]*)/$'
,
'staticbook.views.html_index'
,
name
=
"html_book"
),
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/htmlbook/(?P<book_index>[^/]*)/chapter/(?P<chapter>[^/]*)/$'
,
'staticbook.views.html_index'
),
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/htmlbook/(?P<book_index>[^/]*)/chapter/(?P<chapter>[^/]*)/(?P<anchor_id>[^/]*)/$'
,
'staticbook.views.html_index'
),
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/htmlbook/(?P<book_index>[^/]*)/(?P<anchor_id>[^/]*)/$'
,
'staticbook.views.html_index'
),
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/?$'
,
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/?$'
,
'courseware.views.index'
,
name
=
"courseware"
),
'courseware.views.index'
,
name
=
"courseware"
),
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/$'
,
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/$'
,
...
...
local-requirements.txt
View file @
bdf1b1b0
...
@@ -6,4 +6,4 @@
...
@@ -6,4 +6,4 @@
# XBlock:
# XBlock:
# Might change frequently, so put it in local-requirements.txt,
# Might change frequently, so put it in local-requirements.txt,
# but conceptually is an external package, so it is in a separate repo.
# but conceptually is an external package, so it is in a separate repo.
-e git+ssh://git@github.com/MITx/xmodule-debugger@
857dcfe8
#egg=XBlock
-e git+ssh://git@github.com/MITx/xmodule-debugger@
6d5c2443
#egg=XBlock
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