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
Show whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
396 additions
and
120 deletions
+396
-120
.ruby-version
+0
-0
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
+9
-0
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
+0
-0
common/lib/xmodule/xmodule/modulestore/tests/test_location.py
+0
-0
common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py
+0
-0
jenkins/test.sh
+3
-0
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
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
bdf1b1b0
...
...
@@ -10,10 +10,8 @@ from datetime import timedelta
import
json
from
fs.osfs
import
OSFS
import
copy
from
mock
import
Mock
from
json
import
dumps
,
loads
from
json
import
loads
from
student.models
import
Registration
from
django.contrib.auth.models
import
User
from
cms.djangoapps.contentstore.utils
import
get_modulestore
...
...
@@ -23,13 +21,12 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.store_utilities
import
clone_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.templates
import
update_templates
from
xmodule.modulestore.xml_exporter
import
export_to_xml
from
xmodule.modulestore.xml_importer
import
import_from_xml
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.templates
import
update_templates
from
xmodule.capa_module
import
CapaDescriptor
from
xmodule.course_module
import
CourseDescriptor
...
...
@@ -65,7 +62,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
client
=
Client
()
self
.
client
.
login
(
username
=
uname
,
password
=
password
)
def
check_edit_unit
(
self
,
test_course_name
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
test_course_name
])
...
...
@@ -84,8 +80,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
def
test_static_tab_reordering
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
course
=
m
s
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
m
odule_store
=
modulestore
(
'direct'
)
course
=
m
odule_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
# reverse the ordering
reverse_tabs
=
[]
...
...
@@ -93,9 +89,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
if
tab
[
'type'
]
==
'static_tab'
:
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
course_tabs
=
[]
...
...
@@ -105,28 +101,60 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
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
):
'''
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
'''
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
effort
=
m
s
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'about'
,
'effort'
,
None
]))
m
odule_store
=
modulestore
(
'direct'
)
effort
=
m
odule_store
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'about'
,
'effort'
,
None
]))
self
.
assertEqual
(
effort
.
data
,
'6 hours'
)
# 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'
)
def
test_remove_hide_progress_tab
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
m
odule_store
=
modulestore
(
'direct'
)
c
ontent_store
=
contentstore
()
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
)
def
test_clone_course
(
self
):
...
...
@@ -145,19 +173,19 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
data
=
parse_json
(
resp
)
self
.
assertEqual
(
data
[
'id'
],
'i4x://MITx/999/course/Robot_Super_Course'
)
m
s
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
m
odule_store
=
modulestore
(
'direct'
)
c
ontent_store
=
contentstore
()
source_location
=
CourseDescriptor
.
id_to_location
(
'edX/full/6.002_Spring_2012'
)
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
# 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
)
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
)
for
descriptor
in
items
:
new_loc
=
descriptor
.
location
.
_replace
(
org
=
'MITx'
,
course
=
'999'
)
...
...
@@ -168,14 +196,14 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
def
test_delete_course
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
m
odule_store
=
modulestore
(
'direct'
)
c
ontent_store
=
contentstore
()
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
)
def
verify_content_existence
(
self
,
modulestore
,
root_dir
,
location
,
dirname
,
category_name
,
filename_suffix
=
''
):
...
...
@@ -190,10 +218,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertTrue
(
fs
.
exists
(
item
.
location
.
name
+
filename_suffix
))
def
test_export_course
(
self
):
m
s
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
m
odule_store
=
modulestore
(
'direct'
)
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'
)
root_dir
=
path
(
mkdtemp_clean
())
...
...
@@ -201,24 +229,24 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
print
'Exporting to tempdir = {0}'
.
format
(
root_dir
)
# 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
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
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
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
fs
=
OSFS
(
root_dir
/
'test_export/policies/6.002_Spring_2012'
)
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
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
())
self
.
assertEqual
(
on_disk
,
course
.
grading_policy
)
...
...
@@ -226,18 +254,18 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertTrue
(
fs
.
exists
(
'policy.json'
))
# 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
())
self
.
assertIn
(
'course/6.002_Spring_2012'
,
on_disk
)
self
.
assertEqual
(
on_disk
[
'course/6.002_Spring_2012'
],
own_metadata
(
course
))
# remove old course
delete_course
(
m
s
,
cs
,
location
)
delete_course
(
m
odule_store
,
content_store
,
location
)
# 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
)
for
descriptor
in
items
:
print
"Checking {0}...."
.
format
(
descriptor
.
location
.
url
())
...
...
@@ -247,11 +275,11 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
shutil
.
rmtree
(
root_dir
)
def
test_course_handouts_rewrites
(
self
):
m
s
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
m
odule_store
=
modulestore
(
'direct'
)
c
ontent_store
=
contentstore
()
# 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'
])
...
...
@@ -266,33 +294,33 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertContains
(
resp
,
'/c4x/edX/full/asset/handouts_schematic_tutorial.pdf'
)
def
test_export_course_with_unknown_metadata
(
self
):
m
s
=
modulestore
(
'direct'
)
c
s
=
contentstore
()
m
odule_store
=
modulestore
(
'direct'
)
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'
)
root_dir
=
path
(
mkdtemp_clean
())
course
=
m
s
.
get_item
(
location
)
course
=
m
odule_store
.
get_item
(
location
)
metadata
=
own_metadata
(
course
)
# add a bool piece of unknown metadata so we can verify we don't throw an exception
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
)
# export out to a tempdir
bE
xported
=
False
e
xported
=
False
try
:
export_to_xml
(
m
s
,
cs
,
location
,
root_dir
,
'test_export'
)
bE
xported
=
True
export_to_xml
(
m
odule_store
,
content_store
,
location
,
root_dir
,
'test_export'
)
e
xported
=
True
except
Exception
:
pass
self
.
assertTrue
(
bE
xported
)
self
.
assertTrue
(
e
xported
)
class
ContentStoreTest
(
ModuleStoreTestCase
):
"""
...
...
@@ -431,7 +459,7 @@ class ContentStoreTest(ModuleStoreTestCase):
def
test_capa_module
(
self
):
"""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
=
{
'parent_location'
:
'i4x://MITx/999/course/Robot_Super_Course'
,
...
...
@@ -452,10 +480,10 @@ class ContentStoreTest(ModuleStoreTestCase):
def
test_import_metadata_with_attempts_empty_string
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'simple'
])
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
did_load_item
=
False
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
except
ItemNotFoundError
:
pass
...
...
@@ -466,10 +494,10 @@ class ContentStoreTest(ModuleStoreTestCase):
def
test_metadata_inheritance
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
m
s
=
modulestore
(
'direct'
)
course
=
m
s
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
m
odule_store
=
modulestore
(
'direct'
)
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
for
vertical
in
verticals
:
...
...
@@ -481,13 +509,13 @@ class ContentStoreTest(ModuleStoreTestCase):
source_template_location
=
Location
(
'i4x'
,
'edx'
,
'templates'
,
'html'
,
'Blank_HTML_Page'
)
# 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
]
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
m
s
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
new_module
=
m
s
.
get_item
(
new_component_location
)
m
odule_store
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
new_module
=
m
odule_store
.
get_item
(
new_component_location
)
# check for grace period definition which should be defined at the course level
self
.
assertEqual
(
parent
.
lms
.
graceperiod
,
new_module
.
lms
.
graceperiod
)
...
...
@@ -498,11 +526,11 @@ class ContentStoreTest(ModuleStoreTestCase):
# now let's define an override at the leaf node level
#
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
m
s
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
new_module
=
m
s
.
get_item
(
new_component_location
)
m
odule_store
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
new_module
=
m
odule_store
.
get_item
(
new_component_location
)
self
.
assertEqual
(
timedelta
(
1
),
new_module
.
lms
.
graceperiod
)
...
...
@@ -510,15 +538,15 @@ class ContentStoreTest(ModuleStoreTestCase):
class
TemplateTestCase
(
ModuleStoreTestCase
):
def
test_template_cleanup
(
self
):
m
s
=
modulestore
(
'direct'
)
m
odule_store
=
modulestore
(
'direct'
)
# insert a bogus template in the store
bogus_template_location
=
Location
(
'i4x'
,
'edx'
,
'templates'
,
'html'
,
'bogus'
)
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
)
# now run cleanup
...
...
@@ -527,10 +555,8 @@ class TemplateTestCase(ModuleStoreTestCase):
# now try to find dangling template, it should not be in DB any longer
asserted
=
False
try
:
verify_create
=
m
s
.
get_item
(
bogus_template_location
)
verify_create
=
m
odule_store
.
get_item
(
bogus_template_location
)
except
ItemNotFoundError
:
asserted
=
True
self
.
assertTrue
(
asserted
)
cms/djangoapps/contentstore/views.py
View file @
bdf1b1b0
...
...
@@ -90,12 +90,14 @@ def signup(request):
csrf_token
=
csrf
(
request
)[
'csrf_token'
]
return
render_to_response
(
'signup.html'
,
{
'csrf'
:
csrf_token
})
def
old_login_redirect
(
request
):
'''
Redirect to the active login url.
'''
return
redirect
(
'login'
,
permanent
=
True
)
@ssl_login_shortcut
@ensure_csrf_cookie
def
login_page
(
request
):
...
...
@@ -108,6 +110,7 @@ def login_page(request):
'forgot_password_link'
:
"//{base}/#forgot-password-modal"
.
format
(
base
=
settings
.
LMS_BASE
),
})
def
howitworks
(
request
):
if
request
.
user
.
is_authenticated
():
return
index
(
request
)
...
...
@@ -116,6 +119,7 @@ def howitworks(request):
# ==== Views for any logged-in user ==================================
@login_required
@ensure_csrf_cookie
def
index
(
request
):
...
...
@@ -149,6 +153,7 @@ def index(request):
# ==== Views with per-item permissions================================
def
has_access
(
user
,
location
,
role
=
STAFF_ROLE_NAME
):
'''
Return True if user allowed to access this piece of data
...
...
@@ -396,6 +401,7 @@ def preview_component(request, location):
'editor'
:
wrap_xmodule
(
component
.
get_html
,
component
,
'xmodule_edit.html'
)(),
})
@expect_json
@login_required
@ensure_csrf_cookie
...
...
@@ -636,6 +642,17 @@ def delete_item(request):
if
item
.
location
.
revision
is
None
and
item
.
location
.
category
==
'vertical'
and
delete_all_versions
:
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
()
...
...
@@ -709,6 +726,7 @@ def create_draft(request):
return
HttpResponse
()
@login_required
@expect_json
def
publish_draft
(
request
):
...
...
@@ -738,6 +756,7 @@ def unpublish_unit(request):
return
HttpResponse
()
@login_required
@expect_json
def
clone_item
(
request
):
...
...
@@ -765,8 +784,7 @@ def clone_item(request):
return
HttpResponse
(
json
.
dumps
({
'id'
:
dest_location
.
url
()}))
#@login_required
#@ensure_csrf_cookie
def
upload_asset
(
request
,
org
,
course
,
coursename
):
'''
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):
response
[
'asset_url'
]
=
StaticContent
.
get_url_path_from_location
(
content
.
location
)
return
response
'''
This view will return all CMS users who are editors for the specified course
'''
...
...
@@ -860,6 +879,7 @@ def create_json_response(errmsg = None):
return
resp
'''
This POST-back view will add a user - specified by email - to the list of editors for
the specified course
...
...
@@ -892,6 +912,7 @@ def add_user(request, location):
return
create_json_response
()
'''
This POST-back view will remove a user - specified by email - from the list of editors for
the specified course
...
...
@@ -923,6 +944,7 @@ def remove_user(request, location):
def
landing
(
request
,
org
,
course
,
coursename
):
return
render_to_response
(
'temp-course-landing.html'
,
{})
@login_required
@ensure_csrf_cookie
def
static_pages
(
request
,
org
,
course
,
coursename
):
...
...
@@ -1026,6 +1048,7 @@ def edit_tabs(request, org, course, coursename):
'components'
:
components
})
def
not_found
(
request
):
return
render_to_response
(
'error.html'
,
{
'error'
:
'404'
})
...
...
@@ -1061,6 +1084,7 @@ def course_info(request, org, course, name, provided_id=None):
'handouts_location'
:
Location
([
'i4x'
,
org
,
course
,
'course_info'
,
'handouts'
])
.
url
()
})
@expect_json
@login_required
@ensure_csrf_cookie
...
...
@@ -1158,6 +1182,7 @@ def get_course_settings(request, org, course, name):
"section"
:
"details"
})
})
@login_required
@ensure_csrf_cookie
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
)
})
@login_required
@ensure_csrf_cookie
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
)),
})
@expect_json
@login_required
@ensure_csrf_cookie
...
...
@@ -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
),
mimetype
=
"application/json"
)
@expect_json
@login_required
@ensure_csrf_cookie
...
...
@@ -1359,6 +1387,7 @@ def asset_index(request, org, course, name):
def
edge
(
request
):
return
render_to_response
(
'university_profiles/edge.html'
,
{})
@login_required
@expect_json
def
create_new_course
(
request
):
...
...
@@ -1411,6 +1440,7 @@ def create_new_course(request):
return
HttpResponse
(
json
.
dumps
({
'id'
:
new_course
.
location
.
url
()}))
def
initialize_course_tabs
(
course
):
# 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
...
...
@@ -1428,6 +1458,7 @@ def initialize_course_tabs(course):
modulestore
(
'direct'
)
.
update_metadata
(
course
.
location
.
url
(),
own_metadata
(
course
))
@ensure_csrf_cookie
@login_required
def
import_course
(
request
,
org
,
course
,
name
):
...
...
@@ -1505,6 +1536,7 @@ def import_course(request, org, course, name):
course_module
.
location
.
name
])
})
@ensure_csrf_cookie
@login_required
def
generate_export_course
(
request
,
org
,
course
,
name
):
...
...
@@ -1556,6 +1588,7 @@ def export_course(request, org, course, name):
'successful_import_redirect_url'
:
''
})
def
event
(
request
):
'''
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):
# 0.33k or -17
number
=
(
Optional
(
minus
|
plus
)
+
inner_number
+
Optional
(
CaselessLiteral
(
"E"
)
+
Optional
(
"-"
)
+
number_part
)
+
Optional
(
CaselessLiteral
(
"E"
)
+
Optional
(
(
plus
|
minus
)
)
+
number_part
)
+
Optional
(
number_suffix
))
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):
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
):
return
{
'input_type'
:
self
.
html_input_type
,
'choices'
:
self
.
choices
,
...
...
common/lib/capa/capa/templates/choicegroup.html
View file @
bdf1b1b0
<form
class=
"choicegroup capa_inputtype"
id=
"inputtype_${id}"
>
<div
class=
"indicator_container"
>
% 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>
% elif status == 'correct':
<span
class=
"correct"
id=
"status_${id}"
></span>
...
...
@@ -26,7 +26,7 @@
else:
correctness =
None
%
>
% if correctness:
% if correctness
and not show_correctness=='never'
:
class="choicegroup_${correctness}"
% endif
% endif
...
...
@@ -41,4 +41,7 @@
<span
id=
"answer_${id}"
></span>
</fieldset>
% if show_correctness == "never" and (value or status not in ['unsubmitted']):
<div
class=
"capa_alert"
>
${submitted_message}
</div>
%endif
</form>
common/lib/capa/capa/tests/test_inputtypes.py
View file @
bdf1b1b0
...
...
@@ -102,6 +102,8 @@ class ChoiceGroupTest(unittest.TestCase):
'choices'
:
[(
'foil1'
,
'<text>This is foil One.</text>'
),
(
'foil2'
,
'<text>This is foil Two.</text>'
),
(
'foil3'
,
'This is foil Three.'
),
],
'show_correctness'
:
'always'
,
'submitted_message'
:
'Answer received.'
,
'name_array_suffix'
:
expected_suffix
,
# what is this for??
}
...
...
common/lib/capa/capa/tests/test_responsetypes.py
View file @
bdf1b1b0
...
...
@@ -642,6 +642,15 @@ class NumericalResponseTest(ResponseTest):
incorrect_responses
=
[
""
,
"2.11"
,
"1.89"
,
"0"
]
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
):
from
response_xml_factory
import
CustomResponseXMLFactory
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
bdf1b1b0
...
...
@@ -121,7 +121,7 @@ class Textbook(object):
return
table_of_contents
class
TextbookList
(
ModelType
):
class
TextbookList
(
List
):
def
from_json
(
self
,
values
):
textbooks
=
[]
for
title
,
book_url
in
values
:
...
...
@@ -150,19 +150,19 @@ class TextbookList(ModelType):
class
CourseDescriptor
(
SequenceDescriptor
):
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
)
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
)
start
=
Date
(
help
=
"Start time when this module is visible"
,
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
)
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
)
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
)
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
(
help
=
"Map of topics names to ids"
,
scope
=
Scope
.
settings
,
...
...
@@ -174,11 +174,12 @@ class CourseDescriptor(SequenceDescriptor):
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
)
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
)
remote_gradebook
=
Object
(
scope
=
Scope
.
settings
,
default
=
{})
pdf_textbooks
=
List
(
help
=
"List of dictionaries containing pdf_textbook configuration"
,
scope
=
Scope
.
settings
)
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_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
info_sidebar_name
=
String
(
scope
=
Scope
.
settings
,
default
=
'Course Handouts'
)
...
...
@@ -256,27 +257,27 @@ class CourseDescriptor(SequenceDescriptor):
"min_count"
:
12
,
"drop_count"
:
2
,
"short_label"
:
"HW"
,
"weight"
:
15
"weight"
:
0.
15
},
{
"type"
:
"Lab"
,
"min_count"
:
12
,
"drop_count"
:
2
,
"weight"
:
15
"weight"
:
0.
15
},
{
"type"
:
"Midterm Exam"
,
"short_label"
:
"Midterm"
,
"min_count"
:
1
,
"drop_count"
:
0
,
"weight"
:
30
"weight"
:
0.3
},
{
"type"
:
"Final Exam"
,
"short_label"
:
"Final"
,
"min_count"
:
1
,
"drop_count"
:
0
,
"weight"
:
40
"weight"
:
0.4
}
],
"GRADE_CUTOFFS"
:
{
...
...
common/lib/xmodule/xmodule/foldit_module.py
View file @
bdf1b1b0
...
...
@@ -85,7 +85,10 @@ class FolditModule(XModule):
"""
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
):
"""
...
...
common/lib/xmodule/xmodule/modulestore/tests/factories.py
View file @
bdf1b1b0
common/lib/xmodule/xmodule/modulestore/tests/test_location.py
View file @
bdf1b1b0
common/lib/xmodule/xmodule/modulestore/tests/test_modulestore.py
View file @
bdf1b1b0
jenkins/test.sh
View file @
bdf1b1b0
...
...
@@ -38,6 +38,9 @@ pip install -q -r test-requirements.txt
yes w | pip install
-q
-r
requirements.txt
rake clobber
rake pep8
rake pylint
TESTS_FAILED
=
0
rake test_cms[false]
||
TESTS_FAILED
=
1
rake test_lms[false]
||
TESTS_FAILED
=
1
...
...
lms/djangoapps/courseware/tabs.py
View file @
bdf1b1b0
...
...
@@ -131,6 +131,17 @@ def _pdf_textbooks(tab, user, course, active_page):
for
index
,
textbook
in
enumerate
(
course
.
pdf_textbooks
)]
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
):
if
has_access
(
user
,
course
,
'staff'
):
link
=
reverse
(
'staff_grading'
,
args
=
[
course
.
id
])
...
...
@@ -210,6 +221,7 @@ VALID_TAB_TYPES = {
'external_link'
:
TabImpl
(
key_checker
([
'name'
,
'link'
]),
_external_link
),
'textbooks'
:
TabImpl
(
null_validator
,
_textbooks
),
'pdf_textbooks'
:
TabImpl
(
null_validator
,
_pdf_textbooks
),
'html_textbooks'
:
TabImpl
(
null_validator
,
_html_textbooks
),
'progress'
:
TabImpl
(
need_name
,
_progress
),
'static_tab'
:
TabImpl
(
key_checker
([
'name'
,
'url_slug'
]),
_static_tab
),
'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
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.search
import
path_to_location
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -170,7 +169,6 @@ def initialize_discussion_info(course):
# 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
)
path_to_locations
=
{}
for
module
in
all_modules
:
skip_module
=
False
for
key
in
(
'discussion_id'
,
'discussion_category'
,
'discussion_target'
):
...
...
@@ -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
))
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
:
continue
...
...
@@ -248,7 +238,6 @@ def initialize_discussion_info(course):
_DISCUSSIONINFO
[
course
.
id
][
'id_map'
]
=
discussion_id_map
_DISCUSSIONINFO
[
course
.
id
][
'category_map'
]
=
category_map
_DISCUSSIONINFO
[
course
.
id
][
'timestamp'
]
=
datetime
.
now
()
_DISCUSSIONINFO
[
course
.
id
][
'path_to_location'
]
=
path_to_locations
class
JsonResponse
(
HttpResponse
):
...
...
@@ -405,21 +394,8 @@ def get_courseware_context(content, course):
location
=
id_map
[
id
][
"location"
]
.
url
()
title
=
id_map
[
id
][
"title"
]
# cdodge: did we pre-compute, if so, then let's use that rather than recomputing
if
'path_to_location'
in
_DISCUSSIONINFO
[
course
.
id
]
and
location
in
_DISCUSSIONINFO
[
course
.
id
][
'path_to_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
})
url
=
reverse
(
'jump_to'
,
kwargs
=
{
"course_id"
:
course
.
location
.
course_id
,
"location"
:
location
})
content_info
=
{
"courseware_url"
:
url
,
"courseware_title"
:
title
}
return
content_info
...
...
lms/djangoapps/foldit/models.py
View file @
bdf1b1b0
...
...
@@ -59,7 +59,7 @@ class Score(models.Model):
scores
=
Score
.
objects
\
.
filter
(
puzzle_id__in
=
puzzles
)
\
.
annotate
(
total_score
=
models
.
Sum
(
'best_score'
))
\
.
order_by
(
'
-
total_score'
)[:
n
]
.
order_by
(
'total_score'
)[:
n
]
num
=
len
(
puzzles
)
return
[{
'username'
:
s
.
user
.
username
,
...
...
lms/djangoapps/foldit/tests.py
View file @
bdf1b1b0
...
...
@@ -143,11 +143,12 @@ class FolditTestCase(TestCase):
def
test_SetPlayerPuzzleScores_manyplayers
(
self
):
"""
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'
]
player1_score
=
0.0
7
player2_score
=
0.0
8
player1_score
=
0.0
8
player2_score
=
0.0
2
response1
=
self
.
make_puzzle_score_request
(
puzzle_id
,
player1_score
,
self
.
user
)
...
...
@@ -164,8 +165,12 @@ class FolditTestCase(TestCase):
self
.
assertEqual
(
len
(
top_10
),
2
)
# Top score should be player2_score. Second should be player1_score
self
.
assertEqual
(
top_10
[
0
][
'score'
],
Score
.
display_score
(
player2_score
))
self
.
assertEqual
(
top_10
[
1
][
'score'
],
Score
.
display_score
(
player1_score
))
self
.
assertAlmostEqual
(
top_10
[
0
][
'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
self
.
assertEqual
(
top_10
[
0
][
'username'
],
self
.
user2
.
username
)
...
...
lms/djangoapps/staticbook/views.py
View file @
bdf1b1b0
from
lxml
import
etree
# from django.conf import settings
from
django.contrib.auth.decorators
import
login_required
from
django.http
import
Http404
from
mitxmako.shortcuts
import
render_to_response
from
courseware.access
import
has_access
...
...
@@ -15,6 +15,8 @@ def index(request, course_id, book_index, page=None):
staff_access
=
has_access
(
request
.
user
,
course
,
'staff'
)
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
]
table_of_contents
=
textbook
.
table_of_contents
...
...
@@ -40,6 +42,8 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None):
staff_access
=
has_access
(
request
.
user
,
course
,
'staff'
)
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
]
def
remap_static_url
(
original_url
,
course
):
...
...
@@ -67,3 +71,39 @@ def pdf_index(request, course_id, book_index, chapter=None, page=None):
'chapter'
:
chapter
,
'page'
:
page
,
'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 {
img
{
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:
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/pdfbook/(?P<book_index>[^/]*)/chapter/(?P<chapter>[^/]*)/(?P<page>[^/]*)$'
,
'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/?$'
,
'courseware.views.index'
,
name
=
"courseware"
),
url
(
r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/$'
,
...
...
local-requirements.txt
View file @
bdf1b1b0
...
...
@@ -6,4 +6,4 @@
# XBlock:
# Might change frequently, so put it in local-requirements.txt,
# 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