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
d8d4111a
Commit
d8d4111a
authored
May 08, 2013
by
Chris Dodge
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of github.com:edx/mitx into feature/cdodge/autoprovision-forums-master
parents
3c747f0e
d522b53f
Hide whitespace changes
Inline
Side-by-side
Showing
36 changed files
with
507 additions
and
149 deletions
+507
-149
cms/djangoapps/contentstore/module_info_model.py
+1
-5
cms/djangoapps/contentstore/views.py
+1
-5
cms/djangoapps/models/settings/course_metadata.py
+8
-7
cms/static/coffee/spec/views/module_edit_spec.coffee
+11
-0
cms/static/coffee/src/views/module_edit.coffee
+10
-1
cms/templates/widgets/metadata-edit.html
+29
-5
cms/templates/widgets/source-edit.html
+1
-1
common/djangoapps/util/memcache.py
+34
-7
common/djangoapps/util/tests/__init__.py
+1
-0
common/djangoapps/util/tests/test_memcache.py
+124
-0
common/djangoapps/util/tests/test_zendesk.py
+1
-1
common/lib/xmodule/xmodule/capa_module.py
+9
-11
common/lib/xmodule/xmodule/combined_open_ended_module.py
+1
-3
common/lib/xmodule/xmodule/discussion_module.py
+7
-0
common/lib/xmodule/xmodule/html_module.py
+1
-10
common/lib/xmodule/xmodule/js/src/.gitignore
+4
-1
common/lib/xmodule/xmodule/js/src/capa/.gitignore
+2
-0
common/lib/xmodule/xmodule/js/src/graphical_slider_tool/.gitignore
+1
-0
common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee
+13
-10
common/lib/xmodule/xmodule/js/src/poll/.gitignore
+1
-0
common/lib/xmodule/xmodule/js/src/sequence/display/.gitignore
+1
-0
common/lib/xmodule/xmodule/js/src/videoalpha/display/.gitignore
+1
-0
common/lib/xmodule/xmodule/mako_module.py
+1
-12
common/lib/xmodule/xmodule/peer_grading_module.py
+7
-7
common/lib/xmodule/xmodule/tests/test_xml_module.py
+79
-0
common/lib/xmodule/xmodule/x_module.py
+43
-7
common/lib/xmodule/xmodule/xml_module.py
+8
-1
lms/djangoapps/courseware/tests/tests.py
+4
-4
lms/djangoapps/open_ended_grading/open_ended_notifications.py
+32
-8
lms/lib/comment_client/utils.py
+4
-0
lms/static/sass/shared/_modal.scss
+1
-1
lms/templates/courseware/courseware.html
+3
-3
lms/templates/forgot_password_modal.html
+9
-4
lms/templates/login_modal.html
+16
-8
lms/templates/seq_module.html
+2
-1
lms/templates/signup_modal.html
+36
-26
No files found.
cms/djangoapps/contentstore/module_info_model.py
View file @
d8d4111a
...
@@ -75,11 +75,7 @@ def set_module_info(store, location, post_data):
...
@@ -75,11 +75,7 @@ def set_module_info(store, location, post_data):
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
for
metadata_key
,
value
in
posted_metadata
.
items
():
for
metadata_key
,
value
in
posted_metadata
.
items
():
# let's strip out any metadata fields from the postback which have been identified as system metadata
if
posted_metadata
[
metadata_key
]
is
None
:
# and therefore should not be user-editable, so we should accept them back from the client
if
metadata_key
in
module
.
system_metadata_fields
:
del
posted_metadata
[
metadata_key
]
elif
posted_metadata
[
metadata_key
]
is
None
:
# remove both from passed in collection as well as the collection read in from the modulestore
# remove both from passed in collection as well as the collection read in from the modulestore
if
metadata_key
in
module
.
_model_data
:
if
metadata_key
in
module
.
_model_data
:
del
module
.
_model_data
[
metadata_key
]
del
module
.
_model_data
[
metadata_key
]
...
...
cms/djangoapps/contentstore/views.py
View file @
d8d4111a
...
@@ -678,11 +678,7 @@ def save_item(request):
...
@@ -678,11 +678,7 @@ def save_item(request):
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
for
metadata_key
,
value
in
posted_metadata
.
items
():
for
metadata_key
,
value
in
posted_metadata
.
items
():
# let's strip out any metadata fields from the postback which have been identified as system metadata
if
posted_metadata
[
metadata_key
]
is
None
:
# and therefore should not be user-editable, so we should accept them back from the client
if
metadata_key
in
existing_item
.
system_metadata_fields
:
del
posted_metadata
[
metadata_key
]
elif
posted_metadata
[
metadata_key
]
is
None
:
# remove both from passed in collection as well as the collection read in from the modulestore
# remove both from passed in collection as well as the collection read in from the modulestore
if
metadata_key
in
existing_item
.
_model_data
:
if
metadata_key
in
existing_item
.
_model_data
:
del
existing_item
.
_model_data
[
metadata_key
]
del
existing_item
.
_model_data
[
metadata_key
]
...
...
cms/djangoapps/models/settings/course_metadata.py
View file @
d8d4111a
...
@@ -14,13 +14,14 @@ class CourseMetadata(object):
...
@@ -14,13 +14,14 @@ class CourseMetadata(object):
The objects have no predefined attrs but instead are obj encodings of the
The objects have no predefined attrs but instead are obj encodings of the
editable metadata.
editable metadata.
'''
'''
FILTERED_LIST
=
XModuleDescriptor
.
system_metadata_fields
+
[
'start'
,
FILTERED_LIST
=
[
'xml_attributes'
,
'end'
,
'start'
,
'enrollment_start'
,
'end'
,
'enrollment_end'
,
'enrollment_start'
,
'tabs'
,
'enrollment_end'
,
'graceperiod'
,
'tabs'
,
'checklists'
]
'graceperiod'
,
'checklists'
]
@classmethod
@classmethod
def
fetch
(
cls
,
course_location
):
def
fetch
(
cls
,
course_location
):
...
...
cms/static/coffee/spec/views/module_edit_spec.coffee
View file @
d8d4111a
...
@@ -72,3 +72,14 @@ describe "CMS.Views.ModuleEdit", ->
...
@@ -72,3 +72,14 @@ describe "CMS.Views.ModuleEdit", ->
it
"loads the .xmodule-display inside the module editor"
,
->
it
"loads the .xmodule-display inside the module editor"
,
->
expect
(
XModule
.
loadModule
).
toHaveBeenCalled
()
expect
(
XModule
.
loadModule
).
toHaveBeenCalled
()
expect
(
XModule
.
loadModule
.
mostRecentCall
.
args
[
0
]).
toBe
(
$
(
'.xmodule_display'
))
expect
(
XModule
.
loadModule
.
mostRecentCall
.
args
[
0
]).
toBe
(
$
(
'.xmodule_display'
))
describe
"changedMetadata"
,
->
it
"returns empty if no metadata loaded"
,
->
expect
(
@
moduleEdit
.
changedMetadata
()).
toEqual
({})
it
"returns only changed values"
,
->
@
moduleEdit
.
originalMetadata
=
{
'foo'
,
'bar'
}
spyOn
(
@
moduleEdit
,
'metadata'
).
andReturn
({
'a'
:
''
,
'b'
:
'before'
,
'c'
:
''
})
@
moduleEdit
.
loadEdit
()
@
moduleEdit
.
metadata
.
andReturn
({
'a'
:
''
,
'b'
:
'after'
,
'd'
:
'only_after'
})
expect
(
@
moduleEdit
.
changedMetadata
()).
toEqual
({
'b'
:
'after'
,
'd'
:
'only_after'
})
cms/static/coffee/src/views/module_edit.coffee
View file @
d8d4111a
...
@@ -20,6 +20,7 @@ class CMS.Views.ModuleEdit extends Backbone.View
...
@@ -20,6 +20,7 @@ class CMS.Views.ModuleEdit extends Backbone.View
loadEdit
:
->
loadEdit
:
->
if
not
@
module
if
not
@
module
@
module
=
XModule
.
loadModule
(
@
$el
.
find
(
'.xmodule_edit'
))
@
module
=
XModule
.
loadModule
(
@
$el
.
find
(
'.xmodule_edit'
))
@
originalMetadata
=
@
metadata
()
metadata
:
->
metadata
:
->
# cdodge: package up metadata which is separated into a number of input fields
# cdodge: package up metadata which is separated into a number of input fields
...
@@ -35,6 +36,14 @@ class CMS.Views.ModuleEdit extends Backbone.View
...
@@ -35,6 +36,14 @@ class CMS.Views.ModuleEdit extends Backbone.View
return
_metadata
return
_metadata
changedMetadata
:
->
currentMetadata
=
@
metadata
()
changedMetadata
=
{}
for
key
of
currentMetadata
if
currentMetadata
[
key
]
!=
@
originalMetadata
[
key
]
changedMetadata
[
key
]
=
currentMetadata
[
key
]
return
changedMetadata
cloneTemplate
:
(
parent
,
template
)
->
cloneTemplate
:
(
parent
,
template
)
->
$
.
post
(
"/clone_item"
,
{
$
.
post
(
"/clone_item"
,
{
parent_location
:
parent
parent_location
:
parent
...
@@ -60,7 +69,7 @@ class CMS.Views.ModuleEdit extends Backbone.View
...
@@ -60,7 +69,7 @@ class CMS.Views.ModuleEdit extends Backbone.View
course
:
course_location_analytics
course
:
course_location_analytics
id
:
_this
.
model
.
id
id
:
_this
.
model
.
id
data
.
metadata
=
_
.
extend
(
data
.
metadata
||
{},
@
m
etadata
())
data
.
metadata
=
_
.
extend
(
data
.
metadata
||
{},
@
changedM
etadata
())
@
hideModal
()
@
hideModal
()
@
model
.
save
(
data
).
done
(
=>
@
model
.
save
(
data
).
done
(
=>
# # showToastMessage("Your changes have been saved.", null, 3)
# # showToastMessage("Your changes have been saved.", null, 3)
...
...
cms/templates/widgets/metadata-edit.html
View file @
d8d4111a
<
%
<
%
import
hashlib
import
hashlib
from
xmodule
.
fields
import
StringyInteger
,
StringyFloat
hlskey =
hashlib.md5(module.location.url()).hexdigest()
hlskey =
hashlib.md5(module.location.url()).hexdigest()
%
>
%
>
<section
class=
"metadata_edit"
>
<section
class=
"metadata_edit"
>
...
@@ -7,17 +8,40 @@
...
@@ -7,17 +8,40 @@
% for field_name, field_value in editable_metadata_fields.items():
% for field_name, field_value in editable_metadata_fields.items():
<li>
<li>
% if field_name == 'source_code':
% if field_name == 'source_code':
<a
href=
"#hls-modal-${hlskey}"
style=
"color:yellow;"
id=
"hls-trig-${hlskey}"
>
Edit High Level Source
</a>
% if field_value['is_default'] is False:
<a
href=
"#hls-modal-${hlskey}"
style=
"color:yellow;"
id=
"hls-trig-${hlskey}"
>
Edit High Level Source
</a>
% endif
% else:
% else:
<label>
${field_name}:
</label>
<label>
${field_value['field'].display_name}:
</label>
<input
type=
'text'
data-metadata-name=
'${field_name}'
value=
'${field_value}'
size=
'60'
/>
<input
type=
'text'
data-metadata-name=
'${field_value["field"].display_name}'
##
This
is
a
hack
to
keep
current
behavior
for
weight
and
attempts
(
empty
will
parse
OK
as
unset
).
##
This
hack
will
go
away
with
our
custom
editors
.
%
if
field_value
["
value
"]
==
None
and
(
isinstance
(
field_value
["
field
"],
StringyFloat
)
or
isinstance
(
field_value
["
field
"],
StringyInteger
))
:
value =
''
%
else:
value=
'${field_value["field"].to_json(field_value["value"])}'
%
endif
size=
'60'
/>
## Change to True to see all the information being passed through.
% if False:
<label>
Help: ${field_value['field'].help}
</label>
<label>
Type: ${type(field_value['field']).__name__}
</label>
<label>
Inherited: ${field_value['is_inherited']}
</label>
<label>
Default: ${field_value['is_default']}
</label>
% if field_value['field'].values:
<label>
Possible values:
</label>
% for value in field_value['field'].values:
<label>
${value}
</label>
% endfor
% endif
% endif
% endif
% endif
</li>
</li>
% endfor
% endfor
</ul>
</ul>
% if 'source_code' in editable_metadata_fields:
% if 'source_code' in editable_metadata_fields
and not editable_metadata_fields['source_code']['is_default']
:
<
%
include
file=
"source-edit.html"
/>
<
%
include
file=
"source-edit.html"
/>
% endif
% endif
</section>
</section>
cms/templates/widgets/source-edit.html
View file @
d8d4111a
...
@@ -12,7 +12,7 @@
...
@@ -12,7 +12,7 @@
<form
id=
"hls-form"
enctype=
"multipart/form-data"
>
<form
id=
"hls-form"
enctype=
"multipart/form-data"
>
<section
class=
"source-edit"
>
<section
class=
"source-edit"
>
<textarea
name=
""
data-metadata-name=
"source_code"
class=
"source-edit-box hls-data"
rows=
"8"
cols=
"40"
>
${editable_metadata_fields['source_code']|h}
</textarea>
<textarea
name=
""
data-metadata-name=
"source_code"
class=
"source-edit-box hls-data"
rows=
"8"
cols=
"40"
>
${editable_metadata_fields['source_code']
['value']
|h}
</textarea>
</section>
</section>
<div
class=
"submit"
>
<div
class=
"submit"
>
<button
type=
"reset"
class=
"hls-compile"
>
Save
&
Compile to edX XML
</button>
<button
type=
"reset"
class=
"hls-compile"
>
Save
&
Compile to edX XML
</button>
...
...
common/djangoapps/util/memcache.py
View file @
d8d4111a
...
@@ -8,15 +8,42 @@ import urllib
...
@@ -8,15 +8,42 @@ import urllib
def
fasthash
(
string
):
def
fasthash
(
string
):
m
=
hashlib
.
new
(
"md4"
)
"""
m
.
update
(
string
)
Hashes `string` into a string representation of a 128-bit digest.
return
m
.
hexdigest
()
"""
md4
=
hashlib
.
new
(
"md4"
)
md4
.
update
(
string
)
return
md4
.
hexdigest
()
def
cleaned_string
(
val
):
"""
Converts `val` to unicode and URL-encodes special characters
(including quotes and spaces)
"""
return
urllib
.
quote_plus
(
smart_str
(
val
))
def
safe_key
(
key
,
key_prefix
,
version
):
def
safe_key
(
key
,
key_prefix
,
version
):
safe_key
=
urllib
.
quote_plus
(
smart_str
(
key
))
"""
Given a `key`, `key_prefix`, and `version`,
return a key that is safe to use with memcache.
`key`, `key_prefix`, and `version` can be numbers, strings, or unicode.
"""
# Clean for whitespace and control characters, which
# cause memcache to raise an exception
key
=
cleaned_string
(
key
)
key_prefix
=
cleaned_string
(
key_prefix
)
version
=
cleaned_string
(
version
)
# Attempt to combine the prefix, version, and key
combined
=
":"
.
join
([
key_prefix
,
version
,
key
])
if
len
(
safe_key
)
>
250
:
# If the total length is too long for memcache, hash it
safe_key
=
fasthash
(
safe_key
)
if
len
(
combined
)
>
250
:
combined
=
fasthash
(
combined
)
return
":"
.
join
([
key_prefix
,
str
(
version
),
safe_key
])
# Return the result
return
combined
common/djangoapps/util/tests/__init__.py
0 → 100644
View file @
d8d4111a
common/djangoapps/util/tests/test_memcache.py
0 → 100644
View file @
d8d4111a
"""
Tests for memcache in util app
"""
from
django.test
import
TestCase
from
django.core.cache
import
get_cache
from
django.conf
import
settings
from
util.memcache
import
safe_key
class
MemcacheTest
(
TestCase
):
"""
Test memcache key cleanup
"""
# Test whitespace, control characters, and some non-ASCII UTF-16
UNICODE_CHAR_CODES
=
([
c
for
c
in
range
(
0
,
30
)]
+
[
127
]
+
[
129
,
500
,
2
**
8
-
1
,
2
**
8
+
1
,
2
**
16
-
1
])
def
setUp
(
self
):
self
.
cache
=
get_cache
(
'default'
)
def
test_safe_key
(
self
):
key
=
safe_key
(
'test'
,
'prefix'
,
'version'
)
self
.
assertEqual
(
key
,
'prefix:version:test'
)
def
test_numeric_inputs
(
self
):
# Numeric key
self
.
assertEqual
(
safe_key
(
1
,
'prefix'
,
'version'
),
'prefix:version:1'
)
# Numeric prefix
self
.
assertEqual
(
safe_key
(
'test'
,
5
,
'version'
),
'5:version:test'
)
# Numeric version
self
.
assertEqual
(
safe_key
(
'test'
,
'prefix'
,
5
),
'prefix:5:test'
)
def
test_safe_key_long
(
self
):
# Choose lengths close to memcached's cutoff (250)
for
length
in
[
248
,
249
,
250
,
251
,
252
]:
# Generate a key of that length
key
=
'a'
*
length
# Make the key safe
key
=
safe_key
(
key
,
''
,
''
)
# The key should now be valid
self
.
assertTrue
(
self
.
_is_valid_key
(
key
),
msg
=
"Failed for key length {0}"
.
format
(
length
))
def
test_long_key_prefix_version
(
self
):
# Long key
key
=
safe_key
(
'a'
*
300
,
'prefix'
,
'version'
)
self
.
assertTrue
(
self
.
_is_valid_key
(
key
))
# Long prefix
key
=
safe_key
(
'key'
,
'a'
*
300
,
'version'
)
self
.
assertTrue
(
self
.
_is_valid_key
(
key
))
# Long version
key
=
safe_key
(
'key'
,
'prefix'
,
'a'
*
300
)
self
.
assertTrue
(
self
.
_is_valid_key
(
key
))
def
test_safe_key_unicode
(
self
):
for
unicode_char
in
self
.
UNICODE_CHAR_CODES
:
# Generate a key with that character
key
=
unichr
(
unicode_char
)
# Make the key safe
key
=
safe_key
(
key
,
''
,
''
)
# The key should now be valid
self
.
assertTrue
(
self
.
_is_valid_key
(
key
),
msg
=
"Failed for unicode character {0}"
.
format
(
unicode_char
))
def
test_safe_key_prefix_unicode
(
self
):
for
unicode_char
in
self
.
UNICODE_CHAR_CODES
:
# Generate a prefix with that character
prefix
=
unichr
(
unicode_char
)
# Make the key safe
key
=
safe_key
(
'test'
,
prefix
,
''
)
# The key should now be valid
self
.
assertTrue
(
self
.
_is_valid_key
(
key
),
msg
=
"Failed for unicode character {0}"
.
format
(
unicode_char
))
def
test_safe_key_version_unicode
(
self
):
for
unicode_char
in
self
.
UNICODE_CHAR_CODES
:
# Generate a version with that character
version
=
unichr
(
unicode_char
)
# Make the key safe
key
=
safe_key
(
'test'
,
''
,
version
)
# The key should now be valid
self
.
assertTrue
(
self
.
_is_valid_key
(
key
),
msg
=
"Failed for unicode character {0}"
.
format
(
unicode_char
))
def
_is_valid_key
(
self
,
key
):
"""
Test that a key is memcache-compatible.
Based on Django's validator in core.cache.backends.base
"""
# Check the length
if
len
(
key
)
>
250
:
return
False
# Check that there are no spaces or control characters
for
char
in
key
:
if
ord
(
char
)
<
33
or
ord
(
char
)
==
127
:
return
False
return
True
common/djangoapps/util/tests.py
→
common/djangoapps/util/tests
/test_zendesk
.py
View file @
d8d4111a
"""Tests for the
util package
"""
"""Tests for the
Zendesk
"""
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth.models
import
AnonymousUser
from
django.contrib.auth.models
import
AnonymousUser
...
...
common/lib/xmodule/xmodule/capa_module.py
View file @
d8d4111a
...
@@ -65,7 +65,8 @@ class CapaFields(object):
...
@@ -65,7 +65,8 @@ class CapaFields(object):
max_attempts
=
StringyInteger
(
help
=
"Maximum number of attempts that a student is allowed"
,
scope
=
Scope
.
settings
)
max_attempts
=
StringyInteger
(
help
=
"Maximum number of attempts that a student is allowed"
,
scope
=
Scope
.
settings
)
due
=
Date
(
help
=
"Date that this problem is due by"
,
scope
=
Scope
.
settings
)
due
=
Date
(
help
=
"Date that this problem is due by"
,
scope
=
Scope
.
settings
)
graceperiod
=
Timedelta
(
help
=
"Amount of time after the due date that submissions will be accepted"
,
scope
=
Scope
.
settings
)
graceperiod
=
Timedelta
(
help
=
"Amount of time after the due date that submissions will be accepted"
,
scope
=
Scope
.
settings
)
showanswer
=
String
(
help
=
"When to show the problem answer to the student"
,
scope
=
Scope
.
settings
,
default
=
"closed"
)
showanswer
=
String
(
help
=
"When to show the problem answer to the student"
,
scope
=
Scope
.
settings
,
default
=
"closed"
,
values
=
[
"answered"
,
"always"
,
"attempted"
,
"closed"
,
"never"
])
force_save_button
=
Boolean
(
help
=
"Whether to force the save button to appear on the page"
,
scope
=
Scope
.
settings
,
default
=
False
)
force_save_button
=
Boolean
(
help
=
"Whether to force the save button to appear on the page"
,
scope
=
Scope
.
settings
,
default
=
False
)
rerandomize
=
Randomization
(
help
=
"When to rerandomize the problem"
,
default
=
"always"
,
scope
=
Scope
.
settings
)
rerandomize
=
Randomization
(
help
=
"When to rerandomize the problem"
,
default
=
"always"
,
scope
=
Scope
.
settings
)
data
=
String
(
help
=
"XML data for the problem"
,
scope
=
Scope
.
content
)
data
=
String
(
help
=
"XML data for the problem"
,
scope
=
Scope
.
content
)
...
@@ -882,16 +883,6 @@ class CapaDescriptor(CapaFields, RawDescriptor):
...
@@ -882,16 +883,6 @@ class CapaDescriptor(CapaFields, RawDescriptor):
'enable_markdown'
:
self
.
markdown
is
not
None
})
'enable_markdown'
:
self
.
markdown
is
not
None
})
return
_context
return
_context
@property
def
editable_metadata_fields
(
self
):
"""Remove metadata from the editable fields since it has its own editor"""
subset
=
super
(
CapaDescriptor
,
self
)
.
editable_metadata_fields
if
'markdown'
in
subset
:
del
subset
[
'markdown'
]
if
'empty'
in
subset
:
del
subset
[
'empty'
]
return
subset
# VS[compat]
# VS[compat]
# TODO (cpennington): Delete this method once all fall 2012 course are being
# TODO (cpennington): Delete this method once all fall 2012 course are being
# edited in the cms
# edited in the cms
...
@@ -901,3 +892,10 @@ class CapaDescriptor(CapaFields, RawDescriptor):
...
@@ -901,3 +892,10 @@ class CapaDescriptor(CapaFields, RawDescriptor):
'problems/'
+
path
[
8
:],
'problems/'
+
path
[
8
:],
path
[
8
:],
path
[
8
:],
]
]
@property
def
non_editable_metadata_fields
(
self
):
non_editable_fields
=
super
(
CapaDescriptor
,
self
)
.
non_editable_metadata_fields
non_editable_fields
.
extend
([
CapaDescriptor
.
due
,
CapaDescriptor
.
graceperiod
,
CapaDescriptor
.
force_save_button
,
CapaDescriptor
.
markdown
])
return
non_editable_fields
common/lib/xmodule/xmodule/combined_open_ended_module.py
View file @
d8d4111a
...
@@ -203,9 +203,7 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
...
@@ -203,9 +203,7 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
def
save_instance_data
(
self
):
def
save_instance_data
(
self
):
for
attribute
in
self
.
student_attributes
:
for
attribute
in
self
.
student_attributes
:
child_attr
=
getattr
(
self
.
child_module
,
attribute
)
setattr
(
self
,
attribute
,
getattr
(
self
.
child_module
,
attribute
))
if
child_attr
!=
getattr
(
self
,
attribute
):
setattr
(
self
,
attribute
,
getattr
(
self
.
child_module
,
attribute
))
class
CombinedOpenEndedDescriptor
(
CombinedOpenEndedFields
,
RawDescriptor
):
class
CombinedOpenEndedDescriptor
(
CombinedOpenEndedFields
,
RawDescriptor
):
...
...
common/lib/xmodule/xmodule/discussion_module.py
View file @
d8d4111a
...
@@ -37,3 +37,10 @@ class DiscussionDescriptor(DiscussionFields, MetadataOnlyEditingDescriptor, RawD
...
@@ -37,3 +37,10 @@ class DiscussionDescriptor(DiscussionFields, MetadataOnlyEditingDescriptor, RawD
metadata_translations
=
dict
(
RawDescriptor
.
metadata_translations
)
metadata_translations
=
dict
(
RawDescriptor
.
metadata_translations
)
metadata_translations
[
'id'
]
=
'discussion_id'
metadata_translations
[
'id'
]
=
'discussion_id'
metadata_translations
[
'for'
]
=
'discussion_target'
metadata_translations
[
'for'
]
=
'discussion_target'
@property
def
non_editable_metadata_fields
(
self
):
non_editable_fields
=
super
(
DiscussionDescriptor
,
self
)
.
non_editable_metadata_fields
# We may choose to enable sort_keys in the future, but while Kevin is investigating....
non_editable_fields
.
extend
([
DiscussionDescriptor
.
discussion_id
,
DiscussionDescriptor
.
sort_key
])
return
non_editable_fields
common/lib/xmodule/xmodule/html_module.py
View file @
d8d4111a
...
@@ -19,6 +19,7 @@ log = logging.getLogger("mitx.courseware")
...
@@ -19,6 +19,7 @@ log = logging.getLogger("mitx.courseware")
class
HtmlFields
(
object
):
class
HtmlFields
(
object
):
data
=
String
(
help
=
"Html contents to display for this module"
,
scope
=
Scope
.
content
)
data
=
String
(
help
=
"Html contents to display for this module"
,
scope
=
Scope
.
content
)
source_code
=
String
(
help
=
"Source code for LaTeX documents. This feature is not well-supported."
,
scope
=
Scope
.
settings
)
class
HtmlModule
(
HtmlFields
,
XModule
):
class
HtmlModule
(
HtmlFields
,
XModule
):
...
@@ -166,16 +167,6 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor):
...
@@ -166,16 +167,6 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor):
elt
.
set
(
"filename"
,
relname
)
elt
.
set
(
"filename"
,
relname
)
return
elt
return
elt
@property
def
editable_metadata_fields
(
self
):
"""Remove any metadata from the editable fields which have their own editor or shouldn't be edited by user."""
subset
=
super
(
HtmlDescriptor
,
self
)
.
editable_metadata_fields
if
'empty'
in
subset
:
del
subset
[
'empty'
]
return
subset
class
AboutDescriptor
(
HtmlDescriptor
):
class
AboutDescriptor
(
HtmlDescriptor
):
"""
"""
...
...
common/lib/xmodule/xmodule/js/src/.gitignore
View file @
d8d4111a
# Please do not ignore *.js files. Some xmodules are written in JS.
# Ignore .js files in this folder as they are compiled from coffeescript
# For each of the xmodules subdirectories, add a .gitignore file that
# will version any *.js file that is specifically written, not compiled.
*.js
common/lib/xmodule/xmodule/js/src/capa/.gitignore
0 → 100644
View file @
d8d4111a
!imageinput.js
!schematic.js
common/lib/xmodule/xmodule/js/src/graphical_slider_tool/.gitignore
0 → 100644
View file @
d8d4111a
!*.js
common/lib/xmodule/xmodule/js/src/peergrading/peer_grading.coffee
View file @
d8d4111a
...
@@ -8,20 +8,23 @@ class @PeerGrading
...
@@ -8,20 +8,23 @@ class @PeerGrading
@
use_single_location
=
@
peer_grading_container
.
data
(
'use-single-location'
)
@
use_single_location
=
@
peer_grading_container
.
data
(
'use-single-location'
)
@
peer_grading_outer_container
=
$
(
'.peer-grading-container'
)
@
peer_grading_outer_container
=
$
(
'.peer-grading-container'
)
@
ajax_url
=
@
peer_grading_container
.
data
(
'ajax-url'
)
@
ajax_url
=
@
peer_grading_container
.
data
(
'ajax-url'
)
@
error_container
=
$
(
'.error-container'
)
@
error_container
.
toggle
(
not
@
error_container
.
is
(
':empty'
))
@
message_container
=
$
(
'.message-container'
)
if
@
use_single_location
.
toLowerCase
()
==
"true"
@
message_container
.
toggle
(
not
@
message_container
.
is
(
':empty'
))
#If the peer grading element is linked to a single location, then activate the backend for that location
@
activate_problem
()
else
#Otherwise, activate the panel view.
@
error_container
=
$
(
'.error-container'
)
@
error_container
.
toggle
(
not
@
error_container
.
is
(
':empty'
))
@
problem_button
=
$
(
'.problem-button
'
)
@
message_container
=
$
(
'.message-container
'
)
@
problem_button
.
click
@
show_results
@
message_container
.
toggle
(
not
@
message_container
.
is
(
':empty'
))
@
problem_list
=
$
(
'.problem-list
'
)
@
problem_button
=
$
(
'.problem-button
'
)
@
construct_progress_bar
()
@
problem_button
.
click
@
show_results
if
@
use_single_location
@
problem_list
=
$
(
'.problem-list'
)
@
activate_problem
()
@
construct_progress_bar
()
construct_progress_bar
:
()
=>
construct_progress_bar
:
()
=>
problems
=
@
problem_list
.
find
(
'tr'
).
next
()
problems
=
@
problem_list
.
find
(
'tr'
).
next
()
...
...
common/lib/xmodule/xmodule/js/src/poll/.gitignore
0 → 100644
View file @
d8d4111a
!*.js
common/lib/xmodule/xmodule/js/src/sequence/display/.gitignore
0 → 100644
View file @
d8d4111a
!*.js
common/lib/xmodule/xmodule/js/src/videoalpha/display/.gitignore
0 → 100644
View file @
d8d4111a
!html5_video.js
common/lib/xmodule/xmodule/mako_module.py
View file @
d8d4111a
from
.x_module
import
XModuleDescriptor
,
DescriptorSystem
from
.x_module
import
XModuleDescriptor
,
DescriptorSystem
from
.modulestore.inheritance
import
own_metadata
class
MakoDescriptorSystem
(
DescriptorSystem
):
class
MakoDescriptorSystem
(
DescriptorSystem
):
...
@@ -34,20 +33,10 @@ class MakoModuleDescriptor(XModuleDescriptor):
...
@@ -34,20 +33,10 @@ class MakoModuleDescriptor(XModuleDescriptor):
"""
"""
return
{
return
{
'module'
:
self
,
'module'
:
self
,
'editable_metadata_fields'
:
self
.
editable_metadata_fields
,
'editable_metadata_fields'
:
self
.
editable_metadata_fields
}
}
def
get_html
(
self
):
def
get_html
(
self
):
return
self
.
system
.
render_template
(
return
self
.
system
.
render_template
(
self
.
mako_template
,
self
.
get_context
())
self
.
mako_template
,
self
.
get_context
())
# cdodge: encapsulate a means to expose "editable" metadata fields (i.e. not internal system metadata)
@property
def
editable_metadata_fields
(
self
):
fields
=
{}
for
field
,
value
in
own_metadata
(
self
)
.
items
():
if
field
in
self
.
system_metadata_fields
:
continue
fields
[
field
]
=
value
return
fields
common/lib/xmodule/xmodule/peer_grading_module.py
View file @
d8d4111a
...
@@ -11,7 +11,7 @@ from xmodule.raw_module import RawDescriptor
...
@@ -11,7 +11,7 @@ from xmodule.raw_module import RawDescriptor
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
.timeinfo
import
TimeInfo
from
.timeinfo
import
TimeInfo
from
xblock.core
import
Object
,
Integer
,
Boolean
,
String
,
Scope
from
xblock.core
import
Object
,
Integer
,
Boolean
,
String
,
Scope
from
xmodule.fields
import
Date
,
StringyFloat
from
xmodule.fields
import
Date
,
StringyFloat
,
StringyInteger
,
StringyBoolean
from
xmodule.open_ended_grading_classes.peer_grading_service
import
PeerGradingService
,
GradingServiceError
,
MockPeerGradingService
from
xmodule.open_ended_grading_classes.peer_grading_service
import
PeerGradingService
,
GradingServiceError
,
MockPeerGradingService
from
open_ended_grading_classes
import
combined_open_ended_rubric
from
open_ended_grading_classes
import
combined_open_ended_rubric
...
@@ -28,14 +28,14 @@ EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please
...
@@ -28,14 +28,14 @@ EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please
class
PeerGradingFields
(
object
):
class
PeerGradingFields
(
object
):
use_for_single_location
=
Boolean
(
help
=
"Whether to use this for a single location or as a panel."
,
use_for_single_location
=
Stringy
Boolean
(
help
=
"Whether to use this for a single location or as a panel."
,
default
=
USE_FOR_SINGLE_LOCATION
,
scope
=
Scope
.
settings
)
default
=
USE_FOR_SINGLE_LOCATION
,
scope
=
Scope
.
settings
)
link_to_location
=
String
(
help
=
"The location this problem is linked to."
,
default
=
LINK_TO_LOCATION
,
link_to_location
=
String
(
help
=
"The location this problem is linked to."
,
default
=
LINK_TO_LOCATION
,
scope
=
Scope
.
settings
)
scope
=
Scope
.
settings
)
is_graded
=
Boolean
(
help
=
"Whether or not this module is scored."
,
default
=
IS_GRADED
,
scope
=
Scope
.
settings
)
is_graded
=
Stringy
Boolean
(
help
=
"Whether or not this module is scored."
,
default
=
IS_GRADED
,
scope
=
Scope
.
settings
)
due_date
=
Date
(
help
=
"Due date that should be displayed."
,
default
=
None
,
scope
=
Scope
.
settings
)
due_date
=
Date
(
help
=
"Due date that should be displayed."
,
default
=
None
,
scope
=
Scope
.
settings
)
grace_period_string
=
String
(
help
=
"Amount of grace to give on the due date."
,
default
=
None
,
scope
=
Scope
.
settings
)
grace_period_string
=
String
(
help
=
"Amount of grace to give on the due date."
,
default
=
None
,
scope
=
Scope
.
settings
)
max_grade
=
Integer
(
help
=
"The maximum grade that a student can receieve for this problem."
,
default
=
MAX_SCORE
,
max_grade
=
Stringy
Integer
(
help
=
"The maximum grade that a student can receieve for this problem."
,
default
=
MAX_SCORE
,
scope
=
Scope
.
settings
)
scope
=
Scope
.
settings
)
student_data_for_location
=
Object
(
help
=
"Student data for a given peer grading problem."
,
student_data_for_location
=
Object
(
help
=
"Student data for a given peer grading problem."
,
scope
=
Scope
.
user_state
)
scope
=
Scope
.
user_state
)
...
@@ -93,9 +93,9 @@ class PeerGradingModule(PeerGradingFields, XModule):
...
@@ -93,9 +93,9 @@ class PeerGradingModule(PeerGradingFields, XModule):
if
not
self
.
ajax_url
.
endswith
(
"/"
):
if
not
self
.
ajax_url
.
endswith
(
"/"
):
self
.
ajax_url
=
self
.
ajax_url
+
"/"
self
.
ajax_url
=
self
.
ajax_url
+
"/"
if
not
isinstance
(
self
.
max_grade
,
(
int
,
long
)):
#StringyInteger could return None, so keep this check.
#This could result in an exception, but not wrapping in a try catch block so it moves up the stack
if
not
isinstance
(
self
.
max_grade
,
int
):
self
.
max_grade
=
int
(
self
.
max_grade
)
raise
TypeError
(
"max_grade needs to be an integer."
)
def
closed
(
self
):
def
closed
(
self
):
return
self
.
_closed
(
self
.
timeinfo
)
return
self
.
_closed
(
self
.
timeinfo
)
...
...
common/lib/xmodule/xmodule/tests/test_xml_module.py
0 → 100644
View file @
d8d4111a
from
xmodule.x_module
import
XModuleFields
from
xblock.core
import
Scope
,
String
,
Object
from
xmodule.fields
import
Date
,
StringyInteger
from
xmodule.xml_module
import
XmlDescriptor
import
unittest
from
.
import
test_system
from
mock
import
Mock
class
TestFields
(
object
):
# Will be returned by editable_metadata_fields.
max_attempts
=
StringyInteger
(
scope
=
Scope
.
settings
)
# Will not be returned by editable_metadata_fields because filtered out by non_editable_metadata_fields.
due
=
Date
(
scope
=
Scope
.
settings
)
# Will not be returned by editable_metadata_fields because is not Scope.settings.
student_answers
=
Object
(
scope
=
Scope
.
user_state
)
# Will be returned, and can override the inherited value from XModule.
display_name
=
String
(
scope
=
Scope
.
settings
)
class
EditableMetadataFieldsTest
(
unittest
.
TestCase
):
def
test_display_name_field
(
self
):
editable_fields
=
self
.
get_xml_editable_fields
({})
# Tests that the xblock fields (currently tags and name) get filtered out.
# Also tests that xml_attributes is filtered out of XmlDescriptor.
self
.
assertEqual
(
1
,
len
(
editable_fields
),
"Expected only 1 editable field for xml descriptor."
)
self
.
assert_display_name_default
(
editable_fields
)
def
test_override_default
(
self
):
# Tests that is_default is correct when a value overrides the default.
editable_fields
=
self
.
get_xml_editable_fields
({
'display_name'
:
'foo'
})
display_name
=
editable_fields
[
'display_name'
]
self
.
assertFalse
(
display_name
[
'is_default'
])
self
.
assertEqual
(
'foo'
,
display_name
[
'value'
])
def
test_additional_field
(
self
):
editable_fields
=
self
.
get_module_editable_fields
({
'max_attempts'
:
'7'
})
self
.
assertEqual
(
2
,
len
(
editable_fields
))
self
.
assert_field_values
(
editable_fields
,
'max_attempts'
,
TestFields
.
max_attempts
,
False
,
False
,
7
)
self
.
assert_display_name_default
(
editable_fields
)
editable_fields
=
self
.
get_module_editable_fields
({})
self
.
assert_field_values
(
editable_fields
,
'max_attempts'
,
TestFields
.
max_attempts
,
True
,
False
,
None
)
def
test_inherited_field
(
self
):
editable_fields
=
self
.
get_module_editable_fields
({
'display_name'
:
'inherited'
})
self
.
assert_field_values
(
editable_fields
,
'display_name'
,
XModuleFields
.
display_name
,
False
,
True
,
'inherited'
)
# Start of helper methods
def
get_xml_editable_fields
(
self
,
model_data
):
system
=
test_system
()
system
.
render_template
=
Mock
(
return_value
=
"<div>Test Template HTML</div>"
)
return
XmlDescriptor
(
system
=
system
,
location
=
None
,
model_data
=
model_data
)
.
editable_metadata_fields
def
get_module_editable_fields
(
self
,
model_data
):
class
TestModuleDescriptor
(
TestFields
,
XmlDescriptor
):
@property
def
non_editable_metadata_fields
(
self
):
non_editable_fields
=
super
(
TestModuleDescriptor
,
self
)
.
non_editable_metadata_fields
non_editable_fields
.
append
(
TestModuleDescriptor
.
due
)
return
non_editable_fields
system
=
test_system
()
system
.
render_template
=
Mock
(
return_value
=
"<div>Test Template HTML</div>"
)
descriptor
=
TestModuleDescriptor
(
system
=
system
,
location
=
None
,
model_data
=
model_data
)
descriptor
.
_inherited_metadata
=
{
'display_name'
:
'inherited'
}
return
descriptor
.
editable_metadata_fields
def
assert_display_name_default
(
self
,
editable_fields
):
self
.
assert_field_values
(
editable_fields
,
'display_name'
,
XModuleFields
.
display_name
,
True
,
False
,
None
)
def
assert_field_values
(
self
,
editable_fields
,
name
,
field
,
is_default
,
is_inherited
,
value
):
test_field
=
editable_fields
[
name
]
self
.
assertEqual
(
field
,
test_field
[
'field'
])
self
.
assertEqual
(
is_default
,
test_field
[
'is_default'
])
self
.
assertEqual
(
is_inherited
,
test_field
[
'is_inherited'
])
self
.
assertEqual
(
value
,
test_field
[
'value'
])
common/lib/xmodule/xmodule/x_module.py
View file @
d8d4111a
...
@@ -82,7 +82,7 @@ class XModuleFields(object):
...
@@ -82,7 +82,7 @@ class XModuleFields(object):
display_name
=
String
(
display_name
=
String
(
help
=
"Display name for this module"
,
help
=
"Display name for this module"
,
scope
=
Scope
.
settings
,
scope
=
Scope
.
settings
,
default
=
None
,
default
=
None
)
)
...
@@ -334,12 +334,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
...
@@ -334,12 +334,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
# (like a practice problem).
# (like a practice problem).
has_score
=
False
has_score
=
False
# cdodge: this is a list of metadata names which are 'system' metadata
# and should not be edited by an end-user
system_metadata_fields
=
[
'data_dir'
,
'published_date'
,
'published_by'
,
'is_draft'
,
'discussion_id'
,
'xml_attributes'
]
# A list of descriptor attributes that must be equal for the descriptors to
# A list of descriptor attributes that must be equal for the descriptors to
# be equal
# be equal
equality_attributes
=
(
'_model_data'
,
'location'
)
equality_attributes
=
(
'_model_data'
,
'location'
)
...
@@ -612,6 +606,48 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
...
@@ -612,6 +606,48 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
model_data
=
self
.
_model_data
,
model_data
=
self
.
_model_data
,
))
))
@property
def
non_editable_metadata_fields
(
self
):
"""
Return the list of fields that should not be editable in Studio.
When overriding, be sure to append to the superclasses' list.
"""
# We are not allowing editing of xblock tag and name fields at this time (for any component).
return
[
XBlock
.
tags
,
XBlock
.
name
]
@property
def
editable_metadata_fields
(
self
):
"""
Returns the metadata fields to be edited in Studio. These are fields with scope `Scope.settings`.
Can be limited by extending `non_editable_metadata_fields`.
"""
inherited_metadata
=
getattr
(
self
,
'_inherited_metadata'
,
{})
metadata
=
{}
for
field
in
self
.
fields
:
if
field
.
scope
!=
Scope
.
settings
or
field
in
self
.
non_editable_metadata_fields
:
continue
inherited
=
False
default
=
False
value
=
getattr
(
self
,
field
.
name
)
if
field
.
name
in
self
.
_model_data
:
default
=
False
if
field
.
name
in
inherited_metadata
:
if
self
.
_model_data
.
get
(
field
.
name
)
==
inherited_metadata
.
get
(
field
.
name
):
inherited
=
True
else
:
default
=
True
metadata
[
field
.
name
]
=
{
'field'
:
field
,
'value'
:
value
,
'is_inherited'
:
inherited
,
'is_default'
:
default
}
return
metadata
class
DescriptorSystem
(
object
):
class
DescriptorSystem
(
object
):
def
__init__
(
self
,
load_item
,
resources_fs
,
error_tracker
,
**
kwargs
):
def
__init__
(
self
,
load_item
,
resources_fs
,
error_tracker
,
**
kwargs
):
...
...
common/lib/xmodule/xmodule/xml_module.py
View file @
d8d4111a
...
@@ -84,7 +84,8 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -84,7 +84,8 @@ class XmlDescriptor(XModuleDescriptor):
Mixin class for standardized parsing of from xml
Mixin class for standardized parsing of from xml
"""
"""
xml_attributes
=
Object
(
help
=
"Map of unhandled xml attributes, used only for storage between import and export"
,
default
=
{},
scope
=
Scope
.
settings
)
xml_attributes
=
Object
(
help
=
"Map of unhandled xml attributes, used only for storage between import and export"
,
default
=
{},
scope
=
Scope
.
settings
)
# Extension to append to filename paths
# Extension to append to filename paths
filename_extension
=
'xml'
filename_extension
=
'xml'
...
@@ -418,3 +419,9 @@ class XmlDescriptor(XModuleDescriptor):
...
@@ -418,3 +419,9 @@ class XmlDescriptor(XModuleDescriptor):
"""
"""
raise
NotImplementedError
(
raise
NotImplementedError
(
"
%
s does not implement definition_to_xml"
%
self
.
__class__
.
__name__
)
"
%
s does not implement definition_to_xml"
%
self
.
__class__
.
__name__
)
@property
def
non_editable_metadata_fields
(
self
):
non_editable_fields
=
super
(
XmlDescriptor
,
self
)
.
non_editable_metadata_fields
non_editable_fields
.
append
(
XmlDescriptor
.
xml_attributes
)
return
non_editable_fields
lms/djangoapps/courseware/tests/tests.py
View file @
d8d4111a
'''
'''
Test for lms courseware app
Test for lms courseware app
'''
'''
import
logging
import
logging
import
json
import
json
import
time
import
time
import
random
import
random
from
urlparse
import
urlsplit
,
urlunsplit
from
urlparse
import
urlsplit
,
urlunsplit
from
uuid
import
uuid4
from
django.contrib.auth.models
import
User
,
Group
from
django.contrib.auth.models
import
User
,
Group
from
django.test
import
TestCase
from
django.test
import
TestCase
...
@@ -62,7 +62,7 @@ def mongo_store_config(data_dir):
...
@@ -62,7 +62,7 @@ def mongo_store_config(data_dir):
'default_class'
:
'xmodule.raw_module.RawDescriptor'
,
'default_class'
:
'xmodule.raw_module.RawDescriptor'
,
'host'
:
'localhost'
,
'host'
:
'localhost'
,
'db'
:
'test_xmodule'
,
'db'
:
'test_xmodule'
,
'collection'
:
'modulestore
'
,
'collection'
:
'modulestore
_
%
s'
%
uuid4
()
.
hex
,
'fs_root'
:
data_dir
,
'fs_root'
:
data_dir
,
'render_template'
:
'mitxmako.shortcuts.render_to_string'
,
'render_template'
:
'mitxmako.shortcuts.render_to_string'
,
}
}
...
@@ -81,7 +81,7 @@ def draft_mongo_store_config(data_dir):
...
@@ -81,7 +81,7 @@ def draft_mongo_store_config(data_dir):
'default_class'
:
'xmodule.raw_module.RawDescriptor'
,
'default_class'
:
'xmodule.raw_module.RawDescriptor'
,
'host'
:
'localhost'
,
'host'
:
'localhost'
,
'db'
:
'test_xmodule'
,
'db'
:
'test_xmodule'
,
'collection'
:
'modulestore
'
,
'collection'
:
'modulestore
_
%
s'
%
uuid4
()
.
hex
,
'fs_root'
:
data_dir
,
'fs_root'
:
data_dir
,
'render_template'
:
'mitxmako.shortcuts.render_to_string'
,
'render_template'
:
'mitxmako.shortcuts.render_to_string'
,
}
}
...
@@ -92,7 +92,7 @@ def draft_mongo_store_config(data_dir):
...
@@ -92,7 +92,7 @@ def draft_mongo_store_config(data_dir):
'default_class'
:
'xmodule.raw_module.RawDescriptor'
,
'default_class'
:
'xmodule.raw_module.RawDescriptor'
,
'host'
:
'localhost'
,
'host'
:
'localhost'
,
'db'
:
'test_xmodule'
,
'db'
:
'test_xmodule'
,
'collection'
:
'modulestore
'
,
'collection'
:
'modulestore
_
%
s'
%
uuid4
()
.
hex
,
'fs_root'
:
data_dir
,
'fs_root'
:
data_dir
,
'render_template'
:
'mitxmako.shortcuts.render_to_string'
,
'render_template'
:
'mitxmako.shortcuts.render_to_string'
,
}
}
...
...
lms/djangoapps/open_ended_grading/open_ended_notifications.py
View file @
d8d4111a
...
@@ -11,6 +11,7 @@ from util.cache import cache
...
@@ -11,6 +11,7 @@ from util.cache import cache
import
datetime
import
datetime
from
xmodule.x_module
import
ModuleSystem
from
xmodule.x_module
import
ModuleSystem
from
mitxmako.shortcuts
import
render_to_string
from
mitxmako.shortcuts
import
render_to_string
import
datetime
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -104,6 +105,25 @@ def peer_grading_notifications(course, user):
...
@@ -104,6 +105,25 @@ def peer_grading_notifications(course, user):
def
combined_notifications
(
course
,
user
):
def
combined_notifications
(
course
,
user
):
"""
Show notifications to a given user for a given course. Get notifications from the cache if possible,
or from the grading controller server if not.
@param course: The course object for which we are getting notifications
@param user: The user object for which we are getting notifications
@return: A dictionary with boolean pending_grading (true if there is pending grading), img_path (for notification
image), and response (actual response from grading controller server).
"""
#Set up return values so that we can return them for error cases
pending_grading
=
False
img_path
=
""
notifications
=
{}
notification_dict
=
{
'pending_grading'
:
pending_grading
,
'img_path'
:
img_path
,
'response'
:
notifications
}
#We don't want to show anonymous users anything.
if
not
user
.
is_authenticated
():
return
notification_dict
#Define a mock modulesystem
system
=
ModuleSystem
(
system
=
ModuleSystem
(
ajax_url
=
None
,
ajax_url
=
None
,
track_function
=
None
,
track_function
=
None
,
...
@@ -112,41 +132,44 @@ def combined_notifications(course, user):
...
@@ -112,41 +132,44 @@ def combined_notifications(course, user):
replace_urls
=
None
,
replace_urls
=
None
,
xblock_model_data
=
{}
xblock_model_data
=
{}
)
)
#Initialize controller query service using our mock system
controller_qs
=
ControllerQueryService
(
settings
.
OPEN_ENDED_GRADING_INTERFACE
,
system
)
controller_qs
=
ControllerQueryService
(
settings
.
OPEN_ENDED_GRADING_INTERFACE
,
system
)
student_id
=
unique_id_for_user
(
user
)
student_id
=
unique_id_for_user
(
user
)
user_is_staff
=
has_access
(
user
,
course
,
'staff'
)
user_is_staff
=
has_access
(
user
,
course
,
'staff'
)
course_id
=
course
.
id
course_id
=
course
.
id
notification_type
=
"combined"
notification_type
=
"combined"
#See if we have a stored value in the cache
success
,
notification_dict
=
get_value_from_cache
(
student_id
,
course_id
,
notification_type
)
success
,
notification_dict
=
get_value_from_cache
(
student_id
,
course_id
,
notification_type
)
if
success
:
if
success
:
return
notification_dict
return
notification_dict
min_time_to_query
=
user
.
last_login
#Get the time of the last login of the user
last_login
=
user
.
last_login
#Find the modules they have seen since they logged in
last_module_seen
=
StudentModule
.
objects
.
filter
(
student
=
user
,
course_id
=
course_id
,
last_module_seen
=
StudentModule
.
objects
.
filter
(
student
=
user
,
course_id
=
course_id
,
modified__gt
=
min_time_to_query
)
.
values
(
'modified'
)
.
order_by
(
modified__gt
=
last_login
)
.
values
(
'modified'
)
.
order_by
(
'-modified'
)
'-modified'
)
last_module_seen_count
=
last_module_seen
.
count
()
last_module_seen_count
=
last_module_seen
.
count
()
if
last_module_seen_count
>
0
:
if
last_module_seen_count
>
0
:
#The last time they viewed an updated notification (last module seen minus how long notifications are cached)
last_time_viewed
=
last_module_seen
[
0
][
'modified'
]
-
datetime
.
timedelta
(
seconds
=
(
NOTIFICATION_CACHE_TIME
+
60
))
last_time_viewed
=
last_module_seen
[
0
][
'modified'
]
-
datetime
.
timedelta
(
seconds
=
(
NOTIFICATION_CACHE_TIME
+
60
))
else
:
else
:
last_time_viewed
=
user
.
last_login
#If they have not seen any modules since they logged in, then don't refresh
return
{
'pending_grading'
:
False
,
'img_path'
:
img_path
,
'response'
:
notifications
}
pending_grading
=
False
img_path
=
""
try
:
try
:
#Get the notifications from the grading controller
controller_response
=
controller_qs
.
check_combined_notifications
(
course
.
id
,
student_id
,
user_is_staff
,
controller_response
=
controller_qs
.
check_combined_notifications
(
course
.
id
,
student_id
,
user_is_staff
,
last_time_viewed
)
last_time_viewed
)
log
.
debug
(
controller_response
)
notifications
=
json
.
loads
(
controller_response
)
notifications
=
json
.
loads
(
controller_response
)
if
notifications
[
'success'
]:
if
notifications
[
'success'
]:
if
notifications
[
'overall_need_to_check'
]:
if
notifications
[
'overall_need_to_check'
]:
pending_grading
=
True
pending_grading
=
True
except
:
except
:
#Non catastrophic error, so no real action
#Non catastrophic error, so no real action
notifications
=
{}
#This is a dev_facing_error
#This is a dev_facing_error
log
.
exception
(
log
.
exception
(
"Problem with getting notifications from controller query service for course {0} user {1}."
.
format
(
"Problem with getting notifications from controller query service for course {0} user {1}."
.
format
(
...
@@ -157,6 +180,7 @@ def combined_notifications(course, user):
...
@@ -157,6 +180,7 @@ def combined_notifications(course, user):
notification_dict
=
{
'pending_grading'
:
pending_grading
,
'img_path'
:
img_path
,
'response'
:
notifications
}
notification_dict
=
{
'pending_grading'
:
pending_grading
,
'img_path'
:
img_path
,
'response'
:
notifications
}
#Store the notifications in the cache
set_value_in_cache
(
student_id
,
course_id
,
notification_type
,
notification_dict
)
set_value_in_cache
(
student_id
,
course_id
,
notification_type
,
notification_dict
)
return
notification_dict
return
notification_dict
...
...
lms/lib/comment_client/utils.py
View file @
d8d4111a
...
@@ -37,6 +37,10 @@ def perform_request(method, url, data_or_params=None, *args, **kwargs):
...
@@ -37,6 +37,10 @@ def perform_request(method, url, data_or_params=None, *args, **kwargs):
else
:
else
:
response
=
requests
.
request
(
method
,
url
,
params
=
data_or_params
,
timeout
=
5
)
response
=
requests
.
request
(
method
,
url
,
params
=
data_or_params
,
timeout
=
5
)
except
Exception
as
err
:
except
Exception
as
err
:
# remove API key if it is in the params
if
'api_key'
in
data_or_params
:
log
.
info
(
'Deleting API key from params'
)
del
data_or_params
[
'api_key'
]
log
.
exception
(
"Trying to call {method} on {url} with params {params}"
.
format
(
log
.
exception
(
"Trying to call {method} on {url} with params {params}"
.
format
(
method
=
method
,
url
=
url
,
params
=
data_or_params
))
method
=
method
,
url
=
url
,
params
=
data_or_params
))
# Reraise with a single exception type
# Reraise with a single exception type
...
...
lms/static/sass/shared/_modal.scss
View file @
d8d4111a
...
@@ -149,7 +149,7 @@
...
@@ -149,7 +149,7 @@
}
}
label
{
label
{
color
:
#
999
;
color
:
#
646464
;
&
.field-error
{
&
.field-error
{
display
:
block
;
display
:
block
;
...
...
lms/templates/courseware/courseware.html
View file @
d8d4111a
...
@@ -156,7 +156,7 @@
...
@@ -156,7 +156,7 @@
<div
id=
"calculator_wrapper"
>
<div
id=
"calculator_wrapper"
>
<form
id=
"calculator"
>
<form
id=
"calculator"
>
<div
class=
"input-wrapper"
>
<div
class=
"input-wrapper"
>
<input
type=
"text"
id=
"calculator_input"
/>
<input
type=
"text"
id=
"calculator_input"
title=
"Calculator Input Field"
/>
<div
class=
"help-wrapper"
>
<div
class=
"help-wrapper"
>
<a
href=
"#"
>
Hints
</a>
<a
href=
"#"
>
Hints
</a>
...
@@ -176,8 +176,8 @@
...
@@ -176,8 +176,8 @@
</dl>
</dl>
</div>
</div>
</div>
</div>
<input
id=
"calculator_button"
type=
"submit"
value=
"="
/>
<input
id=
"calculator_button"
type=
"submit"
title=
"Calculate"
value=
"="
/>
<input
type=
"text"
id=
"calculator_output"
readonly
/>
<input
type=
"text"
id=
"calculator_output"
title=
"Calculator Output Field"
readonly
/>
</form>
</form>
</div>
</div>
...
...
lms/templates/forgot_password_modal.html
View file @
d8d4111a
...
@@ -12,19 +12,19 @@
...
@@ -12,19 +12,19 @@
</div>
</div>
<form
id=
"pwd_reset_form"
action=
"${reverse('password_reset')}"
method=
"post"
data-remote=
"true"
>
<form
id=
"pwd_reset_form"
action=
"${reverse('password_reset')}"
method=
"post"
data-remote=
"true"
>
<label
for=
"
id
_email"
>
E-mail address:
</label>
<label
for=
"
pwd_reset
_email"
>
E-mail address:
</label>
<input
id=
"
id
_email"
type=
"email"
name=
"email"
maxlength=
"75"
placeholder=
"Your E-mail"
/>
<input
id=
"
pwd_reset
_email"
type=
"email"
name=
"email"
maxlength=
"75"
placeholder=
"Your E-mail"
/>
<div
class=
"submit"
>
<div
class=
"submit"
>
<input
type=
"submit"
id=
"pwd_reset_button"
value=
"Reset my password"
/>
<input
type=
"submit"
id=
"pwd_reset_button"
value=
"Reset my password"
/>
</div>
</div>
</form>
</form>
</div>
</div>
<
div
class=
"close-m
odal"
>
<
a
href=
"#"
class=
"close-modal"
title=
"Close M
odal"
>
<div
class=
"inner"
>
<div
class=
"inner"
>
<p>
✕
</p>
<p>
✕
</p>
</div>
</div>
</
div
>
</
a
>
</div>
</div>
</section>
</section>
...
@@ -40,5 +40,10 @@
...
@@ -40,5 +40,10 @@
$
(
'#pwd_error'
).
stop
().
css
(
"display"
,
"block"
);
$
(
'#pwd_error'
).
stop
().
css
(
"display"
,
"block"
);
}
}
});
});
// removing close link's default behavior
$
(
'#login-modal .close-modal'
).
click
(
function
(
e
)
{
e
.
preventDefault
();
});
})(
this
)
})(
this
)
</script>
</script>
lms/templates/login_modal.html
View file @
d8d4111a
...
@@ -9,14 +9,17 @@
...
@@ -9,14 +9,17 @@
</header>
</header>
<form
id=
"login_form"
class=
"login_form"
method=
"post"
data-remote=
"true"
action=
"/login"
>
<form
id=
"login_form"
class=
"login_form"
method=
"post"
data-remote=
"true"
action=
"/login"
>
<label>
E-mail
</label>
<label
for=
"login_email"
>
E-mail
</label>
<input
name=
"email"
type=
"email"
>
<input
id=
"login_email"
type=
"email"
name=
"email"
placeholder=
"e.g. yourname@domain.com"
/>
<label>
Password
</label>
<input
name=
"password"
type=
"password"
>
<label
for=
"login_password"
>
Password
</label>
<label
class=
"remember-me"
>
<input
id=
"login_password"
type=
"password"
name=
"password"
placeholder=
"••••••••"
/>
<input
name=
"remember"
type=
"checkbox"
value=
"true"
>
<label
for=
"login_remember_me"
class=
"remember-me"
>
<input
id=
"login_remember_me"
type=
"checkbox"
name=
"remember"
value=
"true"
/>
Remember me
Remember me
</label>
</label>
<div
class=
"submit"
>
<div
class=
"submit"
>
<input
name=
"submit"
type=
"submit"
value=
"Access My Courses"
>
<input
name=
"submit"
type=
"submit"
value=
"Access My Courses"
>
</div>
</div>
...
@@ -34,11 +37,11 @@
...
@@ -34,11 +37,11 @@
% endif
% endif
</section>
</section>
<
div
class=
"close-m
odal"
>
<
a
href=
"#"
class=
"close-modal"
title=
"Close M
odal"
>
<div
class=
"inner"
>
<div
class=
"inner"
>
<p>
✕
</p>
<p>
✕
</p>
</div>
</div>
</
div
>
</
a
>
</div>
</div>
</section>
</section>
...
@@ -59,5 +62,10 @@
...
@@ -59,5 +62,10 @@
$
(
'#login_error'
).
html
(
json
.
value
).
stop
().
css
(
"display"
,
"block"
);
$
(
'#login_error'
).
html
(
json
.
value
).
stop
().
css
(
"display"
,
"block"
);
}
}
});
});
// removing close link's default behavior
$
(
'#login-modal .close-modal'
).
click
(
function
(
e
)
{
e
.
preventDefault
();
});
})(
this
)
})(
this
)
</script>
</script>
lms/templates/seq_module.html
View file @
d8d4111a
...
@@ -10,7 +10,8 @@
...
@@ -10,7 +10,8 @@
<li>
<li>
<a
class=
"seq_${item['type']} inactive progress-${item['progress_status']}"
<a
class=
"seq_${item['type']} inactive progress-${item['progress_status']}"
data-id=
"${item['id']}"
data-id=
"${item['id']}"
data-element=
"${idx+1}"
>
data-element=
"${idx+1}"
href=
"javascript:void(0);"
>
<p>
${item['title']}
</p>
<p>
${item['title']}
</p>
</a>
</a>
</li>
</li>
...
...
lms/templates/signup_modal.html
View file @
d8d4111a
...
@@ -20,27 +20,31 @@
...
@@ -20,27 +20,31 @@
<div
class=
"input-group"
>
<div
class=
"input-group"
>
% if has_extauth_info is UNDEFINED:
% if has_extauth_info is UNDEFINED:
<label
data-field=
"email"
>
E-mail*
</label>
<label
data-field=
"email"
for=
"signup_email"
>
E-mail *
</label>
<input
name=
"email"
type=
"email"
placeholder=
"eg. yourname@domain.com"
>
<input
id=
"signup_email"
type=
"email"
name=
"email"
placeholder=
"e.g. yourname@domain.com"
required
/>
<label
data-field=
"password"
>
Password*
</label>
<input
name=
"password"
type=
"password"
placeholder=
"****"
>
<label
data-field=
"password"
for=
"signup_password"
>
Password *
</label>
<label
data-field=
"username"
>
Public Username*
</label>
<input
id=
"signup_password"
type=
"password"
name=
"password"
placeholder=
"••••••••"
required
/>
<input
name=
"username"
type=
"text"
placeholder=
"Shown on forums"
>
<label
data-field=
"name"
>
Full Name*
</label>
<label
data-field=
"username"
for=
"signup_username"
>
Public Username *
</label>
<input
name=
"name"
type=
"text"
placeholder=
"For your certificate"
>
<input
id=
"signup_username"
type=
"text"
name=
"username"
placeholder=
"e.g. yourname (shown on forums)"
required
/>
<label
data-field=
"name"
for=
"signup_fullname"
>
Full Name *
</label>
<input
id=
"signup_fullname"
type=
"text"
name=
"name"
placeholder=
"e.g. Your Name (for certificates)"
required
/>
% else:
% else:
<p><i>
Welcome
</i>
${extauth_email}
</p><br/>
<p><i>
Welcome
</i>
${extauth_email}
</p><br/>
<p><i>
Enter a public username:
</i></p>
<p><i>
Enter a public username:
</i></p>
<label
data-field=
"username"
>
Public Username*
</label>
<input
name=
"username"
type=
"text"
value=
"${extauth_username}"
placeholder=
"Shown on forums"
>
<label
data-field=
"username"
for=
"signup_username"
>
Public Username *
</label>
<input
id=
"signup_username"
type=
"text"
name=
"username"
value=
"${extauth_username}"
placeholder=
"e.g. yourname (shown on forums)"
required
/>
% endif
% endif
</div>
</div>
<div
class=
"input-group"
>
<div
class=
"input-group"
>
<section
class=
"citizenship"
>
<section
class=
"citizenship"
>
<label
data-field=
"level_of_education"
>
Ed. c
ompleted
</label>
<label
data-field=
"level_of_education"
for=
"signup_ed_level"
>
Ed. C
ompleted
</label>
<div
class=
"input-wrapper"
>
<div
class=
"input-wrapper"
>
<select
name=
"level_of_education"
>
<select
id=
"signup_ed_level"
name=
"level_of_education"
>
<option
value=
""
>
--
</option>
<option
value=
""
>
--
</option>
%for code, ed_level in UserProfile.LEVEL_OF_EDUCATION_CHOICES:
%for code, ed_level in UserProfile.LEVEL_OF_EDUCATION_CHOICES:
<option
value=
"${code}"
>
${ed_level}
</option>
<option
value=
"${code}"
>
${ed_level}
</option>
...
@@ -50,9 +54,9 @@
...
@@ -50,9 +54,9 @@
</section>
</section>
<section
class=
"gender"
>
<section
class=
"gender"
>
<label
data-field=
"gender"
>
Gender
</label>
<label
data-field=
"gender"
for=
"signup_gender"
>
Gender
</label>
<div
class=
"input-wrapper"
>
<div
class=
"input-wrapper"
>
<select
name=
"gender"
>
<select
id=
"signup_gender"
name=
"gender"
>
<option
value=
""
>
--
</option>
<option
value=
""
>
--
</option>
%for code, gender in UserProfile.GENDER_CHOICES:
%for code, gender in UserProfile.GENDER_CHOICES:
<option
value=
"${code}"
>
${gender}
</option>
<option
value=
"${code}"
>
${gender}
</option>
...
@@ -62,9 +66,9 @@
...
@@ -62,9 +66,9 @@
</section>
</section>
<section
class=
"date-of-birth"
>
<section
class=
"date-of-birth"
>
<label
data-field=
"date-of-birth"
>
Year of birth
</label>
<label
data-field=
"date-of-birth"
for=
"signup_birth_year"
>
Year of birth
</label>
<div
class=
"input-wrapper"
>
<div
class=
"input-wrapper"
>
<select
name=
"year_of_birth"
>
<select
id=
"signup_birth_year"
name=
"year_of_birth"
>
<option
value=
""
>
--
</option>
<option
value=
""
>
--
</option>
%for year in UserProfile.VALID_YEARS:
%for year in UserProfile.VALID_YEARS:
<option
value=
"${year}"
>
${year}
</option>
<option
value=
"${year}"
>
${year}
</option>
...
@@ -74,22 +78,23 @@
...
@@ -74,22 +78,23 @@
</div>
</div>
</section>
</section>
<label
data-field=
"mailing_address"
>
Mailing address
</label>
<label
data-field=
"mailing_address"
for=
"signup_mailing_address"
>
Mailing address
</label>
<textarea
name=
"mailing_address"
></textarea>
<textarea
id=
"signup_mailing_address"
name=
"mailing_address"
></textarea>
<label
data-field=
"goals"
>
Goals in signing up for edX
</label>
<textarea
name=
"goals"
></textarea>
<label
data-field=
"goals"
for=
"signup_goals"
>
Goals in signing up for edX
</label>
<textarea
name=
"goals"
id=
"signup_goals"
></textarea>
</div>
</div>
<div
class=
"input-group"
>
<div
class=
"input-group"
>
<label
data-field=
"terms_of_service"
class=
"terms-of-service"
>
<label
data-field=
"terms_of_service"
class=
"terms-of-service"
for=
"signup_tos"
>
<input
name=
"terms_of_service"
type=
"checkbox"
value=
"true"
>
<input
id=
"signup_tos"
name=
"terms_of_service"
type=
"checkbox"
value=
"true"
>
I agree to the
I agree to the
<a
href=
"${reverse('tos')}"
target=
"_blank"
>
Terms of Service
</a>
*
<a
href=
"${reverse('tos')}"
target=
"_blank"
>
Terms of Service
</a>
*
</label>
</label>
<label
data-field=
"honor_code"
class=
"honor-code"
>
<label
data-field=
"honor_code"
class=
"honor-code"
for=
"signup_honor"
>
<input
name=
"honor_code"
type=
"checkbox"
value=
"true"
>
<input
id=
"signup_honor"
name=
"honor_code"
type=
"checkbox"
value=
"true"
>
I agree to the
I agree to the
<a
href=
"${reverse('honor')}"
target=
"_blank"
>
Honor Code
</a>
*
<a
href=
"${reverse('honor')}"
target=
"_blank"
>
Honor Code
</a>
*
</label>
</label>
...
@@ -110,11 +115,11 @@
...
@@ -110,11 +115,11 @@
</div>
</div>
<
div
class=
"close-m
odal"
>
<
a
href=
"#"
class=
"close-modal"
title=
"Close M
odal"
>
<div
class=
"inner"
>
<div
class=
"inner"
>
<p>
✕
</p>
<p>
✕
</p>
</div>
</div>
</
div
>
</
a
>
</div>
</div>
</section>
</section>
...
@@ -129,5 +134,10 @@
...
@@ -129,5 +134,10 @@
$
(
"[data-field='"
+
json
.
field
+
"']"
).
addClass
(
'field-error'
)
$
(
"[data-field='"
+
json
.
field
+
"']"
).
addClass
(
'field-error'
)
}
}
});
});
// removing close link's default behavior
$
(
'#login-modal .close-modal'
).
click
(
function
(
e
)
{
e
.
preventDefault
();
});
})(
this
)
})(
this
)
</script>
</script>
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