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
44b09dd8
Commit
44b09dd8
authored
Jun 18, 2015
by
Fred Smith
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #454 from edx-solutions/rc/2015-06-15
Rc/2015 06 15
parents
b651789c
4ae4f7c9
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
154 additions
and
15 deletions
+154
-15
lms/djangoapps/api_manager/users/tests.py
+36
-0
lms/djangoapps/api_manager/users/views.py
+19
-0
lms/djangoapps/django_comment_client/management/commands/export_discussion_participation.py
+78
-8
lms/djangoapps/django_comment_client/tests/management/test_export_discussion_participation.py
+0
-0
lms/djangoapps/static_template_view/views.py
+2
-2
lms/lib/comment_client/user.py
+6
-4
lms/templates/static_templates/404-plain.html
+5
-0
lms/templates/static_templates/server-error-plain.html
+7
-0
requirements/edx/custom.txt
+1
-1
No files found.
lms/djangoapps/api_manager/users/tests.py
View file @
44b09dd8
...
@@ -418,6 +418,40 @@ class UsersApiTests(ModuleStoreTestCase):
...
@@ -418,6 +418,40 @@ class UsersApiTests(ModuleStoreTestCase):
self
.
assertEqual
(
response
.
data
[
'is_active'
],
False
)
self
.
assertEqual
(
response
.
data
[
'is_active'
],
False
)
self
.
assertIsNotNone
(
response
.
data
[
'created'
])
self
.
assertIsNotNone
(
response
.
data
[
'created'
])
def
test_user_detail_invalid_email
(
self
):
test_uri
=
'{}/{}'
.
format
(
self
.
users_base_uri
,
self
.
user
.
id
)
data
=
{
'email'
:
'fail'
}
response
=
self
.
do_post
(
test_uri
,
data
)
self
.
assertEqual
(
response
.
status_code
,
400
)
self
.
assertIn
(
'Invalid email address'
,
response
.
content
)
def
test_user_detail_duplicate_email
(
self
):
user2
=
UserFactory
()
test_uri
=
'{}/{}'
.
format
(
self
.
users_base_uri
,
self
.
user
.
id
)
test_uri2
=
'{}/{}'
.
format
(
self
.
users_base_uri
,
user2
.
id
)
data
=
{
'email'
:
self
.
test_email
}
response
=
self
.
do_post
(
test_uri
,
data
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response
=
self
.
do_post
(
test_uri2
,
data
)
self
.
assertEqual
(
response
.
status_code
,
400
)
self
.
assertIn
(
'A user with that email address already exists.'
,
response
.
content
)
def
test_user_detail_email_updated
(
self
):
test_uri
=
'{}/{}'
.
format
(
self
.
users_base_uri
,
self
.
user
.
id
)
new_email
=
'test@example.com'
data
=
{
'email'
:
new_email
}
self
.
assertNotEqual
(
self
.
user
.
email
,
new_email
)
response
=
self
.
do_post
(
test_uri
,
data
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
user
=
User
.
objects
.
get
(
id
=
self
.
user
.
id
)
self
.
assertEqual
(
self
.
user
.
email
,
new_email
)
def
test_user_detail_post_duplicate_username
(
self
):
def
test_user_detail_post_duplicate_username
(
self
):
"""
"""
Create two users, then pass the same first username in request in order to update username of second user.
Create two users, then pass the same first username in request in order to update username of second user.
...
@@ -1435,6 +1469,8 @@ class UsersApiTests(ModuleStoreTestCase):
...
@@ -1435,6 +1469,8 @@ class UsersApiTests(ModuleStoreTestCase):
response
.
data
[
'level_of_education'
],
data
[
"level_of_education"
])
response
.
data
[
'level_of_education'
],
data
[
"level_of_education"
])
self
.
assertEqual
(
self
.
assertEqual
(
str
(
response
.
data
[
'year_of_birth'
]),
data
[
"year_of_birth"
])
str
(
response
.
data
[
'year_of_birth'
]),
data
[
"year_of_birth"
])
# This one's technically on the user model itself, but can be updated.
self
.
assertEqual
(
response
.
data
[
'email'
],
data
[
'email'
])
def
test_user_organizations_list
(
self
):
def
test_user_organizations_list
(
self
):
user_id
=
self
.
user
.
id
user_id
=
self
.
user
.
id
...
...
lms/djangoapps/api_manager/users/views.py
View file @
44b09dd8
...
@@ -469,6 +469,25 @@ class UsersDetail(SecureAPIView):
...
@@ -469,6 +469,25 @@ class UsersDetail(SecureAPIView):
if
is_staff
is
not
None
:
if
is_staff
is
not
None
:
existing_user
.
is_staff
=
is_staff
existing_user
.
is_staff
=
is_staff
response_data
[
'is_staff'
]
=
existing_user
.
is_staff
response_data
[
'is_staff'
]
=
existing_user
.
is_staff
email
=
request
.
DATA
.
get
(
'email'
)
if
email
is
not
None
:
email_fail
=
False
try
:
validate_email
(
email
)
except
ValidationError
:
email_fail
=
True
response_data
[
'message'
]
=
_
(
'Invalid email address {}.'
)
.
format
(
repr
(
email
))
if
email
!=
existing_user
.
email
:
try
:
# Email addresses need to be unique in the LMS, though Django doesn't enforce it directly.
User
.
objects
.
get
(
email
=
email
)
email_fail
=
True
response_data
[
'message'
]
=
_
(
'A user with that email address already exists.'
)
except
ObjectDoesNotExist
:
pass
if
email_fail
:
return
Response
(
response_data
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
existing_user
.
email
=
email
existing_user
.
save
()
existing_user
.
save
()
username
=
request
.
DATA
.
get
(
'username'
,
None
)
username
=
request
.
DATA
.
get
(
'username'
,
None
)
...
...
lms/djangoapps/django_comment_client/management/commands/export_discussion_participation.py
View file @
44b09dd8
...
@@ -5,6 +5,8 @@ from datetime import datetime
...
@@ -5,6 +5,8 @@ from datetime import datetime
from
optparse
import
make_option
from
optparse
import
make_option
from
django.core.management.base
import
BaseCommand
,
CommandError
from
django.core.management.base
import
BaseCommand
,
CommandError
import
os
from
path
import
path
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
...
@@ -15,6 +17,7 @@ from student.models import CourseEnrollment
...
@@ -15,6 +17,7 @@ from student.models import CourseEnrollment
from
lms.lib.comment_client.user
import
User
from
lms.lib.comment_client.user
import
User
import
django_comment_client.utils
as
utils
import
django_comment_client.utils
as
utils
from
xmodule.modulestore.django
import
modulestore
class
DiscussionExportFields
(
object
):
class
DiscussionExportFields
(
object
):
...
@@ -39,9 +42,11 @@ class Command(BaseCommand):
...
@@ -39,9 +42,11 @@ class Command(BaseCommand):
Usage:
Usage:
./manage.py lms export_discussion_participation course_key [dest_file] [OPTIONS]
./manage.py lms export_discussion_participation course_key [dest_file] [OPTIONS]
./manage.py lms export_discussion_participation [dest_directory] --all [OPTIONS]
* course_key - target course key (e.g. edX/DemoX/T1)
* course_key - target course key (e.g. edX/DemoX/T1)
* dest_file - location of destination file (created if missing, overwritten if exists)
* dest_file - location of destination file (created if missing, overwritten if exists)
* dest_directory - location to store all dumped files to. Will dump into the current directory otherwise.
OPTIONS:
OPTIONS:
...
@@ -49,6 +54,10 @@ class Command(BaseCommand):
...
@@ -49,6 +54,10 @@ class Command(BaseCommand):
* end-date - date time in iso8601 format (YYYY-MM-DD hh:mm:ss). Filters discussion participation stats
* end-date - date time in iso8601 format (YYYY-MM-DD hh:mm:ss). Filters discussion participation stats
by creation date: no threads/comments/replies created *after* this date is included in calculation
by creation date: no threads/comments/replies created *after* this date is included in calculation
FLAGS:
* cohorted_only - only dump cohorted inline discussion threads
* all - Dump all social stats at once into a particular directory.
Examples:
Examples:
* `./manage.py lms export_discussion_participation <course_key>` - exports entire discussion participation stats for
* `./manage.py lms export_discussion_participation <course_key>` - exports entire discussion participation stats for
...
@@ -62,9 +71,13 @@ class Command(BaseCommand):
...
@@ -62,9 +71,13 @@ class Command(BaseCommand):
* `./manage.py lms export_discussion_participation <course_key> --end-date=<iso8601 datetime>
* `./manage.py lms export_discussion_participation <course_key> --end-date=<iso8601 datetime>
--thread-type=[discussion|question]` - exports discussion participation stats for a course for
--thread-type=[discussion|question]` - exports discussion participation stats for a course for
threads/comments/replies created before specified date, including only threads of specified type
threads/comments/replies created before specified date, including only threads of specified type
* `./manage.py lms export_discussion_participation <course_key> --cohorted_only` - exports only cohorted discussion
participation stats for a course; output is written to default location (same folder, auto-generated file name)
"""
"""
THREAD_TYPE_PARAMETER
=
'thread_type'
THREAD_TYPE_PARAMETER
=
'thread_type'
END_DATE_PARAMETER
=
'end_date'
END_DATE_PARAMETER
=
'end_date'
ALL_PARAMETER
=
'all'
COHORTED_ONLY_PARAMETER
=
'cohorted_only'
args
=
"<course_id> <output_file_location>"
args
=
"<course_id> <output_file_location>"
...
@@ -86,6 +99,18 @@ class Command(BaseCommand):
...
@@ -86,6 +99,18 @@ class Command(BaseCommand):
default
=
None
,
default
=
None
,
help
=
'Include threads, comments and replies created before the supplied date (iso8601 format)'
help
=
'Include threads, comments and replies created before the supplied date (iso8601 format)'
),
),
make_option
(
'--all'
,
action
=
'store_true'
,
dest
=
ALL_PARAMETER
,
default
=
False
,
),
make_option
(
'--cohorted_only'
,
action
=
'store_true'
,
dest
=
COHORTED_ONLY_PARAMETER
,
default
=
False
,
)
)
)
def
_get_filter_string_representation
(
self
,
options
):
def
_get_filter_string_representation
(
self
,
options
):
...
@@ -103,8 +128,34 @@ class Command(BaseCommand):
...
@@ -103,8 +128,34 @@ class Command(BaseCommand):
"social_stats_{course}_{date:
%
Y_
%
m_
%
d_
%
H_
%
M_
%
S}.csv"
.
format
(
course
=
course_key
,
date
=
datetime
.
utcnow
())
"social_stats_{course}_{date:
%
Y_
%
m_
%
d_
%
H_
%
M_
%
S}.csv"
.
format
(
course
=
course_key
,
date
=
datetime
.
utcnow
())
)
)
def
handle
(
self
,
*
args
,
**
options
):
@staticmethod
""" Executes command """
def
get_all_courses
():
"""
Gets all courses. Made into a separate function because patch isn't cooperating.
"""
return
modulestore
()
.
get_courses
()
def
dump_all
(
self
,
*
args
,
**
options
):
if
len
(
args
)
>
1
:
raise
CommandError
(
"May not specify course and destination root directory with the --all option."
)
args
=
list
(
args
)
try
:
dir_name
=
path
(
args
.
pop
())
except
IndexError
:
dir_name
=
path
(
'social_stats'
)
if
not
os
.
path
.
exists
(
dir_name
):
os
.
makedirs
(
dir_name
)
for
course
in
self
.
get_all_courses
():
raw_course_key
=
unicode
(
course
.
location
.
course_key
)
args
=
[
raw_course_key
,
dir_name
/
self
.
get_default_file_location
(
raw_course_key
)
]
self
.
dump_one
(
*
args
,
**
options
)
def
dump_one
(
self
,
*
args
,
**
options
):
if
not
args
:
if
not
args
:
raise
CommandError
(
"Course id not specified"
)
raise
CommandError
(
"Course id not specified"
)
if
len
(
args
)
>
2
:
if
len
(
args
)
>
2
:
...
@@ -125,12 +176,22 @@ class Command(BaseCommand):
...
@@ -125,12 +176,22 @@ class Command(BaseCommand):
if
not
course
:
if
not
course
:
raise
CommandError
(
"Invalid course id: {}"
.
format
(
course_key
))
raise
CommandError
(
"Invalid course id: {}"
.
format
(
course_key
))
target_discussion_ids
=
None
if
options
.
get
(
self
.
COHORTED_ONLY_PARAMETER
,
False
):
cohorted_discussions
=
course
.
cohort_config
.
get
(
'cohorted_inline_discussions'
,
None
)
if
not
cohorted_discussions
:
raise
CommandError
(
"Only cohorted discussions are marked for export, "
"but no cohorted discussions found for the course"
)
else
:
target_discussion_ids
=
cohorted_discussions
raw_end_date
=
options
.
get
(
self
.
END_DATE_PARAMETER
,
None
)
raw_end_date
=
options
.
get
(
self
.
END_DATE_PARAMETER
,
None
)
end_date
=
dateutil
.
parser
.
parse
(
raw_end_date
)
if
raw_end_date
else
None
end_date
=
dateutil
.
parser
.
parse
(
raw_end_date
)
if
raw_end_date
else
None
data
=
Extractor
()
.
extract
(
data
=
Extractor
()
.
extract
(
course_key
,
course_key
,
end_date
=
end_date
,
end_date
=
end_date
,
thread_type
=
(
options
.
get
(
self
.
THREAD_TYPE_PARAMETER
,
None
))
thread_type
=
(
options
.
get
(
self
.
THREAD_TYPE_PARAMETER
,
None
)),
thread_ids
=
target_discussion_ids
,
)
)
filter_str
=
self
.
_get_filter_string_representation
(
options
)
filter_str
=
self
.
_get_filter_string_representation
(
options
)
...
@@ -139,8 +200,14 @@ class Command(BaseCommand):
...
@@ -139,8 +200,14 @@ class Command(BaseCommand):
with
open
(
output_file_location
,
'wb'
)
as
output_stream
:
with
open
(
output_file_location
,
'wb'
)
as
output_stream
:
Exporter
(
output_stream
)
.
export
(
data
)
Exporter
(
output_stream
)
.
export
(
data
)
self
.
stdout
.
write
(
"Success!
\n
"
)
def
handle
(
self
,
*
args
,
**
options
):
""" Executes command """
if
options
.
get
(
self
.
ALL_PARAMETER
,
False
):
self
.
dump_all
(
*
args
,
**
options
)
else
:
self
.
dump_one
(
*
args
,
**
options
)
self
.
stdout
.
write
(
"Success!
\n
"
)
class
Extractor
(
object
):
class
Extractor
(
object
):
""" Extracts discussion participation data from db and cs_comments_service """
""" Extracts discussion participation data from db and cs_comments_service """
...
@@ -165,11 +232,13 @@ class Extractor(object):
...
@@ -165,11 +232,13 @@ class Extractor(object):
users
=
CourseEnrollment
.
users_enrolled_in
(
course_key
)
users
=
CourseEnrollment
.
users_enrolled_in
(
course_key
)
return
{
user
.
id
:
user
for
user
in
users
}
return
{
user
.
id
:
user
for
user
in
users
}
def
_get_social_stats
(
self
,
course_key
,
end_date
=
None
,
thread_type
=
None
):
def
_get_social_stats
(
self
,
course_key
,
end_date
=
None
,
thread_type
=
None
,
thread_ids
=
None
):
""" Gets social stats for course with specified filter parameters """
""" Gets social stats for course with specified filter parameters """
return
{
return
{
int
(
user_id
):
data
for
user_id
,
data
int
(
user_id
):
data
for
user_id
,
data
in
User
.
all_social_stats
(
str
(
course_key
),
end_date
=
end_date
,
thread_type
=
thread_type
)
.
iteritems
()
in
User
.
all_social_stats
(
str
(
course_key
),
end_date
=
end_date
,
thread_type
=
thread_type
,
thread_ids
=
thread_ids
)
.
iteritems
()
}
}
def
_merge_user_data_and_social_stats
(
self
,
userdata
,
social_stats
):
def
_merge_user_data_and_social_stats
(
self
,
userdata
,
social_stats
):
...
@@ -187,13 +256,14 @@ class Extractor(object):
...
@@ -187,13 +256,14 @@ class Extractor(object):
result
.
append
(
utils
.
merge_dict
(
user_record
,
stats
))
result
.
append
(
utils
.
merge_dict
(
user_record
,
stats
))
return
result
return
result
def
extract
(
self
,
course_key
,
end_date
=
None
,
thread_type
=
None
):
def
extract
(
self
,
course_key
,
end_date
=
None
,
thread_type
=
None
,
thread_ids
=
None
):
""" Extracts and merges data according to course key and filter parameters """
""" Extracts and merges data according to course key and filter parameters """
users
=
self
.
_get_users
(
course_key
)
users
=
self
.
_get_users
(
course_key
)
social_stats
=
self
.
_get_social_stats
(
social_stats
=
self
.
_get_social_stats
(
course_key
,
course_key
,
end_date
=
end_date
,
end_date
=
end_date
,
thread_type
=
thread_type
thread_type
=
thread_type
,
thread_ids
=
thread_ids
)
)
return
self
.
_merge_user_data_and_social_stats
(
users
,
social_stats
)
return
self
.
_merge_user_data_and_social_stats
(
users
,
social_stats
)
...
...
lms/djangoapps/django_comment_client/tests/management/test_export_discussion_participation.py
View file @
44b09dd8
This diff is collapsed.
Click to expand it.
lms/djangoapps/static_template_view/views.py
View file @
44b09dd8
...
@@ -62,8 +62,8 @@ def render_press_release(request, slug):
...
@@ -62,8 +62,8 @@ def render_press_release(request, slug):
def
render_404
(
request
):
def
render_404
(
request
):
return
HttpResponseNotFound
(
render_to_string
(
'static_templates/404.html'
,
{}))
return
HttpResponseNotFound
(
render_to_string
(
'static_templates/404
-plain
.html'
,
{}))
def
render_500
(
request
):
def
render_500
(
request
):
return
HttpResponseServerError
(
render_to_string
(
'static_templates/server-error.html'
,
{}))
return
HttpResponseServerError
(
render_to_string
(
'static_templates/server-error
-plain
.html'
,
{}))
lms/lib/comment_client/user.py
View file @
44b09dd8
...
@@ -119,9 +119,9 @@ class User(models.Model):
...
@@ -119,9 +119,9 @@ class User(models.Model):
return
get_user_social_stats
(
self
.
id
,
self
.
course_id
,
end_date
=
end_date
)
return
get_user_social_stats
(
self
.
id
,
self
.
course_id
,
end_date
=
end_date
)
@classmethod
@classmethod
def
all_social_stats
(
cls
,
course_id
,
end_date
=
None
,
thread_type
=
None
):
def
all_social_stats
(
cls
,
course_id
,
end_date
=
None
,
thread_type
=
None
,
thread_ids
=
None
):
""" Get social stats for all users participating in a course """
""" Get social stats for all users participating in a course """
return
get_user_social_stats
(
'*'
,
course_id
,
end_date
=
end_date
,
thread_type
=
thread_type
)
return
get_user_social_stats
(
'*'
,
course_id
,
end_date
=
end_date
,
thread_type
=
thread_type
,
thread_ids
=
thread_ids
)
def
_retrieve
(
self
,
*
args
,
**
kwargs
):
def
_retrieve
(
self
,
*
args
,
**
kwargs
):
url
=
self
.
url
(
action
=
'get'
,
params
=
self
.
attributes
)
url
=
self
.
url
(
action
=
'get'
,
params
=
self
.
attributes
)
...
@@ -156,7 +156,7 @@ class User(models.Model):
...
@@ -156,7 +156,7 @@ class User(models.Model):
self
.
_update_from_response
(
response
)
self
.
_update_from_response
(
response
)
def
get_user_social_stats
(
user_id
,
course_id
,
end_date
=
None
,
thread_type
=
None
):
def
get_user_social_stats
(
user_id
,
course_id
,
end_date
=
None
,
thread_type
=
None
,
thread_ids
=
None
):
""" Queries cs_comments_service for social_stats """
""" Queries cs_comments_service for social_stats """
if
not
course_id
:
if
not
course_id
:
raise
CommentClientRequestError
(
"Must provide course_id when retrieving social stats for the user"
)
raise
CommentClientRequestError
(
"Must provide course_id when retrieving social stats for the user"
)
...
@@ -167,9 +167,11 @@ def get_user_social_stats(user_id, course_id, end_date=None, thread_type=None):
...
@@ -167,9 +167,11 @@ def get_user_social_stats(user_id, course_id, end_date=None, thread_type=None):
params
.
update
({
'end_date'
:
end_date
.
isoformat
()})
params
.
update
({
'end_date'
:
end_date
.
isoformat
()})
if
thread_type
:
if
thread_type
:
params
.
update
({
'thread_type'
:
thread_type
})
params
.
update
({
'thread_type'
:
thread_type
})
if
thread_ids
:
params
.
update
({
'thread_ids'
:
","
.
join
(
thread_ids
)})
response
=
perform_request
(
response
=
perform_request
(
'
ge
t'
,
'
pos
t'
,
url
,
url
,
params
params
)
)
...
...
lms/templates/static_templates/404-plain.html
0 → 100644
View file @
44b09dd8
<html>
<body>
<h1>
Page not found
</h1>
</body>
</html>
lms/templates/static_templates/server-error-plain.html
0 → 100644
View file @
44b09dd8
<html>
<body>
<h1>
There has been a 500 error on the servers
</h1>
</body>
</html>
requirements/edx/custom.txt
View file @
44b09dd8
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
-e git+https://github.com/edx-solutions/xblock-mentoring.git@82a4219b865d12db80ac57bda43fef9e30bec3f1#egg=xblock-mentoring
-e git+https://github.com/edx-solutions/xblock-mentoring.git@82a4219b865d12db80ac57bda43fef9e30bec3f1#egg=xblock-mentoring
-e git+https://github.com/edx-solutions/xblock-image-explorer.git@21b9bcc4f2c7917463ab18a596161ac6c58c9c4a#egg=xblock-image-explorer
-e git+https://github.com/edx-solutions/xblock-image-explorer.git@21b9bcc4f2c7917463ab18a596161ac6c58c9c4a#egg=xblock-image-explorer
-e git+https://github.com/edx-solutions/xblock-drag-and-drop.git@92ee2055a16899090a073e1df81e35d5293ad767#egg=xblock-drag-and-drop
-e git+https://github.com/edx-solutions/xblock-drag-and-drop.git@92ee2055a16899090a073e1df81e35d5293ad767#egg=xblock-drag-and-drop
-e git+https://github.com/edx-solutions/xblock-drag-and-drop-v2.git@
ed24dbef753e411f2c9b17339ae21f3a89a8531c
#egg=xblock-drag-and-drop-v2
-e git+https://github.com/edx-solutions/xblock-drag-and-drop-v2.git@
5736ed8774b92c8b8396b5bd455f8a8fb80295fb
#egg=xblock-drag-and-drop-v2
-e git+https://github.com/edx-solutions/xblock-ooyala.git@ac49b30452aff0cc34cace6a34b788e100490f24#egg=xblock-ooyala
-e git+https://github.com/edx-solutions/xblock-ooyala.git@ac49b30452aff0cc34cace6a34b788e100490f24#egg=xblock-ooyala
-e git+https://github.com/edx-solutions/xblock-group-project.git@dd8eaf16b3bc7b7be3fb392d588328dadef56c00#egg=xblock-group-project
-e git+https://github.com/edx-solutions/xblock-group-project.git@dd8eaf16b3bc7b7be3fb392d588328dadef56c00#egg=xblock-group-project
-e git+https://github.com/edx-solutions/xblock-adventure.git@effa22006bb6528bc6d3788787466eb4e74e1161#egg=xblock-adventure
-e git+https://github.com/edx-solutions/xblock-adventure.git@effa22006bb6528bc6d3788787466eb4e74e1161#egg=xblock-adventure
...
...
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