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):
self
.
assertEqual
(
response
.
data
[
'is_active'
],
False
)
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
):
"""
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):
response
.
data
[
'level_of_education'
],
data
[
"level_of_education"
])
self
.
assertEqual
(
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
):
user_id
=
self
.
user
.
id
...
...
lms/djangoapps/api_manager/users/views.py
View file @
44b09dd8
...
...
@@ -469,6 +469,25 @@ class UsersDetail(SecureAPIView):
if
is_staff
is
not
None
:
existing_user
.
is_staff
=
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
()
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
from
optparse
import
make_option
from
django.core.management.base
import
BaseCommand
,
CommandError
import
os
from
path
import
path
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
...
...
@@ -15,6 +17,7 @@ from student.models import CourseEnrollment
from
lms.lib.comment_client.user
import
User
import
django_comment_client.utils
as
utils
from
xmodule.modulestore.django
import
modulestore
class
DiscussionExportFields
(
object
):
...
...
@@ -39,9 +42,11 @@ class Command(BaseCommand):
Usage:
./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)
* 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:
...
...
@@ -49,6 +54,10 @@ class Command(BaseCommand):
* 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
FLAGS:
* cohorted_only - only dump cohorted inline discussion threads
* all - Dump all social stats at once into a particular directory.
Examples:
* `./manage.py lms export_discussion_participation <course_key>` - exports entire discussion participation stats for
...
...
@@ -62,9 +71,13 @@ class Command(BaseCommand):
* `./manage.py lms export_discussion_participation <course_key> --end-date=<iso8601 datetime>
--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
* `./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'
END_DATE_PARAMETER
=
'end_date'
ALL_PARAMETER
=
'all'
COHORTED_ONLY_PARAMETER
=
'cohorted_only'
args
=
"<course_id> <output_file_location>"
...
...
@@ -86,6 +99,18 @@ class Command(BaseCommand):
default
=
None
,
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
):
...
...
@@ -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
())
)
def
handle
(
self
,
*
args
,
**
options
):
""" Executes command """
@staticmethod
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
:
raise
CommandError
(
"Course id not specified"
)
if
len
(
args
)
>
2
:
...
...
@@ -125,12 +176,22 @@ class Command(BaseCommand):
if
not
course
:
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
)
end_date
=
dateutil
.
parser
.
parse
(
raw_end_date
)
if
raw_end_date
else
None
data
=
Extractor
()
.
extract
(
course_key
,
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
)
...
...
@@ -139,8 +200,14 @@ class Command(BaseCommand):
with
open
(
output_file_location
,
'wb'
)
as
output_stream
:
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
):
""" Extracts discussion participation data from db and cs_comments_service """
...
...
@@ -165,11 +232,13 @@ class Extractor(object):
users
=
CourseEnrollment
.
users_enrolled_in
(
course_key
)
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 """
return
{
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
):
...
...
@@ -187,13 +256,14 @@ class Extractor(object):
result
.
append
(
utils
.
merge_dict
(
user_record
,
stats
))
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 """
users
=
self
.
_get_users
(
course_key
)
social_stats
=
self
.
_get_social_stats
(
course_key
,
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
)
...
...
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):
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
):
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):
return
get_user_social_stats
(
self
.
id
,
self
.
course_id
,
end_date
=
end_date
)
@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 """
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
):
url
=
self
.
url
(
action
=
'get'
,
params
=
self
.
attributes
)
...
...
@@ -156,7 +156,7 @@ class User(models.Model):
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 """
if
not
course_id
:
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):
params
.
update
({
'end_date'
:
end_date
.
isoformat
()})
if
thread_type
:
params
.
update
({
'thread_type'
:
thread_type
})
if
thread_ids
:
params
.
update
({
'thread_ids'
:
","
.
join
(
thread_ids
)})
response
=
perform_request
(
'
ge
t'
,
'
pos
t'
,
url
,
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 @@
-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-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-group-project.git@dd8eaf16b3bc7b7be3fb392d588328dadef56c00#egg=xblock-group-project
-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