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
d225b85d
Commit
d225b85d
authored
Sep 24, 2013
by
Nick Parlante
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Port get-anonymized-id feature to new instructor dashboard
parent
1b590393
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
143 additions
and
1 deletions
+143
-1
lms/djangoapps/instructor/tests/test_api.py
+13
-0
lms/djangoapps/instructor/tests/test_legacy_anon_csv.py
+71
-0
lms/djangoapps/instructor/views/api.py
+34
-0
lms/djangoapps/instructor/views/api_urls.py
+2
-0
lms/djangoapps/instructor/views/instructor_dashboard.py
+1
-0
lms/djangoapps/instructor/views/legacy.py
+1
-1
lms/static/coffee/src/instructor_dashboard/data_download.coffee
+6
-0
lms/templates/instructor/instructor_dashboard_2/data_download.html
+2
-0
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
+13
-0
No files found.
lms/djangoapps/instructor/tests/test_api.py
View file @
d225b85d
...
@@ -446,6 +446,19 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
...
@@ -446,6 +446,19 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
self
.
assertEqual
(
student_json
[
'username'
],
student
.
username
)
self
.
assertEqual
(
student_json
[
'username'
],
student
.
username
)
self
.
assertEqual
(
student_json
[
'email'
],
student
.
email
)
self
.
assertEqual
(
student_json
[
'email'
],
student
.
email
)
def
test_get_anon_ids
(
self
):
"""
Test the CSV output for the anonymized user ids.
"""
url
=
reverse
(
'get_anon_ids'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
})
with
patch
(
'instructor.views.api.unique_id_for_user'
)
as
mock_unique
:
mock_unique
.
return_value
=
'42'
response
=
self
.
client
.
get
(
url
,
{})
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
)
body
=
response
.
content
.
replace
(
'
\r
'
,
''
)
self
.
assertTrue
(
body
.
startswith
(
'"User ID","Anonymized user ID"
\n
"2","42"
\n
'
))
self
.
assertTrue
(
body
.
endswith
(
'"7","42"
\n
'
))
def
test_get_students_features_csv
(
self
):
def
test_get_students_features_csv
(
self
):
"""
"""
Test that some minimum of information is formatted
Test that some minimum of information is formatted
...
...
lms/djangoapps/instructor/tests/test_legacy_anon_csv.py
0 → 100644
View file @
d225b85d
"""
Unit tests for instructor dashboard
Based on (and depends on) unit tests for courseware.
Notes for running by hand:
./manage.py lms --settings test test lms/djangoapps/instructor
"""
from
django.test.utils
import
override_settings
# Need access to internal func to put users in the right group
from
django.contrib.auth.models
import
Group
,
User
from
django.core.urlresolvers
import
reverse
from
courseware.access
import
_course_staff_group_name
from
courseware.tests.helpers
import
LoginEnrollmentTestCase
from
courseware.tests.modulestore_config
import
TEST_DATA_MIXED_MODULESTORE
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.django
import
modulestore
,
clear_existing_modulestores
import
xmodule.modulestore.django
from
mock
import
patch
@override_settings
(
MODULESTORE
=
TEST_DATA_MIXED_MODULESTORE
)
class
TestInstructorDashboardAnonCSV
(
ModuleStoreTestCase
,
LoginEnrollmentTestCase
):
'''
Check for download of csv
'''
# Note -- I copied this setUp from a similar test
def
setUp
(
self
):
clear_existing_modulestores
()
self
.
toy
=
modulestore
()
.
get_course
(
"edX/toy/2012_Fall"
)
# Create two accounts
self
.
student
=
'view@test.com'
self
.
instructor
=
'view2@test.com'
self
.
password
=
'foo'
self
.
create_account
(
'u1'
,
self
.
student
,
self
.
password
)
self
.
create_account
(
'u2'
,
self
.
instructor
,
self
.
password
)
self
.
activate_user
(
self
.
student
)
self
.
activate_user
(
self
.
instructor
)
def
make_instructor
(
course
):
""" Create an instructor for the course. """
group_name
=
_course_staff_group_name
(
course
.
location
)
group
=
Group
.
objects
.
create
(
name
=
group_name
)
group
.
user_set
.
add
(
User
.
objects
.
get
(
email
=
self
.
instructor
))
make_instructor
(
self
.
toy
)
self
.
logout
()
self
.
login
(
self
.
instructor
,
self
.
password
)
self
.
enroll
(
self
.
toy
)
def
test_download_anon_csv
(
self
):
course
=
self
.
toy
url
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
course
.
id
})
with
patch
(
'instructor.views.legacy.unique_id_for_user'
)
as
mock_unique
:
mock_unique
.
return_value
=
42
response
=
self
.
client
.
post
(
url
,
{
'action'
:
'Download CSV of all student anonymized IDs'
})
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
)
body
=
response
.
content
.
replace
(
'
\r
'
,
''
)
self
.
assertEqual
(
body
,
'"User ID","Anonymized user ID"
\n
"2","42"
\n
'
)
lms/djangoapps/instructor/views/api.py
View file @
d225b85d
...
@@ -28,6 +28,7 @@ from django_comment_common.models import (Role,
...
@@ -28,6 +28,7 @@ from django_comment_common.models import (Role,
FORUM_ROLE_COMMUNITY_TA
)
FORUM_ROLE_COMMUNITY_TA
)
from
courseware.models
import
StudentModule
from
courseware.models
import
StudentModule
from
student.models
import
unique_id_for_user
import
instructor_task.api
import
instructor_task.api
from
instructor_task.api_helper
import
AlreadyRunningError
from
instructor_task.api_helper
import
AlreadyRunningError
import
instructor.enrollment
as
enrollment
import
instructor.enrollment
as
enrollment
...
@@ -37,6 +38,7 @@ import instructor.access as access
...
@@ -37,6 +38,7 @@ import instructor.access as access
import
analytics.basic
import
analytics.basic
import
analytics.distributions
import
analytics.distributions
import
analytics.csvs
import
analytics.csvs
import
csv
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -371,6 +373,38 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=W06
...
@@ -371,6 +373,38 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=W06
@ensure_csrf_cookie
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
@require_level
(
'staff'
)
def
get_anon_ids
(
request
,
course_id
):
# pylint: disable=W0613
"""
Respond with 2-column CSV output of user-id, anonymized-user-id
"""
# TODO: the User.objects query and CSV generation here could be
# centralized into analytics. Currently analytics has similar functionality
# but not quite what's needed.
def
csv_response
(
filename
,
header
,
rows
):
"""Returns a CSV http response for the given header and rows (excel/utf-8)."""
response
=
HttpResponse
(
mimetype
=
'text/csv'
)
response
[
'Content-Disposition'
]
=
'attachment; filename={0}'
.
format
(
filename
)
writer
=
csv
.
writer
(
response
,
dialect
=
'excel'
,
quotechar
=
'"'
,
quoting
=
csv
.
QUOTE_ALL
)
# In practice, there should not be non-ascii data in this query,
# but trying to do the right thing anyway.
encoded
=
[
unicode
(
s
)
.
encode
(
'utf-8'
)
for
s
in
header
]
writer
.
writerow
(
encoded
)
for
row
in
rows
:
encoded
=
[
unicode
(
s
)
.
encode
(
'utf-8'
)
for
s
in
row
]
writer
.
writerow
(
encoded
)
return
response
students
=
User
.
objects
.
filter
(
courseenrollment__course_id
=
course_id
,
)
.
order_by
(
'id'
)
header
=
[
'User ID'
,
'Anonymized user ID'
]
rows
=
[[
s
.
id
,
unique_id_for_user
(
s
)]
for
s
in
students
]
return
csv_response
(
course_id
.
replace
(
'/'
,
'-'
)
+
'-anon-ids.csv'
,
header
,
rows
)
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_level
(
'staff'
)
def
get_distribution
(
request
,
course_id
):
def
get_distribution
(
request
,
course_id
):
"""
"""
Respond with json of the distribution of students over selected features which have choices.
Respond with json of the distribution of students over selected features which have choices.
...
...
lms/djangoapps/instructor/views/api_urls.py
View file @
d225b85d
...
@@ -16,6 +16,8 @@ urlpatterns = patterns('', # nopep8
...
@@ -16,6 +16,8 @@ urlpatterns = patterns('', # nopep8
'instructor.views.api.get_grading_config'
,
name
=
"get_grading_config"
),
'instructor.views.api.get_grading_config'
,
name
=
"get_grading_config"
),
url
(
r'^get_students_features(?P<csv>/csv)?$'
,
url
(
r'^get_students_features(?P<csv>/csv)?$'
,
'instructor.views.api.get_students_features'
,
name
=
"get_students_features"
),
'instructor.views.api.get_students_features'
,
name
=
"get_students_features"
),
url
(
r'^get_anon_ids$'
,
'instructor.views.api.get_anon_ids'
,
name
=
"get_anon_ids"
),
url
(
r'^get_distribution$'
,
url
(
r'^get_distribution$'
,
'instructor.views.api.get_distribution'
,
name
=
"get_distribution"
),
'instructor.views.api.get_distribution'
,
name
=
"get_distribution"
),
url
(
r'^get_student_progress_url$'
,
url
(
r'^get_student_progress_url$'
,
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
d225b85d
...
@@ -133,6 +133,7 @@ def _section_data_download(course_id):
...
@@ -133,6 +133,7 @@ def _section_data_download(course_id):
'section_display_name'
:
_
(
'Data Download'
),
'section_display_name'
:
_
(
'Data Download'
),
'get_grading_config_url'
:
reverse
(
'get_grading_config'
,
kwargs
=
{
'course_id'
:
course_id
}),
'get_grading_config_url'
:
reverse
(
'get_grading_config'
,
kwargs
=
{
'course_id'
:
course_id
}),
'get_students_features_url'
:
reverse
(
'get_students_features'
,
kwargs
=
{
'course_id'
:
course_id
}),
'get_students_features_url'
:
reverse
(
'get_students_features'
,
kwargs
=
{
'course_id'
:
course_id
}),
'get_anon_ids_url'
:
reverse
(
'get_anon_ids'
,
kwargs
=
{
'course_id'
:
course_id
}),
}
}
return
section_data
return
section_data
...
...
lms/djangoapps/instructor/views/legacy.py
View file @
d225b85d
...
@@ -591,7 +591,7 @@ def instructor_dashboard(request, course_id):
...
@@ -591,7 +591,7 @@ def instructor_dashboard(request, course_id):
datatable
=
{
'header'
:
[
'User ID'
,
'Anonymized user ID'
]}
datatable
=
{
'header'
:
[
'User ID'
,
'Anonymized user ID'
]}
datatable
[
'data'
]
=
[[
s
.
id
,
unique_id_for_user
(
s
)]
for
s
in
students
]
datatable
[
'data'
]
=
[[
s
.
id
,
unique_id_for_user
(
s
)]
for
s
in
students
]
return
return_csv
(
course_id
.
replace
(
'/'
,
'-'
)
+
'-anon-ids.csv'
,
datatable
)
return
return_csv
(
course_id
.
replace
(
'/'
,
'-'
)
+
'-anon-ids.csv'
,
datatable
)
#----------------------------------------
#----------------------------------------
# Group management
# Group management
...
...
lms/static/coffee/src/instructor_dashboard/data_download.coffee
View file @
d225b85d
...
@@ -16,10 +16,16 @@ class DataDownload
...
@@ -16,10 +16,16 @@ class DataDownload
@
$display_table
=
@
$display
.
find
'.data-display-table'
@
$display_table
=
@
$display
.
find
'.data-display-table'
@
$request_response_error
=
@
$display
.
find
'.request-response-error'
@
$request_response_error
=
@
$display
.
find
'.request-response-error'
@
$list_studs_btn
=
@
$section
.
find
(
"input[name='list-profiles']'"
)
@
$list_studs_btn
=
@
$section
.
find
(
"input[name='list-profiles']'"
)
@
$list_anon_btn
=
@
$section
.
find
(
"input[name='list-anon-ids']'"
)
@
$grade_config_btn
=
@
$section
.
find
(
"input[name='dump-gradeconf']'"
)
@
$grade_config_btn
=
@
$section
.
find
(
"input[name='dump-gradeconf']'"
)
# attach click handlers
# attach click handlers
# The list-anon case is always CSV
@
$list_anon_btn
.
click
(
e
)
=>
url
=
@
$list_anon_btn
.
data
'endpoint'
location
.
href
=
url
# this handler binds to both the download
# this handler binds to both the download
# and the csv button
# and the csv button
@
$list_studs_btn
.
click
(
e
)
=>
@
$list_studs_btn
.
click
(
e
)
=>
...
...
lms/templates/instructor/instructor_dashboard_2/data_download.html
View file @
d225b85d
...
@@ -10,6 +10,8 @@
...
@@ -10,6 +10,8 @@
##
<input
type=
"button"
name=
"list-answer-distributions"
value=
"Answer distributions (x students got y points)"
>
##
<input
type=
"button"
name=
"list-answer-distributions"
value=
"Answer distributions (x students got y points)"
>
##
<br>
##
<br>
<input
type=
"button"
name=
"dump-gradeconf"
value=
"${_("
Grading
Configuration
")}"
data-endpoint=
"${ section_data['get_grading_config_url'] }"
>
<input
type=
"button"
name=
"dump-gradeconf"
value=
"${_("
Grading
Configuration
")}"
data-endpoint=
"${ section_data['get_grading_config_url'] }"
>
<input
type=
"button"
name=
"list-anon-ids"
value=
"${_("
Get
Student
Anonymized
IDs
CSV
")}"
data-csv=
"true"
class=
"csv"
data-endpoint=
"${ section_data['get_anon_ids_url'] }"
>
<div
class=
"data-display"
>
<div
class=
"data-display"
>
<div
class=
"data-display-text"
></div>
<div
class=
"data-display-text"
></div>
...
...
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
View file @
d225b85d
...
@@ -3,6 +3,19 @@
...
@@ -3,6 +3,19 @@
<
%!
from
django
.
core
.
urlresolvers
import
reverse
%
>
<
%!
from
django
.
core
.
urlresolvers
import
reverse
%
>
<
%
namespace
name=
'static'
file=
'/static_content.html'
/>
<
%
namespace
name=
'static'
file=
'/static_content.html'
/>
## ----- Tips on adding something to the new instructor dashboard -----
## 1. add your input element, e.g. in instructor_dashboard2/data_download.html
## the input includes a reference like data-endpoint="${ section_data['get_anon_ids_url'] }"
## 2. Go to the old dashboard djangoapps/instructor/views/instructor_dashboard.py and
## add in a definition of 'xxx_url' in the right section_data for whatever page your
## feature is on.
## 3. Add a url() entry in api_urls.py
## 4. Over in lms/static/coffee/src/instructor_dashboard/ there there are .coffee files
## for each page which define the .js. Edit this to make your input do something
## when clicked. The .coffee files use the name=xx to pick out inputs, not id=
## 5. Implement your standard django/python in lms/djangoapps/instructor/views/api.py
## 6. And tests go in lms/djangoapps/instructor/tests/
<
%
block
name=
"headextra"
>
<
%
block
name=
"headextra"
>
<
%
static:css
group=
'course'
/>
<
%
static:css
group=
'course'
/>
<script
type=
"text/javascript"
src=
"${static.url('js/vendor/underscore-min.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/vendor/underscore-min.js')}"
></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