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
01e15c1e
Commit
01e15c1e
authored
Apr 23, 2013
by
John Jarvis
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into drupal-new
parents
0c1fd783
7e35bf19
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
421 additions
and
116 deletions
+421
-116
cms/djangoapps/contentstore/tests/test_contentstore.py
+0
-0
cms/djangoapps/contentstore/utils.py
+1
-0
cms/djangoapps/contentstore/views.py
+4
-3
cms/one_time_startup.py
+4
-1
cms/static/js/base.js
+0
-1
cms/static/sass/elements/_controls.scss
+1
-1
cms/static/sass/elements/_forms.scss
+72
-8
cms/static/sass/views/_account.scss
+7
-7
cms/static/sass/views/_settings.scss
+80
-26
cms/templates/settings.html
+0
-0
cms/templates/widgets/metadata-only-edit.html
+1
-0
cms/templates/widgets/source-edit.html
+1
-1
common/lib/xmodule/xmodule/capa_module.py
+1
-0
common/lib/xmodule/xmodule/discussion_module.py
+2
-1
common/lib/xmodule/xmodule/editing_module.py
+12
-0
common/lib/xmodule/xmodule/html_module.py
+4
-3
common/lib/xmodule/xmodule/js/src/raw/edit/metadata-only.coffee
+5
-0
common/lib/xmodule/xmodule/modulestore/__init__.py
+1
-2
common/lib/xmodule/xmodule/modulestore/draft.py
+10
-7
common/lib/xmodule/xmodule/modulestore/mongo.py
+26
-3
common/lib/xmodule/xmodule/modulestore/xml_exporter.py
+19
-2
common/lib/xmodule/xmodule/modulestore/xml_importer.py
+0
-0
common/lib/xmodule/xmodule/raw_module.py
+2
-2
common/lib/xmodule/xmodule/templates/discussion/default.yaml
+2
-2
common/lib/xmodule/xmodule/templates/html/latex_html.yaml
+1
-2
common/lib/xmodule/xmodule/templates/problem/latex_problem.yaml
+1
-2
common/lib/xmodule/xmodule/templates/problem/problem_with_hint.yaml
+0
-1
common/lib/xmodule/xmodule/x_module.py
+3
-1
common/lib/xmodule/xmodule/xml_module.py
+2
-3
lms/djangoapps/courseware/tests/test_login.py
+17
-19
lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py
+5
-4
lms/djangoapps/django_comment_client/tests/mock_cs_server/__init__.py
+0
-0
lms/djangoapps/django_comment_client/tests/mock_cs_server/mock_cs_server.py
+76
-0
lms/djangoapps/django_comment_client/tests/mock_cs_server/test_mock_cs_server.py
+59
-0
lms/djangoapps/django_comment_client/utils.py
+2
-14
No files found.
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
01e15c1e
This diff is collapsed.
Click to expand it.
cms/djangoapps/contentstore/utils.py
View file @
01e15c1e
...
...
@@ -11,6 +11,7 @@ DIRECT_ONLY_CATEGORIES = ['course', 'chapter', 'sequential', 'about', 'static_ta
#In order to instantiate an open ended tab automatically, need to have this data
OPEN_ENDED_PANEL
=
{
"name"
:
"Open Ended Panel"
,
"type"
:
"open_ended"
}
def
get_modulestore
(
location
):
"""
Returns the correct modulestore to use for modifying the specified location
...
...
cms/djangoapps/contentstore/views.py
View file @
01e15c1e
...
...
@@ -1586,7 +1586,8 @@ def import_course(request, org, course, name):
shutil
.
move
(
r
/
fname
,
course_dir
)
module_store
,
course_items
=
import_from_xml
(
modulestore
(
'direct'
),
settings
.
GITHUB_REPO_ROOT
,
[
course_subdir
],
load_error_modules
=
False
,
static_content_store
=
contentstore
(),
target_location_namespace
=
Location
(
location
))
[
course_subdir
],
load_error_modules
=
False
,
static_content_store
=
contentstore
(),
target_location_namespace
=
Location
(
location
),
draft_store
=
modulestore
())
# we can blow this away when we're done importing.
shutil
.
rmtree
(
course_dir
)
...
...
@@ -1620,8 +1621,8 @@ def generate_export_course(request, org, course, name):
logging
.
debug
(
'root = {0}'
.
format
(
root_dir
))
export_to_xml
(
modulestore
(
'direct'
),
contentstore
(),
loc
,
root_dir
,
name
)
#
filename = root_dir / name + '.tar.gz'
export_to_xml
(
modulestore
(
'direct'
),
contentstore
(),
loc
,
root_dir
,
name
,
modulestore
()
)
#filename = root_dir / name + '.tar.gz'
logging
.
debug
(
'tar file being generated at {0}'
.
format
(
export_file
.
name
))
tf
=
tarfile
.
open
(
name
=
export_file
.
name
,
mode
=
'w:gz'
)
...
...
cms/one_time_startup.py
View file @
01e15c1e
from
dogapi
import
dog_http_api
,
dog_stats_api
from
django.conf
import
settings
from
xmodule.modulestore.django
import
modulestore
from
django.dispatch
import
Signal
from
request_cache.middleware
import
RequestCache
from
django.core.cache
import
get_cache
,
InvalidCacheBackendError
from
django.core.cache
import
get_cache
cache
=
get_cache
(
'mongo_metadata_inheritance'
)
for
store_name
in
settings
.
MODULESTORE
:
...
...
@@ -11,6 +12,8 @@ for store_name in settings.MODULESTORE:
store
.
metadata_inheritance_cache_subsystem
=
cache
store
.
request_cache
=
RequestCache
.
get_request_cache
()
modulestore_update_signal
=
Signal
(
providing_args
=
[
'modulestore'
,
'course_id'
,
'location'
])
store
.
modulestore_update_signal
=
modulestore_update_signal
if
hasattr
(
settings
,
'DATADOG_API'
):
dog_http_api
.
api_key
=
settings
.
DATADOG_API
dog_stats_api
.
start
(
api_key
=
settings
.
DATADOG_API
,
statsd
=
True
)
cms/static/js/base.js
View file @
01e15c1e
...
...
@@ -225,7 +225,6 @@ function toggleSections(e) {
function
editSectionPublishDate
(
e
)
{
e
.
preventDefault
();
$modal
=
$
(
'.edit-subsection-publish-settings'
).
show
();
$modal
=
$
(
'.edit-subsection-publish-settings'
).
show
();
$modal
.
attr
(
'data-id'
,
$
(
this
).
attr
(
'data-id'
));
$modal
.
find
(
'.start-date'
).
val
(
$
(
this
).
attr
(
'data-date'
));
$modal
.
find
(
'.start-time'
).
val
(
$
(
this
).
attr
(
'data-time'
));
...
...
cms/static/sass/elements/_controls.scss
View file @
01e15c1e
...
...
@@ -97,7 +97,7 @@
color
:
$blue
;
&
:hover
,
&
:active
{
background
:
$blue-l
3
;
background
:
$blue-l
4
;
color
:
$blue-s2
;
}
...
...
cms/static/sass/elements/_forms.scss
View file @
01e15c1e
...
...
@@ -8,11 +8,11 @@ input[type="password"],
textarea
.text
{
padding
:
6px
8px
8px
;
@include
box-sizing
(
border-box
);
border
:
1px
solid
$
mediumGrey
;
border
:
1px
solid
$
gray-l2
;
border-radius
:
2px
;
@include
linear-gradient
(
$
lightGrey
,
tint
(
$lightGrey
,
90%
)
);
background-color
:
$
lightGrey
;
@include
box-shadow
(
0
1px
2px
rgba
(
0
,
0
,
0
,
.1
)
inset
);
@include
linear-gradient
(
$
gray-l5
,
$white
);
background-color
:
$
gray-l5
;
@include
box-shadow
(
inset
0
1px
2px
$shadow-l1
);
font-family
:
'Open Sans'
,
sans-serif
;
font-size
:
11px
;
color
:
$baseFontColor
;
...
...
@@ -21,7 +21,7 @@ textarea.text {
&
:
:-
webkit-input-placeholder
,
&:-
moz-placeholder
,
&:-
ms-input-placeholder
{
color
:
#979faf
;
color
:
$gray-l2
;
}
&
:focus
{
...
...
@@ -30,7 +30,72 @@ textarea.text {
}
}
// forms - specific
// ====================
// forms - fields - not editable
.field.is-not-editable
{
&
label
.is-focused
{
color
:
$gray-d2
;
}
label
,
input
,
textarea
{
pointer-events
:
none
;
}
}
// ====================
// field with error
.field.error
{
input
,
textarea
{
border-color
:
$red
;
}
}
// ====================
// forms - additional UI
form
{
.note
{
@include
box-sizing
(
border-box
);
.title
{
}
.copy
{
}
// note with actions
&
.has-actions
{
@include
clearfix
();
.title
{
}
.copy
{
}
.list-actions
{
}
}
}
.note-promotion
{
}
}
// ====================
// forms - grandfathered
input
.search
{
padding
:
6px
15px
8px
30px
;
@include
box-sizing
(
border-box
);
...
...
@@ -73,4 +138,4 @@ code {
background-color
:
#edf1f5
;
@include
box-shadow
(
0
1px
2px
rgba
(
0
,
0
,
0
,
0
.1
)
inset
);
font-family
:
Monaco
,
monospace
;
}
\ No newline at end of file
}
cms/static/sass/views/_account.scss
View file @
01e15c1e
...
...
@@ -4,7 +4,7 @@
body
.signup
,
body
.signin
{
.wrapper-content
{
margin
:
0
;
margin
:
(
$baseline
*
1
.5
)
0
0
0
;
padding
:
0
$baseline
;
position
:
relative
;
width
:
100%
;
...
...
@@ -18,7 +18,7 @@ body.signup, body.signin {
width
:
flex-grid
(
12
);
margin
:
0
auto
;
color
:
$gray-d2
;
header
{
position
:
relative
;
margin-bottom
:
$baseline
;
...
...
@@ -121,7 +121,7 @@ body.signup, body.signin {
@include
font-size
(
16
);
height
:
100%
;
width
:
100%
;
padding
:
(
$baseline
/
2
);
padding
:
(
$baseline
/
2
);
&
.long
{
width
:
100%
;
...
...
@@ -136,15 +136,15 @@ body.signup, body.signin {
}
:-moz-placeholder
{
color
:
$gray-l3
;
color
:
$gray-l3
;
}
::-moz-placeholder
{
color
:
$gray-l3
;
color
:
$gray-l3
;
}
:-ms-input-placeholder
{
color
:
$gray-l3
;
:-ms-input-placeholder
{
color
:
$gray-l3
;
}
&
:focus
{
...
...
cms/static/sass/views/_settings.scss
View file @
01e15c1e
...
...
@@ -147,7 +147,7 @@ body.course.settings {
}
label
{
@
include
font-size
(
14
)
;
@
extend
.t-copy-sub1
;
@include
transition
(
color
,
0
.15s
,
ease-in-out
);
margin
:
0
0
(
$baseline
/
4
)
0
;
font-weight
:
400
;
...
...
@@ -161,7 +161,7 @@ body.course.settings {
@include
placeholder
(
$gray-l4
);
@include
font-size
(
16
);
@include
size
(
100%
,
100%
);
padding
:
(
$baseline
/
2
);
padding
:
(
$baseline
/
2
);
&
.long
{
}
...
...
@@ -212,7 +212,7 @@ body.course.settings {
padding
:
$baseline
;
&
:last-child
{
padding-bottom
:
$baseline
;
padding-bottom
:
$baseline
;
}
.actions
{
...
...
@@ -238,33 +238,36 @@ body.course.settings {
}
}
// not editable fields
.field.is-not-editable
{
&
label
.is-focused
{
color
:
$gray-d2
;
}
}
// field with error
.field.error
{
input
,
textarea
{
border-color
:
$red
;
}
}
// specific fields - basic
&
.basic
{
.list-input
{
@include
clearfix
();
padding
:
0
(
$baseline
/
2
);
.field
{
margin-bottom
:
0
;
}
}
// course details that should appear more like content than elements to change
.field.is-not-editable
{
label
{
}
input
,
textarea
{
@extend
.t-copy-lead1
;
@include
box-shadow
(
none
);
border
:
none
;
background
:
none
;
padding
:
0
;
margin
:
0
;
font-weight
:
600
;
}
}
#field-course-organization
{
float
:
left
;
width
:
flex-grid
(
2
,
9
);
...
...
@@ -281,6 +284,58 @@ body.course.settings {
float
:
left
;
width
:
flex-grid
(
5
,
9
);
}
// course link note
.note-promotion-courseURL
{
@include
box-shadow
(
0
2px
1px
$shadow-l1
);
@include
border-radius
((
$baseline
/
5
));
margin-top
:
(
$baseline
*
1
.5
);
border
:
1px
solid
$gray-l2
;
padding
:
(
$baseline
/
2
)
0
0
0
;
.title
{
@extend
.t-copy-sub1
;
margin
:
0
0
(
$baseline
/
10
)
0
;
padding
:
0
(
$baseline
/
2
);
.tip
{
display
:
inline
;
margin-left
:
(
$baseline
/
4
);
}
}
.copy
{
padding
:
0
(
$baseline
/
2
)
(
$baseline
/
2
)
(
$baseline
/
2
);
.link-courseURL
{
@extend
.t-copy-lead1
;
&
:hover
{
}
}
}
.list-actions
{
@include
box-shadow
(
inset
0
1px
1px
$shadow-l1
);
border-top
:
1px
solid
$gray-l2
;
padding
:
(
$baseline
/
2
);
background
:
$gray-l5
;
.action-primary
{
@include
blue-button
();
@include
font-size
(
13
);
font-weight
:
600
;
.icon
{
@extend
.t-icon
;
@include
font-size
(
16
);
display
:
inline-block
;
vertical-align
:
middle
;
}
}
}
}
}
// specific fields - schedule
...
...
@@ -322,7 +377,7 @@ body.course.settings {
}
}
}
// specific fields - overview
#field-course-overview
{
...
...
@@ -468,7 +523,7 @@ body.course.settings {
}
}
}
.grade-specific-bar
{
height
:
50px
!
important
;
}
...
...
@@ -479,7 +534,7 @@ body.course.settings {
li
{
position
:
absolute
;
top
:
0
;
height
:
50px
;
height
:
50px
;
text-align
:
right
;
@include
border-radius
(
2px
);
...
...
@@ -600,8 +655,8 @@ body.course.settings {
}
#field-course-grading-assignment-shortname
,
#field-course-grading-assignment-totalassignments
,
#field-course-grading-assignment-gradeweight
,
#field-course-grading-assignment-totalassignments
,
#field-course-grading-assignment-gradeweight
,
#field-course-grading-assignment-droppable
{
width
:
flex-grid
(
2
,
6
);
}
...
...
@@ -734,4 +789,4 @@ body.course.settings {
.content-supplementary
{
width
:
flex-grid
(
3
,
12
);
}
}
\ No newline at end of file
}
cms/templates/settings.html
View file @
01e15c1e
This diff is collapsed.
Click to expand it.
cms/templates/widgets/metadata-only-edit.html
0 → 100644
View file @
01e15c1e
<
%
include
file=
"metadata-edit.html"
/>
cms/templates/widgets/source-edit.html
View file @
01e15c1e
...
...
@@ -12,7 +12,7 @@
<form
id=
"hls-form"
enctype=
"multipart/form-data"
>
<section
class=
"source-edit"
>
<textarea
name=
""
data-metadata-name=
"source_code"
class=
"source-edit-box hls-data"
rows=
"8"
cols=
"40"
>
${
metadata
['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']|h}
</textarea>
</section>
<div
class=
"submit"
>
<button
type=
"reset"
class=
"hls-compile"
>
Save
&
Compile to edX XML
</button>
...
...
common/lib/xmodule/xmodule/capa_module.py
View file @
01e15c1e
...
...
@@ -99,6 +99,7 @@ class CapaFields(object):
seed
=
StringyInteger
(
help
=
"Random seed for this student"
,
scope
=
Scope
.
user_state
)
weight
=
StringyFloat
(
help
=
"How much to weight this problem by"
,
scope
=
Scope
.
settings
)
markdown
=
String
(
help
=
"Markdown source of this module"
,
scope
=
Scope
.
settings
)
source_code
=
String
(
help
=
"Source code for LaTeX and Word problems. This feature is not well-supported."
,
scope
=
Scope
.
settings
)
class
CapaModule
(
CapaFields
,
XModule
):
...
...
common/lib/xmodule/xmodule/discussion_module.py
View file @
01e15c1e
...
...
@@ -3,6 +3,7 @@ from pkg_resources import resource_string, resource_listdir
from
xmodule.x_module
import
XModule
from
xmodule.raw_module
import
RawDescriptor
from
xmodule.editing_module
import
MetadataOnlyEditingDescriptor
from
xblock.core
import
String
,
Scope
...
...
@@ -28,7 +29,7 @@ class DiscussionModule(DiscussionFields, XModule):
return
self
.
system
.
render_template
(
'discussion/_discussion_module.html'
,
context
)
class
DiscussionDescriptor
(
DiscussionFields
,
RawDescriptor
):
class
DiscussionDescriptor
(
DiscussionFields
,
MetadataOnlyEditingDescriptor
,
RawDescriptor
):
module_class
=
DiscussionModule
template_dir_name
=
"discussion"
...
...
common/lib/xmodule/xmodule/editing_module.py
View file @
01e15c1e
...
...
@@ -41,6 +41,18 @@ class XMLEditingDescriptor(EditingDescriptor):
js_module_name
=
"XMLEditingDescriptor"
class
MetadataOnlyEditingDescriptor
(
EditingDescriptor
):
"""
Module which only provides an editing interface for the metadata, it does
not expose a UI for editing the module data
"""
js
=
{
'coffee'
:
[
resource_string
(
__name__
,
'js/src/raw/edit/metadata-only.coffee'
)]}
js_module_name
=
"MetadataOnlyEditingDescriptor"
mako_template
=
"widgets/metadata-only-edit.html"
class
JSONEditingDescriptor
(
EditingDescriptor
):
"""
Module that provides a raw editing view of its data as XML. It does not perform
...
...
common/lib/xmodule/xmodule/html_module.py
View file @
01e15c1e
...
...
@@ -118,8 +118,8 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor):
with
system
.
resources_fs
.
open
(
filepath
)
as
file
:
html
=
file
.
read
()
.
decode
(
'utf-8'
)
# Log a warning if we can't parse the file, but don't error
if
not
check_html
(
html
):
msg
=
"Couldn't parse html in {0}
."
.
format
(
filepath
)
if
not
check_html
(
html
)
and
len
(
html
)
>
0
:
msg
=
"Couldn't parse html in {0}
, content = {1}"
.
format
(
filepath
,
html
)
log
.
warning
(
msg
)
system
.
error_tracker
(
"Warning: "
+
msg
)
...
...
@@ -156,7 +156,8 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor):
resource_fs
.
makedir
(
os
.
path
.
dirname
(
filepath
),
recursive
=
True
,
allow_recreate
=
True
)
with
resource_fs
.
open
(
filepath
,
'w'
)
as
file
:
file
.
write
(
self
.
data
.
encode
(
'utf-8'
))
html_data
=
self
.
data
.
encode
(
'utf-8'
)
file
.
write
(
html_data
)
# write out the relative name
relname
=
path
(
pathname
)
.
basename
()
...
...
common/lib/xmodule/xmodule/js/src/raw/edit/metadata-only.coffee
0 → 100644
View file @
01e15c1e
class
@
MetadataOnlyEditingDescriptor
extends
XModule
.
Descriptor
constructor
:
(
@
element
)
->
save
:
->
data
:
null
common/lib/xmodule/xmodule/modulestore/__init__.py
View file @
01e15c1e
...
...
@@ -252,7 +252,6 @@ class Location(_LocationBase):
def
__repr__
(
self
):
return
"Location
%
s"
%
repr
(
tuple
(
self
))
@property
def
course_id
(
self
):
"""Return the ID of the Course that this item belongs to by looking
...
...
@@ -414,7 +413,6 @@ class ModuleStore(object):
return
courses
class
ModuleStoreBase
(
ModuleStore
):
'''
Implement interface functionality that can be shared.
...
...
@@ -425,6 +423,7 @@ class ModuleStoreBase(ModuleStore):
'''
self
.
_location_errors
=
{}
# location -> ErrorLog
self
.
metadata_inheritance_cache
=
None
self
.
modulestore_update_signal
=
None
# can be set by runtime to route notifications of datastore changes
def
_get_errorlog
(
self
,
location
):
"""
...
...
common/lib/xmodule/xmodule/modulestore/draft.py
View file @
01e15c1e
...
...
@@ -3,7 +3,6 @@ from datetime import datetime
from
.
import
ModuleStoreBase
,
Location
,
namedtuple_to_son
from
.exceptions
import
ItemNotFoundError
from
.inheritance
import
own_metadata
import
logging
DRAFT
=
'draft'
...
...
@@ -107,7 +106,7 @@ class DraftModuleStore(ModuleStoreBase):
"""
return
wrap_draft
(
super
(
DraftModuleStore
,
self
)
.
clone_item
(
source
,
as_draft
(
location
)))
def
update_item
(
self
,
location
,
data
):
def
update_item
(
self
,
location
,
data
,
allow_not_found
=
False
):
"""
Set the data in the item specified by the location to
data
...
...
@@ -116,9 +115,13 @@ class DraftModuleStore(ModuleStoreBase):
data: A nested dictionary of problem data
"""
draft_loc
=
as_draft
(
location
)
draft_item
=
self
.
get_item
(
location
)
if
not
getattr
(
draft_item
,
'is_draft'
,
False
):
self
.
clone_item
(
location
,
draft_loc
)
try
:
draft_item
=
self
.
get_item
(
location
)
if
not
getattr
(
draft_item
,
'is_draft'
,
False
):
self
.
clone_item
(
location
,
draft_loc
)
except
ItemNotFoundError
,
e
:
if
not
allow_not_found
:
raise
e
return
super
(
DraftModuleStore
,
self
)
.
update_item
(
draft_loc
,
data
)
...
...
@@ -164,7 +167,6 @@ class DraftModuleStore(ModuleStoreBase):
"""
return
super
(
DraftModuleStore
,
self
)
.
delete_item
(
as_draft
(
location
))
def
get_parent_locations
(
self
,
location
,
course_id
):
'''Find all locations that are the parents of this location. Needed
for path_to_location().
...
...
@@ -178,6 +180,7 @@ class DraftModuleStore(ModuleStoreBase):
Save a current draft to the underlying modulestore
"""
draft
=
self
.
get_item
(
location
)
draft
.
cms
.
published_date
=
datetime
.
utcnow
()
draft
.
cms
.
published_by
=
published_by_id
super
(
DraftModuleStore
,
self
)
.
update_item
(
location
,
draft
.
_model_data
.
_kvs
.
_data
)
...
...
@@ -221,6 +224,6 @@ class DraftModuleStore(ModuleStoreBase):
# convert the dict - which is used for look ups - back into a list
for
key
,
value
in
to_process_dict
.
iteritems
():
queried_children
.
append
(
value
)
queried_children
.
append
(
value
)
return
queried_children
common/lib/xmodule/xmodule/modulestore/mongo.py
View file @
01e15c1e
...
...
@@ -9,6 +9,7 @@ from itertools import repeat
from
path
import
path
from
datetime
import
datetime
from
operator
import
attrgetter
from
uuid
import
uuid4
from
importlib
import
import_module
from
xmodule.errortracker
import
null_error_tracker
,
exc_info_to_str
...
...
@@ -30,6 +31,10 @@ log = logging.getLogger(__name__)
# there is only one revision for each item. Once we start versioning inside the CMS,
# that assumption will have to change
def
get_course_id_no_run
(
location
):
'''
'''
return
"/"
.
join
([
location
.
org
,
location
.
course
])
class
MongoKeyValueStore
(
KeyValueStore
):
"""
...
...
@@ -333,7 +338,7 @@ class MongoModuleStore(ModuleStoreBase):
'''
key
=
metadata_cache_key
(
location
)
tree
=
{}
if
not
force_refresh
:
# see if we are first in the request cache (if present)
if
self
.
request_cache
is
not
None
and
key
in
self
.
request_cache
.
data
.
get
(
'metadata_inheritance'
,
{}):
...
...
@@ -348,7 +353,7 @@ class MongoModuleStore(ModuleStoreBase):
if
not
tree
:
# if not in subsystem, or we are on force refresh, then we have to compute
tree
=
self
.
compute_metadata_inheritance_tree
(
location
)
# now write out computed tree to caching subsystem (e.g. memcached), if available
if
self
.
metadata_inheritance_cache_subsystem
is
not
None
:
self
.
metadata_inheritance_cache_subsystem
.
set
(
key
,
tree
)
...
...
@@ -541,8 +546,15 @@ class MongoModuleStore(ModuleStoreBase):
Clone a new item that is a copy of the item at the location `source`
and writes it to `location`
"""
item
=
None
try
:
source_item
=
self
.
collection
.
find_one
(
location_to_query
(
source
))
# allow for some programmatically generated substitutions in metadata, e.g. Discussion_id's should be auto-generated
for
key
in
source_item
[
'metadata'
]
.
keys
():
if
source_item
[
'metadata'
][
key
]
==
'$$GUID$$'
:
source_item
[
'metadata'
][
key
]
=
uuid4
()
.
hex
source_item
[
'_id'
]
=
Location
(
location
)
.
dict
()
self
.
collection
.
insert
(
source_item
,
...
...
@@ -566,12 +578,19 @@ class MongoModuleStore(ModuleStoreBase):
course
.
tabs
=
existing_tabs
self
.
update_metadata
(
course
.
location
,
course
.
_model_data
.
_kvs
.
_metadata
)
return
item
except
pymongo
.
errors
.
DuplicateKeyError
:
raise
DuplicateItemError
(
location
)
# recompute (and update) the metadata inheritance tree which is cached
self
.
refresh_cached_metadata_inheritance_tree
(
Location
(
location
))
self
.
fire_updated_modulestore_signal
(
get_course_id_no_run
(
Location
(
location
)),
Location
(
location
))
return
item
def
fire_updated_modulestore_signal
(
self
,
course_id
,
location
):
if
self
.
modulestore_update_signal
is
not
None
:
self
.
modulestore_update_signal
.
send
(
self
,
modulestore
=
self
,
course_id
=
course_id
,
location
=
location
)
def
get_course_for_item
(
self
,
location
,
depth
=
0
):
'''
...
...
@@ -643,6 +662,8 @@ class MongoModuleStore(ModuleStoreBase):
self
.
_update_single_item
(
location
,
{
'definition.children'
:
children
})
# recompute (and update) the metadata inheritance tree which is cached
self
.
refresh_cached_metadata_inheritance_tree
(
Location
(
location
))
# fire signal that we've written to DB
self
.
fire_updated_modulestore_signal
(
get_course_id_no_run
(
Location
(
location
)),
Location
(
location
))
def
update_metadata
(
self
,
location
,
metadata
):
"""
...
...
@@ -669,6 +690,7 @@ class MongoModuleStore(ModuleStoreBase):
self
.
_update_single_item
(
location
,
{
'metadata'
:
metadata
})
# recompute (and update) the metadata inheritance tree which is cached
self
.
refresh_cached_metadata_inheritance_tree
(
loc
)
self
.
fire_updated_modulestore_signal
(
get_course_id_no_run
(
Location
(
location
)),
Location
(
location
))
def
delete_item
(
self
,
location
):
"""
...
...
@@ -692,6 +714,7 @@ class MongoModuleStore(ModuleStoreBase):
safe
=
self
.
collection
.
safe
)
# recompute (and update) the metadata inheritance tree which is cached
self
.
refresh_cached_metadata_inheritance_tree
(
Location
(
location
))
self
.
fire_updated_modulestore_signal
(
get_course_id_no_run
(
Location
(
location
)),
Location
(
location
))
def
get_parent_locations
(
self
,
location
,
course_id
):
'''Find all locations that are the parents of this location in this
...
...
common/lib/xmodule/xmodule/modulestore/xml_exporter.py
View file @
01e15c1e
import
logging
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.inheritance
import
own_metadata
from
fs.osfs
import
OSFS
from
json
import
dumps
def
export_to_xml
(
modulestore
,
contentstore
,
course_location
,
root_dir
,
course_dir
):
def
export_to_xml
(
modulestore
,
contentstore
,
course_location
,
root_dir
,
course_dir
,
draft_modulestore
=
None
):
course
=
modulestore
.
get_item
(
course_location
)
...
...
@@ -40,6 +39,24 @@ def export_to_xml(modulestore, contentstore, course_location, root_dir, course_d
policy
=
{
'course/'
+
course
.
location
.
name
:
own_metadata
(
course
)}
course_policy
.
write
(
dumps
(
policy
))
# export draft content
# NOTE: this code assumes that verticals are the top most draftable container
# should we change the application, then this assumption will no longer
# be valid
if
draft_modulestore
is
not
None
:
draft_verticals
=
draft_modulestore
.
get_items
([
None
,
course_location
.
org
,
course_location
.
course
,
'vertical'
,
None
,
'draft'
])
if
len
(
draft_verticals
)
>
0
:
draft_course_dir
=
export_fs
.
makeopendir
(
'drafts'
)
for
draft_vertical
in
draft_verticals
:
parent_locs
=
draft_modulestore
.
get_parent_locations
(
draft_vertical
.
location
,
course
.
location
.
course_id
)
logging
.
debug
(
'parent_locs = {0}'
.
format
(
parent_locs
))
draft_vertical
.
xml_attributes
[
'parent_sequential_url'
]
=
Location
(
parent_locs
[
0
])
.
url
()
sequential
=
modulestore
.
get_item
(
Location
(
parent_locs
[
0
]))
index
=
sequential
.
children
.
index
(
draft_vertical
.
location
.
url
())
draft_vertical
.
xml_attributes
[
'index_in_children_list'
]
=
str
(
index
)
draft_vertical
.
export_to_xml
(
draft_course_dir
)
def
export_extra_content
(
export_fs
,
modulestore
,
course_location
,
category_type
,
dirname
,
file_suffix
=
''
):
query_loc
=
Location
(
'i4x'
,
course_location
.
org
,
course_location
.
course
,
category_type
,
None
)
...
...
common/lib/xmodule/xmodule/modulestore/xml_importer.py
View file @
01e15c1e
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/raw_module.py
View file @
01e15c1e
...
...
@@ -29,6 +29,6 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor):
line
,
offset
=
err
.
position
msg
=
(
"Unable to create xml for problem {loc}. "
"Context: '{context}'"
.
format
(
context
=
lines
[
line
-
1
][
offset
-
40
:
offset
+
40
],
loc
=
self
.
location
))
context
=
lines
[
line
-
1
][
offset
-
40
:
offset
+
40
],
loc
=
self
.
location
))
raise
Exception
,
msg
,
sys
.
exc_info
()[
2
]
common/lib/xmodule/xmodule/templates/discussion/default.yaml
View file @
01e15c1e
...
...
@@ -2,8 +2,8 @@
metadata
:
display_name
:
Discussion Tag
for
:
Topic-Level Student-Visible Label
id
:
6002x_group_discussion_by_this
id
:
$$GUID$$
discussion_category
:
Week 1
data
:
|
<discussion
for="Topic-Level Student-Visible Label" id="6002x_group_discussion_by_this" discussion_category="Week 1"
/>
<discussion />
children
:
[]
common/lib/xmodule/xmodule/templates/html/latex_html.yaml
View file @
01e15c1e
---
metadata
:
display_name
:
E-text Written in LaTeX
source_processor_url
:
https://qisx.mit.edu:5443/latex2edx
display_name
:
E-text Written in LaTeX
source_code
:
|
\subsection{Example of E-text in LaTeX}
...
...
common/lib/xmodule/xmodule/templates/problem/latex_problem.yaml
View file @
01e15c1e
---
metadata
:
display_name
:
Problem Written in LaTeX
source_processor_url
:
https://studio-input-filter.mitx.mit.edu/latex2edx
display_name
:
Problem Written in LaTeX
source_code
:
|
% Nearly any kind of edX problem can be authored using Latex as
% the source language. Write latex as usual, including equations. The
...
...
common/lib/xmodule/xmodule/templates/problem/problem_with_hint.yaml
View file @
01e15c1e
---
metadata
:
display_name
:
Problem with Adaptive Hint
source_processor_url
:
https://qisx.mit.edu:5443/latex2edx
source_code
:
|
\subsection{Problem With Adaptive Hint}
...
...
common/lib/xmodule/xmodule/x_module.py
View file @
01e15c1e
...
...
@@ -340,7 +340,9 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
# 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'
,
'xml_attributes'
]
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
# be equal
...
...
common/lib/xmodule/xmodule/xml_module.py
View file @
01e15c1e
...
...
@@ -110,8 +110,7 @@ class XmlDescriptor(XModuleDescriptor):
'name'
,
'slug'
)
metadata_to_strip
=
(
'data_dir'
,
# cdodge: @TODO: We need to figure out a way to export out 'tabs' and 'grading_policy' which is on the course
'tabs'
,
'grading_policy'
,
'is_draft'
,
'published_by'
,
'published_date'
,
'tabs'
,
'grading_policy'
,
'published_by'
,
'published_date'
,
'discussion_blackouts'
,
'testcenter_info'
,
# VS[compat] -- remove the below attrs once everything is in the CMS
'course'
,
'org'
,
'url_name'
,
'filename'
,
...
...
@@ -135,7 +134,7 @@ class XmlDescriptor(XModuleDescriptor):
'graded'
:
bool_map
,
'hide_progress_tab'
:
bool_map
,
'allow_anonymous'
:
bool_map
,
'allow_anonymous_to_peers'
:
bool_map
'allow_anonymous_to_peers'
:
bool_map
,
}
...
...
lms/djangoapps/courseware/tests/test_login.py
View file @
01e15c1e
from
django.test
import
TestCase
from
django.test.client
import
Client
from
django.core.urlresolvers
import
reverse
from
django.contrib.auth.models
import
User
from
student.models
import
Registration
,
UserProfile
from
factories
import
UserFactory
,
RegistrationFactory
,
UserProfileFactory
import
json
class
LoginTest
(
TestCase
):
'''
Test student.views.login_user() view
'''
def
setUp
(
self
):
# Create one user and save it to the database
self
.
user
=
User
.
objects
.
create_user
(
'test'
,
'test@edx.org'
,
'test_password
'
)
self
.
user
.
is_active
=
True
self
.
user
=
User
Factory
.
build
(
username
=
'test'
,
email
=
'test@edx.org
'
)
self
.
user
.
set_password
(
'test_password'
)
self
.
user
.
save
()
# Create a registration for the user
Registration
()
.
register
(
self
.
user
)
registration
=
RegistrationFactory
(
user
=
self
.
user
)
registration
.
register
(
self
.
user
)
registration
.
activate
()
# Create a profile for the user
UserProfile
(
user
=
self
.
user
)
.
save
(
)
UserProfile
Factory
(
user
=
self
.
user
)
# Create the test client
self
.
client
=
Client
()
...
...
@@ -42,19 +43,17 @@ class LoginTest(TestCase):
response
=
self
.
_login_response
(
unicode_email
,
'test_password'
)
self
.
_assert_response
(
response
,
success
=
True
)
def
test_login_fail_no_user_exists
(
self
):
response
=
self
.
_login_response
(
'not_a_user@edx.org'
,
'test_password'
)
self
.
_assert_response
(
response
,
success
=
False
,
value
=
'Email or password is incorrect'
)
self
.
_assert_response
(
response
,
success
=
False
,
value
=
'Email or password is incorrect'
)
def
test_login_fail_wrong_password
(
self
):
response
=
self
.
_login_response
(
'test@edx.org'
,
'wrong_password'
)
self
.
_assert_response
(
response
,
success
=
False
,
value
=
'Email or password is incorrect'
)
self
.
_assert_response
(
response
,
success
=
False
,
value
=
'Email or password is incorrect'
)
def
test_login_not_activated
(
self
):
# De-activate the user
self
.
user
.
is_active
=
False
self
.
user
.
save
()
...
...
@@ -62,8 +61,7 @@ class LoginTest(TestCase):
# Should now be unable to login
response
=
self
.
_login_response
(
'test@edx.org'
,
'test_password'
)
self
.
_assert_response
(
response
,
success
=
False
,
value
=
"This account has not been activated"
)
value
=
"This account has not been activated"
)
def
test_login_unicode_email
(
self
):
unicode_email
=
u'test@edx.org'
+
unichr
(
40960
)
...
...
@@ -95,13 +93,13 @@ class LoginTest(TestCase):
try
:
response_dict
=
json
.
loads
(
response
.
content
)
except
ValueError
:
self
.
fail
(
"Could not parse response content as JSON:
%
s"
%
str
(
response
.
content
))
self
.
fail
(
"Could not parse response content as JSON:
%
s"
%
str
(
response
.
content
))
if
success
is
not
None
:
self
.
assertEqual
(
response_dict
[
'success'
],
success
)
if
value
is
not
None
:
msg
=
(
"'
%
s' did not contain '
%
s'"
%
(
str
(
response_dict
[
'value'
]),
str
(
value
)))
msg
=
(
"'
%
s' did not contain '
%
s'"
%
(
str
(
response_dict
[
'value'
]),
str
(
value
)))
self
.
assertTrue
(
value
in
response_dict
[
'value'
],
msg
)
lms/djangoapps/django_comment_client/management/commands/seed_permissions_roles.py
View file @
01e15c1e
from
django.core.management.base
import
BaseCommand
,
CommandError
from
django_comment_client.models
import
Permission
,
Role
from
django_comment_client.models
import
Role
class
Command
(
BaseCommand
):
...
...
@@ -12,18 +12,19 @@ class Command(BaseCommand):
if
len
(
args
)
>
1
:
raise
CommandError
(
"Too many arguments"
)
course_id
=
args
[
0
]
administrator_role
=
Role
.
objects
.
get_or_create
(
name
=
"Administrator"
,
course_id
=
course_id
)[
0
]
moderator_role
=
Role
.
objects
.
get_or_create
(
name
=
"Moderator"
,
course_id
=
course_id
)[
0
]
community_ta_role
=
Role
.
objects
.
get_or_create
(
name
=
"Community TA"
,
course_id
=
course_id
)[
0
]
student_role
=
Role
.
objects
.
get_or_create
(
name
=
"Student"
,
course_id
=
course_id
)[
0
]
for
per
in
[
"vote"
,
"update_thread"
,
"follow_thread"
,
"unfollow_thread"
,
"update_comment"
,
"create_sub_comment"
,
"unvote"
,
"create_thread"
,
"follow_commentable"
,
"unfollow_commentable"
,
"create_comment"
,
]:
"update_comment"
,
"create_sub_comment"
,
"unvote"
,
"create_thread"
,
"follow_commentable"
,
"unfollow_commentable"
,
"create_comment"
,
]:
student_role
.
add_permission
(
per
)
for
per
in
[
"edit_content"
,
"delete_thread"
,
"openclose_thread"
,
"endorse_comment"
,
"delete_comment"
,
"see_all_cohorts"
]:
"endorse_comment"
,
"delete_comment"
,
"see_all_cohorts"
]:
moderator_role
.
add_permission
(
per
)
for
per
in
[
"manage_moderator"
]:
...
...
lms/djangoapps/django_comment_client/tests/mock_cs_server/__init__.py
0 → 100644
View file @
01e15c1e
lms/djangoapps/django_comment_client/tests/mock_cs_server/mock_cs_server.py
0 → 100644
View file @
01e15c1e
from
BaseHTTPServer
import
HTTPServer
,
BaseHTTPRequestHandler
import
json
from
logging
import
getLogger
logger
=
getLogger
(
__name__
)
class
MockCommentServiceRequestHandler
(
BaseHTTPRequestHandler
):
'''
A handler for Comment Service POST requests.
'''
protocol
=
"HTTP/1.0"
def
do_POST
(
self
):
'''
Handle a POST request from the client
Used by the APIs for comment threads, commentables, comments,
subscriptions, commentables, users
'''
# Retrieve the POST data into a dict.
# It should have been sent in json format
length
=
int
(
self
.
headers
.
getheader
(
'content-length'
))
data_string
=
self
.
rfile
.
read
(
length
)
post_dict
=
json
.
loads
(
data_string
)
# Log the request
logger
.
debug
(
"Comment Service received POST request
%
s to path
%
s"
%
(
json
.
dumps
(
post_dict
),
self
.
path
))
# Every good post has at least an API key
if
'api_key'
in
post_dict
:
response
=
self
.
server
.
_response_str
# Log the response
logger
.
debug
(
"Comment Service: sending response
%
s"
%
json
.
dumps
(
response
))
# Send a response back to the client
self
.
send_response
(
200
)
self
.
send_header
(
'Content-type'
,
'application/json'
)
self
.
end_headers
()
self
.
wfile
.
write
(
response
)
else
:
# Respond with failure
self
.
send_response
(
500
,
'Bad Request: does not contain API key'
)
self
.
send_header
(
'Content-type'
,
'text/plain'
)
self
.
end_headers
()
return
False
class
MockCommentServiceServer
(
HTTPServer
):
'''
A mock Comment Service server that responds
to POST requests to localhost.
'''
def
__init__
(
self
,
port_num
,
response
=
{
'username'
:
'new'
,
'external_id'
:
1
}):
'''
Initialize the mock Comment Service server instance.
*port_num* is the localhost port to listen to
*response* is a dictionary that will be JSON-serialized
and sent in response to comment service requests.
'''
self
.
_response_str
=
json
.
dumps
(
response
)
handler
=
MockCommentServiceRequestHandler
address
=
(
''
,
port_num
)
HTTPServer
.
__init__
(
self
,
address
,
handler
)
def
shutdown
(
self
):
'''
Stop the server and free up the port
'''
# First call superclass shutdown()
HTTPServer
.
shutdown
(
self
)
# We also need to manually close the socket
self
.
socket
.
close
()
lms/djangoapps/django_comment_client/tests/mock_cs_server/test_mock_cs_server.py
0 → 100644
View file @
01e15c1e
import
unittest
import
threading
import
json
import
urllib2
from
mock_cs_server
import
MockCommentServiceServer
from
nose.plugins.skip
import
SkipTest
class
MockCommentServiceServerTest
(
unittest
.
TestCase
):
'''
A mock version of the Comment Service server that listens on a local
port and responds with pre-defined grade messages.
'''
def
setUp
(
self
):
# This is a test of the test setup,
# so it does not need to run as part of the unit test suite
# You can re-enable it by commenting out the line below
raise
SkipTest
# Create the server
server_port
=
4567
self
.
server_url
=
'http://127.0.0.1:
%
d'
%
server_port
# Start up the server and tell it that by default it should
# return this as its json response
self
.
expected_response
=
{
'username'
:
'user100'
,
'external_id'
:
'4'
}
self
.
server
=
MockCommentServiceServer
(
port_num
=
server_port
,
response
=
self
.
expected_response
)
# Start the server in a separate daemon thread
server_thread
=
threading
.
Thread
(
target
=
self
.
server
.
serve_forever
)
server_thread
.
daemon
=
True
server_thread
.
start
()
def
tearDown
(
self
):
# Stop the server, freeing up the port
self
.
server
.
shutdown
()
def
test_new_user_request
(
self
):
"""
Test the mock comment service using an example
of how you would create a new user
"""
# Send a request
values
=
{
'username'
:
u'user100'
,
'api_key'
:
'TEST_API_KEY'
,
'external_id'
:
'4'
,
'email'
:
u'user100@edx.org'
}
data
=
json
.
dumps
(
values
)
headers
=
{
'Content-Type'
:
'application/json'
,
'Content-Length'
:
len
(
data
)}
req
=
urllib2
.
Request
(
self
.
server_url
+
'/api/v1/users/4'
,
data
,
headers
)
# Send the request to the mock cs server
response
=
urllib2
.
urlopen
(
req
)
# Receive the reply from the mock cs server
response_dict
=
json
.
loads
(
response
.
read
())
# You should have received the response specified in the setup above
self
.
assertEqual
(
response_dict
,
self
.
expected_response
)
lms/djangoapps/django_comment_client/utils.py
View file @
01e15c1e
...
...
@@ -146,28 +146,16 @@ def sort_map_entries(category_map):
def
initialize_discussion_info
(
course
):
global
_DISCUSSIONINFO
# only cache in-memory discussion information for 10 minutes
# this is because we need a short-term hack fix for
# mongo-backed courseware whereby new discussion modules can be added
# without LMS service restart
if
_DISCUSSIONINFO
[
course
.
id
]:
timestamp
=
_DISCUSSIONINFO
[
course
.
id
]
.
get
(
'timestamp'
,
datetime
.
now
())
age
=
datetime
.
now
()
-
timestamp
# expire every 5 minutes
if
age
.
seconds
<
300
:
return
course_id
=
course
.
id
discussion_id_map
=
{}
unexpanded_category_map
=
defaultdict
(
list
)
# get all discussion models within this course_id
all_modules
=
modulestore
()
.
get_items
([
'i4x'
,
course
.
location
.
org
,
course
.
location
.
course
,
'discussion'
,
None
],
course_id
=
course_id
)
all_modules
=
modulestore
()
.
get_items
([
'i4x'
,
course
.
location
.
org
,
course
.
location
.
course
,
'discussion'
,
None
],
course_id
=
course_id
)
for
module
in
all_modules
:
skip_module
=
False
...
...
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