Commit abcd6122 by Victor Shnayder

Only show student emails and full names to instructors in internal courses

- includes a bit of test refactoring: look up courses by id, not name
parent 8500a1b3
...@@ -312,6 +312,11 @@ class ModuleStore(object): ...@@ -312,6 +312,11 @@ class ModuleStore(object):
''' '''
raise NotImplementedError raise NotImplementedError
def get_course(self, course_id):
'''
Look for a specific course id. Returns the course descriptor, or None if not found.
'''
raise NotImplementedError
def get_parent_locations(self, location): def get_parent_locations(self, location):
'''Find all locations that are the parents of this location. Needed '''Find all locations that are the parents of this location. Needed
...@@ -357,3 +362,10 @@ class ModuleStoreBase(ModuleStore): ...@@ -357,3 +362,10 @@ class ModuleStoreBase(ModuleStore):
errorlog = self._get_errorlog(location) errorlog = self._get_errorlog(location)
return errorlog.errors return errorlog.errors
def get_course(self, course_id):
"""Default impl--linear search through course list"""
for c in self.get_courses():
if c.id == course_id:
return c
return None
...@@ -292,14 +292,10 @@ class TestNavigation(PageLoader): ...@@ -292,14 +292,10 @@ class TestNavigation(PageLoader):
def setUp(self): def setUp(self):
xmodule.modulestore.django._MODULESTORES = {} xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses()
def find_course(course_id): # Assume courses are there
"""Assumes the course is present""" self.full = modulestore().get_course("edX/full/6.002_Spring_2012")
return [c for c in courses if c.id==course_id][0] self.toy = modulestore().get_course("edX/toy/2012_Fall")
self.full = find_course("edX/full/6.002_Spring_2012")
self.toy = find_course("edX/toy/2012_Fall")
# Create two accounts # Create two accounts
self.student = 'view@test.com' self.student = 'view@test.com'
...@@ -350,14 +346,9 @@ class TestViewAuth(PageLoader): ...@@ -350,14 +346,9 @@ class TestViewAuth(PageLoader):
def setUp(self): def setUp(self):
xmodule.modulestore.django._MODULESTORES = {} xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses()
def find_course(course_id):
"""Assumes the course is present"""
return [c for c in courses if c.id==course_id][0]
self.full = find_course("edX/full/6.002_Spring_2012") self.full = modulestore().get_course("edX/full/6.002_Spring_2012")
self.toy = find_course("edX/toy/2012_Fall") self.toy = modulestore().get_course("edX/toy/2012_Fall")
# Create two accounts # Create two accounts
self.student = 'view@test.com' self.student = 'view@test.com'
......
...@@ -35,12 +35,8 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): ...@@ -35,12 +35,8 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
xmodule.modulestore.django._MODULESTORES = {} xmodule.modulestore.django._MODULESTORES = {}
courses = modulestore().get_courses() courses = modulestore().get_courses()
def find_course(name): self.full = modulestore().get_course("edX/full/6.002_Spring_2012")
"""Assumes the course is present""" self.toy = modulestore().get_course("edX/toy/2012_Fall")
return [c for c in courses if c.location.course==name][0]
self.full = find_course("full")
self.toy = find_course("toy")
# Create two accounts # Create two accounts
self.student = 'view@test.com' self.student = 'view@test.com'
...@@ -51,9 +47,12 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): ...@@ -51,9 +47,12 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
self.activate_user(self.student) self.activate_user(self.student)
self.activate_user(self.instructor) self.activate_user(self.instructor)
group_name = _course_staff_group_name(self.toy.location) def make_instructor(course):
g = Group.objects.create(name=group_name) group_name = _course_staff_group_name(course.location)
g.user_set.add(ct.user(self.instructor)) g = Group.objects.create(name=group_name)
g.user_set.add(ct.user(self.instructor))
make_instructor(self.toy)
self.logout() self.logout()
self.login(self.instructor, self.password) self.login(self.instructor, self.password)
...@@ -62,7 +61,21 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): ...@@ -62,7 +61,21 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
def test_download_grades_csv(self): def test_download_grades_csv(self):
print "running test_download_grades_csv" print "running test_download_grades_csv"
course = self.toy toy = self.toy
self.check_download_grades_csv(toy, toy.id in settings.INTERNAL_COURSE_IDS)
@override_settings(INTERNAL_COURSE_IDS=set())
def test_download_external_grades_csv(self):
"""
Test with an external course--should return less stuff.
"""
print "running test_download_grades_csv"
toy = self.toy
self.check_download_grades_csv(toy, toy.id in settings.INTERNAL_COURSE_IDS)
def check_download_grades_csv(self, course, internal):
print "checking course {0}".format(course.id)
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
msg = "url = %s\n" % url msg = "url = %s\n" % url
response = self.client.post(url, {'action': 'Download CSV of all student grades for this course', response = self.client.post(url, {'action': 'Download CSV of all student grades for this course',
...@@ -71,14 +84,20 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader): ...@@ -71,14 +84,20 @@ class TestInstructorDashboardGradeDownloadCSV(ct.PageLoader):
self.assertEqual(response['Content-Type'],'text/csv',msg) self.assertEqual(response['Content-Type'],'text/csv',msg)
cdisp = response['Content-Disposition'].replace('TT_2012','2012') # jenkins course_id is TT_2012_Fall instead of 2012_Fall? cdisp = response['Content-Disposition']
msg += "cdisp = '%s'\n" % cdisp msg += "Content-Disposition = '%s'\n" % cdisp
self.assertEqual(cdisp,'attachment; filename=grades_edX/toy/2012_Fall.csv',msg) self.assertEqual(cdisp, 'attachment; filename=grades_{0}.csv'.format(course.id), msg)
body = response.content.replace('\r','') body = response.content.replace('\r','')
msg += "body = '%s'\n" % body msg += "body = '%s'\n" % body
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" # if internal, expect some more info.
"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","0.0" extra1 = '"Full Name","edX email","External email",' if internal else ''
''' extra2 = '"Fred Weasley","view2@test.com","",' if internal else ''
# All the not-actually-in-the-course hw and labs come from the
# default grading policy string in graders.py
expected_body = '''"ID","Username",{0}"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",{1}"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","0.0"
'''.format(extra1, extra2)
self.assertEqual(body, expected_body, msg) self.assertEqual(body, expected_body, msg)
...@@ -81,7 +81,7 @@ def instructor_dashboard(request, course_id): ...@@ -81,7 +81,7 @@ def instructor_dashboard(request, course_id):
try: try:
group = Group.objects.get(name=staffgrp) group = Group.objects.get(name=staffgrp)
except Group.DoesNotExist: except Group.DoesNotExist:
group = Group(name=staffgrp) # create the group group = Group(name=staffgrp) # create the group
group.save() group.save()
return group return group
...@@ -217,11 +217,18 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True, ...@@ -217,11 +217,18 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
If get_raw_scores=True, then instead of grade summaries, the raw grades for all graded modules are returned. If get_raw_scores=True, then instead of grade summaries, the raw grades for all graded modules are returned.
If the course_id is in INTERNAL_COURSE_IDS, includes student
emails and full names. Otherwise, just returns the usernames and
grades. This may change pending future policy decisions about data access.
''' '''
enrolled_students = User.objects.filter(courseenrollment__course_id=course_id).order_by('username') enrolled_students = User.objects.filter(courseenrollment__course_id=course_id).order_by('username')
header = ['ID', 'Username', 'Full Name', 'edX email', 'External email'] internal = course_id in settings.INTERNAL_COURSE_IDS
if get_grades: if internal:
header = ['ID', 'Username', 'Full Name', 'edX email', 'External email']
else:
header = ['ID', 'Username']
if get_grades and enrolled_students.count() > 0:
# just to construct the header # just to construct the header
gradeset = grades.grade(enrolled_students[0], request, course, keep_raw_scores=get_raw_scores) gradeset = grades.grade(enrolled_students[0], request, course, keep_raw_scores=get_raw_scores)
# log.debug('student %s gradeset %s' % (enrolled_students[0], gradeset)) # log.debug('student %s gradeset %s' % (enrolled_students[0], gradeset))
...@@ -234,11 +241,14 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True, ...@@ -234,11 +241,14 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
data = [] data = []
for student in enrolled_students: for student in enrolled_students:
datarow = [ student.id, student.username, student.profile.name, student.email ] if internal:
try: datarow = [student.id, student.username, student.profile.name, student.email]
datarow.append(student.externalauthmap.external_email) try:
except: # ExternalAuthMap.DoesNotExist datarow.append(student.externalauthmap.external_email)
datarow.append('') except: # ExternalAuthMap.DoesNotExist
datarow.append('')
else:
datarow = [student.id, student.username]
if get_grades: if get_grades:
gradeset = grades.grade(student, request, course, keep_raw_scores=get_raw_scores) gradeset = grades.grade(student, request, course, keep_raw_scores=get_raw_scores)
...@@ -267,11 +277,16 @@ def gradebook(request, course_id): ...@@ -267,11 +277,16 @@ def gradebook(request, course_id):
# TODO (vshnayder): implement pagination. # TODO (vshnayder): implement pagination.
enrolled_students = enrolled_students[:1000] # HACK! enrolled_students = enrolled_students[:1000] # HACK!
internal = course_id in settings.INTERNAL_COURSE_IDS
def tooltip(student):
if internal:
return '{0} <{1}>'.format(student.profile.name, student.email)
return ""
student_info = [{'username': student.username, student_info = [{'username': student.username,
'id': student.id, 'id': student.id,
'email': student.email,
'grade_summary': grades.grade(student, request, course), 'grade_summary': grades.grade(student, request, course),
'realname': student.profile.name, 'tooltip': tooltip(student),
} }
for student in enrolled_students] for student in enrolled_students]
......
...@@ -93,6 +93,11 @@ GENERATE_PROFILE_SCORES = False ...@@ -93,6 +93,11 @@ GENERATE_PROFILE_SCORES = False
# Used with XQueue # Used with XQueue
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
# Used for various access control decisions. Should be a set of course_ids.
# This is not a good long-term solution--eventually this needs to be set in a db somewhere
INTERNAL_COURSE_IDS = set()
############################# SET PATH INFORMATION ############################# ############################# SET PATH INFORMATION #############################
PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/lms PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/lms
REPO_ROOT = PROJECT_ROOT.dirname() REPO_ROOT = PROJECT_ROOT.dirname()
...@@ -126,7 +131,7 @@ NODE_PATH = ':'.join(node_paths) ...@@ -126,7 +131,7 @@ NODE_PATH = ':'.join(node_paths)
############################ OpenID Provider ################################## ############################ OpenID Provider ##################################
OPENID_PROVIDER_TRUSTED_ROOTS = ['cs50.net', '*.cs50.net'] OPENID_PROVIDER_TRUSTED_ROOTS = ['cs50.net', '*.cs50.net']
################################## MITXWEB ##################################### ################################## MITXWEB #####################################
# This is where we stick our compiled template files. Most of the app uses Mako # This is where we stick our compiled template files. Most of the app uses Mako
...@@ -156,7 +161,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ...@@ -156,7 +161,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'askbot.user_messages.context_processors.user_messages',#must be before auth 'askbot.user_messages.context_processors.user_messages',#must be before auth
'django.contrib.auth.context_processors.auth', #this is required for admin 'django.contrib.auth.context_processors.auth', #this is required for admin
'django.core.context_processors.csrf', #necessary for csrf protection 'django.core.context_processors.csrf', #necessary for csrf protection
# Added for django-wiki # Added for django-wiki
'django.core.context_processors.media', 'django.core.context_processors.media',
'django.core.context_processors.tz', 'django.core.context_processors.tz',
...@@ -343,7 +348,7 @@ WIKI_CAN_ASSIGN = lambda article, user: user.is_staff or user.is_superuser ...@@ -343,7 +348,7 @@ WIKI_CAN_ASSIGN = lambda article, user: user.is_staff or user.is_superuser
WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False
WIKI_LINK_LIVE_LOOKUPS = False WIKI_LINK_LIVE_LOOKUPS = False
WIKI_LINK_DEFAULT_LEVEL = 2 WIKI_LINK_DEFAULT_LEVEL = 2
################################# Jasmine ################################### ################################# Jasmine ###################################
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
...@@ -360,10 +365,10 @@ STATICFILES_FINDERS = ( ...@@ -360,10 +365,10 @@ STATICFILES_FINDERS = (
TEMPLATE_LOADERS = ( TEMPLATE_LOADERS = (
'mitxmako.makoloader.MakoFilesystemLoader', 'mitxmako.makoloader.MakoFilesystemLoader',
'mitxmako.makoloader.MakoAppDirectoriesLoader', 'mitxmako.makoloader.MakoAppDirectoriesLoader',
# 'django.template.loaders.filesystem.Loader', # 'django.template.loaders.filesystem.Loader',
# 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.app_directories.Loader',
#'askbot.skins.loaders.filesystem_load_template_source', #'askbot.skins.loaders.filesystem_load_template_source',
# 'django.template.loaders.eggs.Loader', # 'django.template.loaders.eggs.Loader',
) )
...@@ -381,7 +386,7 @@ MIDDLEWARE_CLASSES = ( ...@@ -381,7 +386,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'track.middleware.TrackMiddleware', 'track.middleware.TrackMiddleware',
'mitxmako.middleware.MakoMiddleware', 'mitxmako.middleware.MakoMiddleware',
'course_wiki.course_nav.Middleware', 'course_wiki.course_nav.Middleware',
'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware', 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware',
...@@ -609,7 +614,7 @@ INSTALLED_APPS = ( ...@@ -609,7 +614,7 @@ INSTALLED_APPS = (
'util', 'util',
'certificates', 'certificates',
'instructor', 'instructor',
#For the wiki #For the wiki
'wiki', # The new django-wiki from benjaoming 'wiki', # The new django-wiki from benjaoming
'django_notify', 'django_notify',
......
...@@ -92,6 +92,12 @@ COURSE_LISTINGS = { ...@@ -92,6 +92,12 @@ COURSE_LISTINGS = {
} }
INTERNAL_COURSE_IDS = set(['BerkeleyX/CS169/fa12',
'BerkeleyX/CS188/fa12',
'HarvardX/CS50x/2012H',
'MITx/3.091/MIT_2012_Fall',
'MITx/6.002x-EE98/2012_Fall_SJSU'])
SUBDOMAIN_BRANDING = { SUBDOMAIN_BRANDING = {
'sjsu': 'MITx', 'sjsu': 'MITx',
'mit': 'MITx', 'mit': 'MITx',
......
...@@ -25,6 +25,8 @@ WIKI_ENABLED = True ...@@ -25,6 +25,8 @@ WIKI_ENABLED = True
# Makes the tests run much faster... # Makes the tests run much faster...
SOUTH_TESTS_MIGRATE = False # To disable migrations and use syncdb instead SOUTH_TESTS_MIGRATE = False # To disable migrations and use syncdb instead
INTERNAL_COURSE_IDS = set(['edX/toy/2012_Fall'])
# Nose Test Runner # Nose Test Runner
INSTALLED_APPS += ('django_nose',) INSTALLED_APPS += ('django_nose',)
NOSE_ARGS = ['--cover-erase', '--with-xunit', '--with-xcoverage', '--cover-html', NOSE_ARGS = ['--cover-erase', '--with-xunit', '--with-xcoverage', '--cover-html',
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
%for student in students: %for student in students:
<tr> <tr>
<td> <td>
<a href="${reverse('student_progress', kwargs=dict(course_id=course_id, student_id=student['id']))}">${student['username']}</a> <a href="${reverse('student_progress', kwargs=dict(course_id=course_id, student_id=student['id']))}" title="${student['tooltip']}">${student['username']}</a>
</td> </td>
</tr> </tr>
%endfor %endfor
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
<tbody> <tbody>
%for student in students: %for student in students:
<tr> <tr>
%for section in student['grade_summary']['section_breakdown']: %for section in student['grade_summary']['section_breakdown']:
${percent_data( section['percent'] )} ${percent_data( section['percent'] )}
%endfor %endfor
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment