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
Hide 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
self
.
assertEqual
(
student_json
[
'username'
],
student
.
username
)
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
):
"""
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,
FORUM_ROLE_COMMUNITY_TA
)
from
courseware.models
import
StudentModule
from
student.models
import
unique_id_for_user
import
instructor_task.api
from
instructor_task.api_helper
import
AlreadyRunningError
import
instructor.enrollment
as
enrollment
...
...
@@ -37,6 +38,7 @@ import instructor.access as access
import
analytics.basic
import
analytics.distributions
import
analytics.csvs
import
csv
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -371,6 +373,38 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=W06
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@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
):
"""
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
'instructor.views.api.get_grading_config'
,
name
=
"get_grading_config"
),
url
(
r'^get_students_features(?P<csv>/csv)?$'
,
'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$'
,
'instructor.views.api.get_distribution'
,
name
=
"get_distribution"
),
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):
'section_display_name'
:
_
(
'Data Download'
),
'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_anon_ids_url'
:
reverse
(
'get_anon_ids'
,
kwargs
=
{
'course_id'
:
course_id
}),
}
return
section_data
...
...
lms/djangoapps/instructor/views/legacy.py
View file @
d225b85d
...
...
@@ -591,7 +591,7 @@ def instructor_dashboard(request, course_id):
datatable
=
{
'header'
:
[
'User ID'
,
'Anonymized user ID'
]}
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
...
...
lms/static/coffee/src/instructor_dashboard/data_download.coffee
View file @
d225b85d
...
...
@@ -16,10 +16,16 @@ class DataDownload
@
$display_table
=
@
$display
.
find
'.data-display-table'
@
$request_response_error
=
@
$display
.
find
'.request-response-error'
@
$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']'"
)
# 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
# and the csv button
@
$list_studs_btn
.
click
(
e
)
=>
...
...
lms/templates/instructor/instructor_dashboard_2/data_download.html
View file @
d225b85d
...
...
@@ -10,6 +10,8 @@
##
<input
type=
"button"
name=
"list-answer-distributions"
value=
"Answer distributions (x students got y points)"
>
##
<br>
<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-text"
></div>
...
...
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
View file @
d225b85d
...
...
@@ -3,6 +3,19 @@
<
%!
from
django
.
core
.
urlresolvers
import
reverse
%
>
<
%
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"
>
<
%
static:css
group=
'course'
/>
<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