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
6644b788
Commit
6644b788
authored
Apr 08, 2013
by
Calen Pennington
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix gradebook when using grade cutoffs other than A/B/C
parent
f51aea18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
516 additions
and
203 deletions
+516
-203
cms/djangoapps/contentstore/tests/test_contentstore.py
+2
-1
cms/djangoapps/contentstore/tests/test_course_settings.py
+1
-1
cms/djangoapps/contentstore/tests/test_utils.py
+2
-3
cms/djangoapps/contentstore/tests/tests.py
+2
-1
cms/djangoapps/contentstore/tests/utils.py
+0
-99
common/djangoapps/student/tests/factories.py
+12
-1
common/lib/xmodule/xmodule/modulestore/tests/django_utils.py
+103
-0
common/lib/xmodule/xmodule/modulestore/tests/factories.py
+29
-19
lms/djangoapps/courseware/features/common.py
+2
-0
lms/djangoapps/courseware/tests/factories.py
+13
-0
lms/djangoapps/courseware/tests/tests.py
+14
-1
lms/djangoapps/instructor/test_download_csv.py
+81
-0
lms/djangoapps/instructor/tests/__init__.py
+0
-0
lms/djangoapps/instructor/tests/test_download_csv.py
+81
-0
lms/djangoapps/instructor/tests/test_forum_admin.py
+4
-65
lms/djangoapps/instructor/tests/test_gradebook.py
+152
-0
lms/djangoapps/instructor/views.py
+8
-5
lms/templates/courseware/gradebook.html
+10
-7
No files found.
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
6644b788
...
...
@@ -15,8 +15,9 @@ from datetime import timedelta
from
django.contrib.auth.models
import
User
from
django.dispatch
import
Signal
from
contentstore.utils
import
get_modulestore
from
contentstore.tests.utils
import
parse_json
from
.utils
import
ModuleStoreTestCase
,
parse_json
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore
import
Location
...
...
cms/djangoapps/contentstore/tests/test_course_settings.py
View file @
6644b788
...
...
@@ -12,7 +12,7 @@ from models.settings.course_details import (CourseDetails, CourseSettingsEncoder
from
models.settings.course_grading
import
CourseGradingModel
from
contentstore.utils
import
get_modulestore
from
.
utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_
utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
models.settings.course_metadata
import
CourseMetadata
...
...
cms/djangoapps/contentstore/tests/test_utils.py
View file @
6644b788
...
...
@@ -3,7 +3,7 @@ from contentstore import utils
import
mock
from
django.test
import
TestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
.
utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_
utils
import
ModuleStoreTestCase
class
LMSLinksTestCase
(
TestCase
):
...
...
@@ -70,4 +70,4 @@ class UrlReverseTestCase(ModuleStoreTestCase):
'https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about'
,
utils
.
get_url_reverse
(
'https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about'
,
course
)
)
\ No newline at end of file
cms/djangoapps/contentstore/tests/tests.py
View file @
6644b788
from
django.test.client
import
Client
from
django.core.urlresolvers
import
reverse
from
.utils
import
ModuleStoreTestCase
,
parse_json
,
user
,
registration
from
.utils
import
parse_json
,
user
,
registration
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
class
ContentStoreTestCase
(
ModuleStoreTestCase
):
...
...
cms/djangoapps/contentstore/tests/utils.py
View file @
6644b788
...
...
@@ -5,109 +5,10 @@ Utilities for contentstore tests
#pylint: disable=W0603
import
json
import
copy
from
uuid
import
uuid4
from
django.test
import
TestCase
from
django.conf
import
settings
from
student.models
import
Registration
from
django.contrib.auth.models
import
User
import
xmodule.modulestore.django
from
xmodule.templates
import
update_templates
class
ModuleStoreTestCase
(
TestCase
):
""" Subclass for any test case that uses the mongodb
module store. This populates a uniquely named modulestore
collection with templates before running the TestCase
and drops it they are finished. """
@staticmethod
def
flush_mongo_except_templates
():
'''
Delete everything in the module store except templates
'''
modulestore
=
xmodule
.
modulestore
.
django
.
modulestore
()
# This query means: every item in the collection
# that is not a template
query
=
{
"_id.course"
:
{
"$ne"
:
"templates"
}}
# Remove everything except templates
modulestore
.
collection
.
remove
(
query
)
@staticmethod
def
load_templates_if_necessary
():
'''
Load templates into the modulestore only if they do not already exist.
We need the templates, because they are copied to create
XModules such as sections and problems
'''
modulestore
=
xmodule
.
modulestore
.
django
.
modulestore
()
# Count the number of templates
query
=
{
"_id.course"
:
"templates"
}
num_templates
=
modulestore
.
collection
.
find
(
query
)
.
count
()
if
num_templates
<
1
:
update_templates
()
@classmethod
def
setUpClass
(
cls
):
'''
Flush the mongo store and set up templates
'''
# Use a uuid to differentiate
# the mongo collections on jenkins.
cls
.
orig_modulestore
=
copy
.
deepcopy
(
settings
.
MODULESTORE
)
test_modulestore
=
cls
.
orig_modulestore
test_modulestore
[
'default'
][
'OPTIONS'
][
'collection'
]
=
'modulestore_
%
s'
%
uuid4
()
.
hex
test_modulestore
[
'direct'
][
'OPTIONS'
][
'collection'
]
=
'modulestore_
%
s'
%
uuid4
()
.
hex
xmodule
.
modulestore
.
django
.
_MODULESTORES
=
{}
settings
.
MODULESTORE
=
test_modulestore
TestCase
.
setUpClass
()
@classmethod
def
tearDownClass
(
cls
):
'''
Revert to the old modulestore settings
'''
# Clean up by dropping the collection
modulestore
=
xmodule
.
modulestore
.
django
.
modulestore
()
modulestore
.
collection
.
drop
()
# Restore the original modulestore settings
settings
.
MODULESTORE
=
cls
.
orig_modulestore
def
_pre_setup
(
self
):
'''
Remove everything but the templates before each test
'''
# Flush anything that is not a template
ModuleStoreTestCase
.
flush_mongo_except_templates
()
# Check that we have templates loaded; if not, load them
ModuleStoreTestCase
.
load_templates_if_necessary
()
# Call superclass implementation
super
(
ModuleStoreTestCase
,
self
)
.
_pre_setup
()
def
_post_teardown
(
self
):
'''
Flush everything we created except the templates
'''
# Flush anything that is not a template
ModuleStoreTestCase
.
flush_mongo_except_templates
()
# Call superclass implementation
super
(
ModuleStoreTestCase
,
self
)
.
_post_teardown
()
def
parse_json
(
response
):
"""Parse response, which is assumed to be json"""
...
...
common/djangoapps/student/tests/factories.py
View file @
6644b788
...
...
@@ -2,7 +2,7 @@ from student.models import (User, UserProfile, Registration,
CourseEnrollmentAllowed
,
CourseEnrollment
)
from
django.contrib.auth.models
import
Group
from
datetime
import
datetime
from
factory
import
Factory
,
SubFactory
from
factory
import
Factory
,
SubFactory
,
post_generation
from
uuid
import
uuid4
...
...
@@ -44,6 +44,17 @@ class UserFactory(Factory):
last_login
=
datetime
(
2012
,
1
,
1
)
date_joined
=
datetime
(
2011
,
1
,
1
)
@post_generation
def
set_password
(
self
,
create
,
extracted
,
**
kwargs
):
self
.
_raw_password
=
self
.
password
self
.
set_password
(
self
.
password
)
if
create
:
self
.
save
()
class
AdminFactory
(
UserFactory
):
is_staff
=
True
class
CourseEnrollmentFactory
(
Factory
):
FACTORY_FOR
=
CourseEnrollment
...
...
common/lib/xmodule/xmodule/modulestore/tests/django_utils.py
0 → 100644
View file @
6644b788
import
copy
from
uuid
import
uuid4
from
django.test
import
TestCase
from
django.conf
import
settings
import
xmodule.modulestore.django
from
xmodule.templates
import
update_templates
class
ModuleStoreTestCase
(
TestCase
):
""" Subclass for any test case that uses the mongodb
module store. This populates a uniquely named modulestore
collection with templates before running the TestCase
and drops it they are finished. """
@staticmethod
def
flush_mongo_except_templates
():
'''
Delete everything in the module store except templates
'''
modulestore
=
xmodule
.
modulestore
.
django
.
modulestore
()
# This query means: every item in the collection
# that is not a template
query
=
{
"_id.course"
:
{
"$ne"
:
"templates"
}}
# Remove everything except templates
modulestore
.
collection
.
remove
(
query
)
@staticmethod
def
load_templates_if_necessary
():
'''
Load templates into the modulestore only if they do not already exist.
We need the templates, because they are copied to create
XModules such as sections and problems
'''
modulestore
=
xmodule
.
modulestore
.
django
.
modulestore
()
# Count the number of templates
query
=
{
"_id.course"
:
"templates"
}
num_templates
=
modulestore
.
collection
.
find
(
query
)
.
count
()
if
num_templates
<
1
:
update_templates
()
@classmethod
def
setUpClass
(
cls
):
'''
Flush the mongo store and set up templates
'''
# Use a uuid to differentiate
# the mongo collections on jenkins.
cls
.
orig_modulestore
=
copy
.
deepcopy
(
settings
.
MODULESTORE
)
test_modulestore
=
cls
.
orig_modulestore
if
'direct'
not
in
test_modulestore
:
test_modulestore
[
'direct'
]
=
test_modulestore
[
'default'
]
test_modulestore
[
'default'
][
'OPTIONS'
][
'collection'
]
=
'modulestore_
%
s'
%
uuid4
()
.
hex
test_modulestore
[
'direct'
][
'OPTIONS'
][
'collection'
]
=
'modulestore_
%
s'
%
uuid4
()
.
hex
xmodule
.
modulestore
.
django
.
_MODULESTORES
=
{}
settings
.
MODULESTORE
=
test_modulestore
print
settings
.
MODULESTORE
TestCase
.
setUpClass
()
@classmethod
def
tearDownClass
(
cls
):
'''
Revert to the old modulestore settings
'''
# Clean up by dropping the collection
modulestore
=
xmodule
.
modulestore
.
django
.
modulestore
()
modulestore
.
collection
.
drop
()
# Restore the original modulestore settings
settings
.
MODULESTORE
=
cls
.
orig_modulestore
def
_pre_setup
(
self
):
'''
Remove everything but the templates before each test
'''
# Flush anything that is not a template
ModuleStoreTestCase
.
flush_mongo_except_templates
()
# Check that we have templates loaded; if not, load them
ModuleStoreTestCase
.
load_templates_if_necessary
()
# Call superclass implementation
super
(
ModuleStoreTestCase
,
self
)
.
_pre_setup
()
def
_post_teardown
(
self
):
'''
Flush everything we created except the templates
'''
# Flush anything that is not a template
ModuleStoreTestCase
.
flush_mongo_except_templates
()
# Call superclass implementation
super
(
ModuleStoreTestCase
,
self
)
.
_post_teardown
()
common/lib/xmodule/xmodule/modulestore/tests/factories.py
View file @
6644b788
from
factory
import
Factory
from
factory
import
Factory
,
lazy_attribute_sequence
,
lazy_attribute
from
time
import
gmtime
from
uuid
import
uuid4
from
xmodule.modulestore
import
Location
...
...
@@ -7,21 +7,12 @@ from xmodule.timeparse import stringify_time
from
xmodule.modulestore.inheritance
import
own_metadata
def
XMODULE_COURSE_CREATION
(
class_to_create
,
**
kwargs
):
return
XModuleCourseFactory
.
_create
(
class_to_create
,
**
kwargs
)
def
XMODULE_ITEM_CREATION
(
class_to_create
,
**
kwargs
):
return
XModuleItemFactory
.
_create
(
class_to_create
,
**
kwargs
)
class
XModuleCourseFactory
(
Factory
):
"""
Factory for XModule courses.
"""
ABSTRACT_FACTORY
=
True
_creation_function
=
(
XMODULE_COURSE_CREATION
,)
@classmethod
def
_create
(
cls
,
target_class
,
*
args
,
**
kwargs
):
...
...
@@ -33,7 +24,10 @@ class XModuleCourseFactory(Factory):
location
=
Location
(
'i4x'
,
org
,
number
,
'course'
,
Location
.
clean
(
display_name
))
store
=
modulestore
(
'direct'
)
try
:
store
=
modulestore
(
'direct'
)
except
KeyError
:
store
=
modulestore
()
# Write the data to the mongo datastore
new_course
=
store
.
clone_item
(
template
,
location
)
...
...
@@ -52,6 +46,11 @@ class XModuleCourseFactory(Factory):
# Update the data in the mongo datastore
store
.
update_metadata
(
new_course
.
location
.
url
(),
own_metadata
(
new_course
))
data
=
kwargs
.
get
(
'data'
)
if
data
is
not
None
:
store
.
update_item
(
new_course
.
location
,
data
)
return
new_course
...
...
@@ -74,7 +73,19 @@ class XModuleItemFactory(Factory):
"""
ABSTRACT_FACTORY
=
True
_creation_function
=
(
XMODULE_ITEM_CREATION
,)
display_name
=
None
@lazy_attribute
def
category
(
attr
):
template
=
Location
(
attr
.
template
)
return
template
.
category
@lazy_attribute
def
location
(
attr
):
parent
=
Location
(
attr
.
parent_location
)
dest_name
=
attr
.
display_name
.
replace
(
" "
,
"_"
)
if
attr
.
display_name
is
not
None
else
uuid4
()
.
hex
return
parent
.
_replace
(
category
=
attr
.
category
,
name
=
dest_name
)
@classmethod
def
_create
(
cls
,
target_class
,
*
args
,
**
kwargs
):
...
...
@@ -110,12 +121,7 @@ class XModuleItemFactory(Factory):
# This code was based off that in cms/djangoapps/contentstore/views.py
parent
=
store
.
get_item
(
parent_location
)
# If a display name is set, use that
dest_name
=
display_name
.
replace
(
" "
,
"_"
)
if
display_name
is
not
None
else
uuid4
()
.
hex
dest_location
=
parent_location
.
_replace
(
category
=
template
.
category
,
name
=
dest_name
)
new_item
=
store
.
clone_item
(
template
,
dest_location
)
new_item
=
store
.
clone_item
(
template
,
kwargs
.
get
(
'location'
))
# replace the display name with an optional parameter passed in from the caller
if
display_name
is
not
None
:
...
...
@@ -145,4 +151,7 @@ class ItemFactory(XModuleItemFactory):
parent_location
=
'i4x://MITx/999/course/Robot_Super_Course'
template
=
'i4x://edx/templates/chapter/Empty'
display_name
=
'Section One'
@lazy_attribute_sequence
def
display_name
(
attr
,
n
):
return
"{} {}"
.
format
(
attr
.
category
.
title
(),
n
)
\ No newline at end of file
lms/djangoapps/courseware/features/common.py
View file @
6644b788
#pylint: disable=C0111
#pylint: disable=W0621
from
__future__
import
absolute_import
from
lettuce
import
world
,
step
from
nose.tools
import
assert_equals
,
assert_in
from
lettuce.django
import
django_url
...
...
lms/djangoapps/courseware/tests/factories.py
View file @
6644b788
import
factory
from
student.models
import
(
User
,
UserProfile
,
Registration
,
CourseEnrollmentAllowed
)
from
courseware.models
import
StudentModule
from
django.contrib.auth.models
import
Group
from
datetime
import
datetime
import
uuid
...
...
@@ -47,3 +48,15 @@ class CourseEnrollmentAllowedFactory(factory.Factory):
email
=
'test@edx.org'
course_id
=
'edX/test/2012_Fall'
class
StudentModuleFactory
(
factory
.
Factory
):
FACTORY_FOR
=
StudentModule
module_type
=
"problem"
student
=
factory
.
SubFactory
(
UserFactory
)
course_id
=
"MITx/999/Robot_Super_Course"
state
=
None
grade
=
None
max_grade
=
None
done
=
'na'
lms/djangoapps/courseware/tests/tests.py
View file @
6644b788
...
...
@@ -55,7 +55,7 @@ def mongo_store_config(data_dir):
Use of this config requires mongo to be running
'''
return
{
store
=
{
'default'
:
{
'ENGINE'
:
'xmodule.modulestore.mongo.MongoModuleStore'
,
'OPTIONS'
:
{
...
...
@@ -68,6 +68,8 @@ def mongo_store_config(data_dir):
}
}
}
store
[
'direct'
]
=
store
[
'default'
]
return
store
def
draft_mongo_store_config
(
data_dir
):
...
...
@@ -83,6 +85,17 @@ def draft_mongo_store_config(data_dir):
'fs_root'
:
data_dir
,
'render_template'
:
'mitxmako.shortcuts.render_to_string'
,
}
},
'direct'
:
{
'ENGINE'
:
'xmodule.modulestore.mongo.MongoModuleStore'
,
'OPTIONS'
:
{
'default_class'
:
'xmodule.raw_module.RawDescriptor'
,
'host'
:
'localhost'
,
'db'
:
'test_xmodule'
,
'collection'
:
'modulestore'
,
'fs_root'
:
data_dir
,
'render_template'
:
'mitxmako.shortcuts.render_to_string'
,
}
}
}
...
...
lms/djangoapps/instructor/test_download_csv.py
0 → 100644
View file @
6644b788
"""
Unit tests for instructor dashboard
Based on (and depends on) unit tests for courseware.
Notes for running by hand:
django-admin.py test --settings=lms.envs.test --pythonpath=. 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
from
django.core.urlresolvers
import
reverse
from
courseware.access
import
_course_staff_group_name
from
courseware.tests.tests
import
LoginEnrollmentTestCase
,
TEST_DATA_XML_MODULESTORE
,
get_user
from
xmodule.modulestore.django
import
modulestore
import
xmodule.modulestore.django
@override_settings
(
MODULESTORE
=
TEST_DATA_XML_MODULESTORE
)
class
TestInstructorDashboardGradeDownloadCSV
(
LoginEnrollmentTestCase
):
'''
Check for download of csv
'''
def
setUp
(
self
):
xmodule
.
modulestore
.
django
.
_MODULESTORES
=
{}
self
.
full
=
modulestore
()
.
get_course
(
"edX/full/6.002_Spring_2012"
)
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
):
group_name
=
_course_staff_group_name
(
course
.
location
)
g
=
Group
.
objects
.
create
(
name
=
group_name
)
g
.
user_set
.
add
(
get_user
(
self
.
instructor
))
make_instructor
(
self
.
toy
)
self
.
logout
()
self
.
login
(
self
.
instructor
,
self
.
password
)
self
.
enroll
(
self
.
toy
)
def
test_download_grades_csv
(
self
):
course
=
self
.
toy
url
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
course
.
id
})
msg
=
"url = {0}
\n
"
.
format
(
url
)
response
=
self
.
client
.
post
(
url
,
{
'action'
:
'Download CSV of all student grades for this course'
})
msg
+=
"instructor dashboard download csv grades: response = '{0}'
\n
"
.
format
(
response
)
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
,
msg
)
cdisp
=
response
[
'Content-Disposition'
]
msg
+=
"Content-Disposition = '
%
s'
\n
"
%
cdisp
self
.
assertEqual
(
cdisp
,
'attachment; filename=grades_{0}.csv'
.
format
(
course
.
id
),
msg
)
body
=
response
.
content
.
replace
(
'
\r
'
,
''
)
msg
+=
"body = '{0}'
\n
"
.
format
(
body
)
# All the not-actually-in-the-course hw and labs come from the
# default grading policy string in graders.py
expected_body
=
'''"ID","Username","Full Name","edX email","External email","HW 01","HW 02","HW 03","HW 04","HW 05","HW 06","HW 07","HW 08","HW 09","HW 10","HW 11","HW 12","HW Avg","Lab 01","Lab 02","Lab 03","Lab 04","Lab 05","Lab 06","Lab 07","Lab 08","Lab 09","Lab 10","Lab 11","Lab 12","Lab Avg","Midterm","Final"
"2","u2","Fred Weasley","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"
'''
self
.
assertEqual
(
body
,
expected_body
,
msg
)
lms/djangoapps/instructor/tests/__init__.py
0 → 100644
View file @
6644b788
lms/djangoapps/instructor/tests/test_download_csv.py
0 → 100644
View file @
6644b788
"""
Unit tests for instructor dashboard
Based on (and depends on) unit tests for courseware.
Notes for running by hand:
django-admin.py test --settings=lms.envs.test --pythonpath=. 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
from
django.core.urlresolvers
import
reverse
from
courseware.access
import
_course_staff_group_name
from
courseware.tests.tests
import
LoginEnrollmentTestCase
,
TEST_DATA_XML_MODULESTORE
,
get_user
from
xmodule.modulestore.django
import
modulestore
import
xmodule.modulestore.django
@override_settings
(
MODULESTORE
=
TEST_DATA_XML_MODULESTORE
)
class
TestInstructorDashboardGradeDownloadCSV
(
LoginEnrollmentTestCase
):
'''
Check for download of csv
'''
def
setUp
(
self
):
xmodule
.
modulestore
.
django
.
_MODULESTORES
=
{}
self
.
full
=
modulestore
()
.
get_course
(
"edX/full/6.002_Spring_2012"
)
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
):
group_name
=
_course_staff_group_name
(
course
.
location
)
g
=
Group
.
objects
.
create
(
name
=
group_name
)
g
.
user_set
.
add
(
get_user
(
self
.
instructor
))
make_instructor
(
self
.
toy
)
self
.
logout
()
self
.
login
(
self
.
instructor
,
self
.
password
)
self
.
enroll
(
self
.
toy
)
def
test_download_grades_csv
(
self
):
course
=
self
.
toy
url
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
course
.
id
})
msg
=
"url = {0}
\n
"
.
format
(
url
)
response
=
self
.
client
.
post
(
url
,
{
'action'
:
'Download CSV of all student grades for this course'
})
msg
+=
"instructor dashboard download csv grades: response = '{0}'
\n
"
.
format
(
response
)
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
,
msg
)
cdisp
=
response
[
'Content-Disposition'
]
msg
+=
"Content-Disposition = '
%
s'
\n
"
%
cdisp
self
.
assertEqual
(
cdisp
,
'attachment; filename=grades_{0}.csv'
.
format
(
course
.
id
),
msg
)
body
=
response
.
content
.
replace
(
'
\r
'
,
''
)
msg
+=
"body = '{0}'
\n
"
.
format
(
body
)
# All the not-actually-in-the-course hw and labs come from the
# default grading policy string in graders.py
expected_body
=
'''"ID","Username","Full Name","edX email","External email","HW 01","HW 02","HW 03","HW 04","HW 05","HW 06","HW 07","HW 08","HW 09","HW 10","HW 11","HW 12","HW Avg","Lab 01","Lab 02","Lab 03","Lab 04","Lab 05","Lab 06","Lab 07","Lab 08","Lab 09","Lab 10","Lab 11","Lab 12","Lab Avg","Midterm","Final"
"2","u2","Fred Weasley","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"
'''
self
.
assertEqual
(
body
,
expected_body
,
msg
)
lms/djangoapps/instructor/tests.py
→
lms/djangoapps/instructor/tests
/test_forum_admin
.py
View file @
6644b788
"""
Unit tests for instructor dashboard
Based on (and depends on) unit tests for courseware.
Notes for running by hand:
django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/instructor
Unit tests for instructor dashboard forum administration
"""
from
django.test.utils
import
override_settings
# Need access to internal func to put users in the right group
...
...
@@ -24,63 +19,6 @@ from xmodule.modulestore.django import modulestore
import
xmodule.modulestore.django
@override_settings
(
MODULESTORE
=
TEST_DATA_XML_MODULESTORE
)
class
TestInstructorDashboardGradeDownloadCSV
(
LoginEnrollmentTestCase
):
'''
Check for download of csv
'''
def
setUp
(
self
):
xmodule
.
modulestore
.
django
.
_MODULESTORES
=
{}
self
.
full
=
modulestore
()
.
get_course
(
"edX/full/6.002_Spring_2012"
)
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
):
group_name
=
_course_staff_group_name
(
course
.
location
)
g
=
Group
.
objects
.
create
(
name
=
group_name
)
g
.
user_set
.
add
(
get_user
(
self
.
instructor
))
make_instructor
(
self
.
toy
)
self
.
logout
()
self
.
login
(
self
.
instructor
,
self
.
password
)
self
.
enroll
(
self
.
toy
)
def
test_download_grades_csv
(
self
):
course
=
self
.
toy
url
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
course
.
id
})
msg
=
"url = {0}
\n
"
.
format
(
url
)
response
=
self
.
client
.
post
(
url
,
{
'action'
:
'Download CSV of all student grades for this course'
})
msg
+=
"instructor dashboard download csv grades: response = '{0}'
\n
"
.
format
(
response
)
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
,
msg
)
cdisp
=
response
[
'Content-Disposition'
]
msg
+=
"Content-Disposition = '
%
s'
\n
"
%
cdisp
self
.
assertEqual
(
cdisp
,
'attachment; filename=grades_{0}.csv'
.
format
(
course
.
id
),
msg
)
body
=
response
.
content
.
replace
(
'
\r
'
,
''
)
msg
+=
"body = '{0}'
\n
"
.
format
(
body
)
# All the not-actually-in-the-course hw and labs come from the
# default grading policy string in graders.py
expected_body
=
'''"ID","Username","Full Name","edX email","External email","HW 01","HW 02","HW 03","HW 04","HW 05","HW 06","HW 07","HW 08","HW 09","HW 10","HW 11","HW 12","HW Avg","Lab 01","Lab 02","Lab 03","Lab 04","Lab 05","Lab 06","Lab 07","Lab 08","Lab 09","Lab 10","Lab 11","Lab 12","Lab Avg","Midterm","Final"
"2","u2","Fred Weasley","view2@test.com","","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"
'''
self
.
assertEqual
(
body
,
expected_body
,
msg
)
FORUM_ROLES
=
[
FORUM_ROLE_ADMINISTRATOR
,
FORUM_ROLE_MODERATOR
,
FORUM_ROLE_COMMUNITY_TA
]
FORUM_ADMIN_ACTION_SUFFIX
=
{
FORUM_ROLE_ADMINISTRATOR
:
'admin'
,
FORUM_ROLE_MODERATOR
:
'moderator'
,
FORUM_ROLE_COMMUNITY_TA
:
'community TA'
}
FORUM_ADMIN_USER
=
{
FORUM_ROLE_ADMINISTRATOR
:
'forumadmin'
,
FORUM_ROLE_MODERATOR
:
'forummoderator'
,
FORUM_ROLE_COMMUNITY_TA
:
'forummoderator'
}
...
...
@@ -208,4 +146,4 @@ class TestInstructorDashboardForumAdmin(LoginEnrollmentTestCase):
added_roles
.
append
(
rolename
)
added_roles
.
sort
()
roles
=
', '
.
join
(
added_roles
)
self
.
assertTrue
(
response
.
content
.
find
(
'<td>{0}</td>'
.
format
(
roles
))
>=
0
,
'not finding roles "{0}"'
.
format
(
roles
))
self
.
assertTrue
(
response
.
content
.
find
(
'<td>{0}</td>'
.
format
(
roles
))
>=
0
,
'not finding roles "{0}"'
.
format
(
roles
))
\ No newline at end of file
lms/djangoapps/instructor/tests/test_gradebook.py
0 → 100644
View file @
6644b788
"""
Tests of the instructor dashboard gradebook
"""
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
django.core.urlresolvers
import
reverse
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
,
UserProfileFactory
,
AdminFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
mock
import
patch
,
DEFAULT
from
courseware.tests.tests
import
TEST_DATA_MONGO_MODULESTORE
from
capa.tests.response_xml_factory
import
StringResponseXMLFactory
from
courseware.tests.factories
import
StudentModuleFactory
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.django
import
modulestore
USER_COUNT
=
11
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
class
TestGradebook
(
ModuleStoreTestCase
):
grading_policy
=
None
def
setUp
(
self
):
instructor
=
AdminFactory
.
create
()
self
.
client
.
login
(
username
=
instructor
.
username
,
password
=
'test'
)
modulestore
()
.
request_cache
=
modulestore
()
.
metadata_inheritance_cache_subsystem
=
None
course_data
=
{}
if
self
.
grading_policy
is
not
None
:
course_data
[
'grading_policy'
]
=
self
.
grading_policy
self
.
course
=
CourseFactory
.
create
(
data
=
course_data
)
chapter
=
ItemFactory
.
create
(
parent_location
=
self
.
course
.
location
,
template
=
"i4x://edx/templates/sequential/Empty"
,
)
section
=
ItemFactory
.
create
(
parent_location
=
chapter
.
location
,
template
=
"i4x://edx/templates/sequential/Empty"
,
metadata
=
{
'graded'
:
True
,
'format'
:
'Homework'
}
)
self
.
users
=
[
UserFactory
.
create
(
username
=
'robot
%
d'
%
i
,
email
=
'robot+test+
%
d@edx.org'
%
i
)
for
i
in
xrange
(
USER_COUNT
)]
for
user
in
self
.
users
:
UserProfileFactory
.
create
(
user
=
user
)
CourseEnrollmentFactory
.
create
(
user
=
user
,
course_id
=
self
.
course
.
id
)
for
i
in
xrange
(
USER_COUNT
-
1
):
template_name
=
"i4x://edx/templates/problem/Blank_Common_Problem"
item
=
ItemFactory
.
create
(
parent_location
=
section
.
location
,
template
=
template_name
,
data
=
StringResponseXMLFactory
()
.
build_xml
(
answer
=
'foo'
),
metadata
=
{
'rerandomize'
:
'always'
}
)
for
j
,
user
in
enumerate
(
self
.
users
):
StudentModuleFactory
.
create
(
grade
=
1
if
i
<
j
else
0
,
max_grade
=
1
,
student
=
user
,
course_id
=
self
.
course
.
id
,
module_state_key
=
Location
(
item
.
location
)
.
url
()
)
self
.
response
=
self
.
client
.
get
(
reverse
(
'gradebook'
,
args
=
(
self
.
course
.
id
,)))
def
test_response_code
(
self
):
self
.
assertEquals
(
self
.
response
.
status_code
,
200
)
class
TestDefaultGradingPolicy
(
TestGradebook
):
def
test_all_users_listed
(
self
):
for
user
in
self
.
users
:
self
.
assertIn
(
user
.
username
,
self
.
response
.
content
)
def
test_default_policy
(
self
):
# Default >= 50% passes, so Users 5-10 should be passing for Homework 1 [6]
# One use at the top of the page [1]
self
.
assertEquals
(
7
,
self
.
response
.
content
.
count
(
'grade_Pass'
))
# Users 1-5 attempted Homework 1 (and get Fs) [4]
# Users 1-10 attempted any homework (and get Fs) [10]
# Users 4-10 scored enough to not get rounded to 0 for the class (and get Fs) [7]
# One use at top of the page [1]
self
.
assertEquals
(
22
,
self
.
response
.
content
.
count
(
'grade_F'
))
# All other grades are None [29 categories * 11 users - 27 non-empty grades = 292]
# One use at the top of the page [1]
self
.
assertEquals
(
293
,
self
.
response
.
content
.
count
(
'grade_None'
))
class
TestLetterCutoffPolicy
(
TestGradebook
):
grading_policy
=
{
"GRADER"
:
[
{
"type"
:
"Homework"
,
"min_count"
:
1
,
"drop_count"
:
0
,
"short_label"
:
"HW"
,
"weight"
:
1
},
],
"GRADE_CUTOFFS"
:
{
'A'
:
.
9
,
'B'
:
.
8
,
'C'
:
.
7
,
'D'
:
.
6
,
}
}
def
test_styles
(
self
):
self
.
assertIn
(
"grade_A {color:green;}"
,
self
.
response
.
content
)
self
.
assertIn
(
"grade_B {color:Chocolate;}"
,
self
.
response
.
content
)
self
.
assertIn
(
"grade_C {color:DarkSlateGray;}"
,
self
.
response
.
content
)
self
.
assertIn
(
"grade_D {color:DarkSlateGray;}"
,
self
.
response
.
content
)
def
test_assigned_grades
(
self
):
print
self
.
response
.
content
# Users 9-10 have >= 90% on Homeworks [2]
# Users 9-10 have >= 90% on the class [2]
# One use at the top of the page [1]
self
.
assertEquals
(
5
,
self
.
response
.
content
.
count
(
'grade_A'
))
# User 8 has 80 <= Homeworks < 90 [1]
# User 8 has 80 <= class < 90 [1]
# One use at the top of the page [1]
self
.
assertEquals
(
3
,
self
.
response
.
content
.
count
(
'grade_B'
))
# User 7 has 70 <= Homeworks < 80 [1]
# User 7 has 70 <= class < 80 [1]
# One use at the top of the page [1]
self
.
assertEquals
(
3
,
self
.
response
.
content
.
count
(
'grade_C'
))
# User 6 has 60 <= Homeworks < 70 [1]
# User 6 has 60 <= class < 70 [1]
# One use at the top of the page [1]
self
.
assertEquals
(
3
,
self
.
response
.
content
.
count
(
'grade_C'
))
# Users 1-5 have 60% > grades > 0 on Homeworks [5]
# Users 1-5 have 60% > grades > 0 on the class [5]
# One use at top of the page [1]
self
.
assertEquals
(
11
,
self
.
response
.
content
.
count
(
'grade_F'
))
# User 0 has 0 on Homeworks [1]
# User 0 has 0 on the class [1]
# One use at the top of the page [1]
self
.
assertEquals
(
3
,
self
.
response
.
content
.
count
(
'grade_None'
))
\ No newline at end of file
lms/djangoapps/instructor/views.py
View file @
6644b788
...
...
@@ -961,11 +961,14 @@ def gradebook(request, course_id):
}
for
student
in
enrolled_students
]
return
render_to_response
(
'courseware/gradebook.html'
,
{
'students'
:
student_info
,
'course'
:
course
,
'course_id'
:
course_id
,
# Checked above
'staff_access'
:
True
,
})
return
render_to_response
(
'courseware/gradebook.html'
,
{
'students'
:
student_info
,
'course'
:
course
,
'course_id'
:
course_id
,
# Checked above
'staff_access'
:
True
,
'ordered_grades'
:
sorted
(
course
.
grade_cutoffs
.
items
(),
key
=
lambda
i
:
i
[
1
],
reverse
=
True
),
})
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
...
...
lms/templates/courseware/gradebook.html
View file @
6644b788
...
...
@@ -13,9 +13,12 @@
<
%
static:css
group=
'course'
/>
<style
type=
"text/css"
>
.grade_A
{
color
:
green
;}
.grade_B
{
color
:
Chocolate
;}
.grade_C
{
color
:
DarkSlateGray
;}
%
for
(
grade
,
_
),
color
in
zip
(
ordered_grades
,
[
'green'
,
'Chocolate'
]):
.grade_
$
{
grade
}
{
color
:
${
color
}
;
}
%
endfor
%
for
(
grade
,
_
)
in
ordered_grades
[
2
:]:
.grade_
$
{
grade
}
{
color
:
DarkSlateGray
;}
%
endfor
.grade_F
{
color
:
DimGray
;}
.grade_None
{
color
:
LightGray
;}
</style>
...
...
@@ -78,8 +81,8 @@
letter_grade =
'None'
if
fraction
>
0:
letter_grade = 'F'
for
grade in ['A', 'B', 'C']
:
if fraction >= c
ourse.grade_cutoffs[grade]
:
for
(grade, cutoff) in ordered_grades
:
if fraction >= c
utoff
:
letter_grade = grade
break
...
...
@@ -90,11 +93,11 @@
<tbody>
%for student in students:
<tr>
<tr>
%for section in student['grade_summary']['section_breakdown']:
${percent_data( section['percent'] )}
%endfor
<td>
${percent_data( student['grade_summary']['percent'])}
</td>
${percent_data( student['grade_summary']['percent'])}
</tr>
%endfor
</tbody>
...
...
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