def upload_students_csv(_xmodule_instance_args, _entry_id, course_id, task_input, action_name): """ For a given `course_id`, generate a CSV file containing profile information for all students that are enrolled, and store using a `ReportStore`. """ start_time = time() start_date = datetime.now(UTC) task_progress = TaskProgress(action_name, CourseEnrollment.num_enrolled_in(course_id), start_time) current_step = {'step': 'Calculating Profile Info'} task_progress.update_task_state(extra_meta=current_step) # compute the student features table and format it query_features = task_input.get('features') student_data = enrolled_students_features(course_id, query_features) header, rows = format_dictlist(student_data, query_features) task_progress.attempted = task_progress.succeeded = len(rows) task_progress.skipped = task_progress.total - task_progress.attempted rows.insert(0, header) current_step = {'step': 'Uploading CSV'} task_progress.update_task_state(extra_meta=current_step) # Perform the upload upload_csv_to_report_store(rows, 'student_profile_info', course_id, start_date) return task_progress.update_task_state(extra_meta=current_step)
def _section_course_info(course_id, access): """ Provide data for the corresponding dashboard section """ course = get_course_by_id(course_id, depth=None) course_id_dict = Location.parse_course_id(course_id) section_data = { 'section_key': 'course_info', 'section_display_name': _('Course Info'), 'access': access, 'course_id': course_id, 'course_org': course_id_dict['org'], 'course_num': course_id_dict['course'], 'course_name': course_id_dict['name'], 'course_display_name': course.display_name, 'enrollment_count': CourseEnrollment.num_enrolled_in(course_id), 'has_started': course.has_started(), 'has_ended': course.has_ended(), 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}), } try: advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo section_data['grade_cutoffs'] = reduce(advance, course.grade_cutoffs.items(), "")[:-2] except Exception: section_data['grade_cutoffs'] = "Not Available" # section_data['offline_grades'] = offline_grades_available(course_id) try: section_data['course_errors'] = [(escape(a), '') for (a, _unused) in modulestore().get_item_errors(course.location)] except Exception: section_data['course_errors'] = [('Error fetching errors', '')] return section_data
def _section_student_admin(course_id, access): """ Provide data for the corresponding dashboard section """ is_small_course = False enrollment_count = CourseEnrollment.num_enrolled_in(course_id) max_enrollment_for_buttons = settings.FEATURES.get( "MAX_ENROLLMENT_INSTR_BUTTONS") if max_enrollment_for_buttons is not None: is_small_course = enrollment_count <= max_enrollment_for_buttons section_data = { 'section_key': 'student_admin', 'section_display_name': _('Student Admin'), 'access': access, 'is_small_course': is_small_course, 'get_student_progress_url_url': reverse('get_student_progress_url', kwargs={'course_id': course_id}), 'enrollment_url': reverse('students_update_enrollment', kwargs={'course_id': course_id}), 'reset_student_attempts_url': reverse('reset_student_attempts', kwargs={'course_id': course_id}), 'rescore_problem_url': reverse('rescore_problem', kwargs={'course_id': course_id}), 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_id}), 'spoc_gradebook_url': reverse('spoc_gradebook', kwargs={'course_id': course_id}), } return section_data
def _is_small_course(course_key): """ Compares against MAX_ENROLLMENT_INSTR_BUTTONS to determine if course enrollment is considered small. """ is_small_course = False enrollment_count = CourseEnrollment.num_enrolled_in(course_key) max_enrollment_for_buttons = settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS") if max_enrollment_for_buttons is not None: is_small_course = enrollment_count <= max_enrollment_for_buttons return is_small_course
def _section_metrics(course, access): """Provide data for the corresponding dashboard section """ course_key = course.id section_data = { 'section_key': 'metrics', 'section_display_name': _('Metrics'), 'access': access, 'course_id': unicode(course_key), 'sub_section_display_name': get_section_display_name(course_key), 'section_has_problem': get_array_section_has_problem(course_key), 'get_students_opened_subsection_url': reverse('get_students_opened_subsection'), 'get_students_problem_grades_url': reverse('get_students_problem_grades'), 'post_metrics_data_csv_url': reverse('post_metrics_data_csv'), 'enrollment': CourseEnrollment.num_enrolled_in(course_key), } return section_data
def _section_student_admin(course_key, access): """ Provide data for the corresponding dashboard section """ is_small_course = False enrollment_count = CourseEnrollment.num_enrolled_in(course_key) max_enrollment_for_buttons = settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS") if max_enrollment_for_buttons is not None: is_small_course = enrollment_count <= max_enrollment_for_buttons section_data = { 'section_key': 'student_admin', 'section_display_name': _('Student Admin'), 'access': access, 'is_small_course': is_small_course, 'get_student_progress_url_url': reverse('get_student_progress_url', kwargs={'course_id': course_key.to_deprecated_string()}), 'enrollment_url': reverse('students_update_enrollment', kwargs={'course_id': course_key.to_deprecated_string()}), 'reset_student_attempts_url': reverse('reset_student_attempts', kwargs={'course_id': course_key.to_deprecated_string()}), 'rescore_problem_url': reverse('rescore_problem', kwargs={'course_id': course_key.to_deprecated_string()}), 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': course_key.to_deprecated_string()}), 'spoc_gradebook_url': reverse('spoc_gradebook', kwargs={'course_id': course_key.to_deprecated_string()}), } return section_data
def _section_course_info(course_id, access): """ Provide data for the corresponding dashboard section """ course = get_course_by_id(course_id, depth=None) course_org, course_num, course_name = course_id.split("/") section_data = { "section_key": "course_info", "section_display_name": _("Course Info"), "access": access, "course_id": course_id, "course_org": course_org, "course_num": course_num, "course_name": course_name, "course_display_name": course.display_name, "enrollment_count": CourseEnrollment.num_enrolled_in(course_id), "has_started": course.has_started(), "has_ended": course.has_ended(), "list_instructor_tasks_url": reverse("list_instructor_tasks", kwargs={"course_id": course_id}), } try: advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo section_data["grade_cutoffs"] = reduce(advance, course.grade_cutoffs.items(), "")[:-2] except Exception: section_data["grade_cutoffs"] = "Not Available" # section_data['offline_grades'] = offline_grades_available(course_id) try: section_data["course_errors"] = [ (escape(a), "") for (a, _unused) in modulestore().get_item_errors(course.location) ] except Exception: section_data["course_errors"] = [("Error fetching errors", "")] return section_data
def instructor_dashboard(request, course_id): """Display the instructor dashboard for a course.""" course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'staff', course_key, depth=None) instructor_access = has_access(request.user, 'instructor', course) # an instructor can manage staff lists forum_admin_access = has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR) msg = '' show_email_tab = False problems = [] plots = [] datatable = {} # the instructor dashboard page is modal: grades, psychometrics, admin # keep that state in request.session (defaults to grades mode) idash_mode = request.POST.get('idash_mode', '') idash_mode_key = u'idash_mode:{0}'.format(course_id) if idash_mode: request.session[idash_mode_key] = idash_mode else: idash_mode = request.session.get(idash_mode_key, 'Grades') enrollment_number = CourseEnrollment.num_enrolled_in(course_key) # assemble some course statistics for output to instructor def get_course_stats_table(): datatable = { 'header': ['Statistic', 'Value'], 'title': _('Course Statistics At A Glance'), } data = [['Date', timezone.now().isoformat()]] data += compute_course_stats(course).items() if request.user.is_staff: for field in course.fields.values(): if getattr(field.scope, 'user', False): continue data.append([ field.name, json.dumps(field.read_json(course), cls=i4xEncoder) ]) datatable['data'] = data return datatable def return_csv(func, datatable, file_pointer=None): """Outputs a CSV file from the contents of a datatable.""" if file_pointer is None: response = HttpResponse(mimetype='text/csv') response['Content-Disposition'] = (u'attachment; filename={0}'.format(func)).encode('utf-8') else: response = file_pointer writer = csv.writer(response, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL) encoded_row = [unicode(s).encode('utf-8') for s in datatable['header']] writer.writerow(encoded_row) for datarow in datatable['data']: # 's' here may be an integer, float (eg score) or string (eg student name) encoded_row = [ # If s is already a UTF-8 string, trying to make a unicode # object out of it will fail unless we pass in an encoding to # the constructor. But we can't do that across the board, # because s is often a numeric type. So just do this. s if isinstance(s, str) else unicode(s).encode('utf-8') for s in datarow ] writer.writerow(encoded_row) return response # process actions from form POST action = request.POST.get('action', '') use_offline = request.POST.get('use_offline_grades', False) if settings.FEATURES['ENABLE_MANUAL_GIT_RELOAD']: if 'GIT pull' in action: data_dir = course.data_dir log.debug('git pull {0}'.format(data_dir)) gdir = settings.DATA_DIR / data_dir if not os.path.exists(gdir): msg += "====> ERROR in gitreload - no such directory {0}".format(gdir) else: cmd = "cd {0}; git reset --hard HEAD; git clean -f -d; git pull origin; chmod g+w course.xml".format(gdir) msg += "git pull on {0}:<p>".format(data_dir) msg += "<pre>{0}</pre></p>".format(escape(os.popen(cmd).read())) track.views.server_track(request, "git-pull", {"directory": data_dir}, page="idashboard") if 'Reload course' in action: log.debug('reloading {0} ({1})'.format(course_key, course)) try: data_dir = course.data_dir modulestore().try_load_course(data_dir) msg += "<br/><p>Course reloaded from {0}</p>".format(data_dir) track.views.server_track(request, "reload", {"directory": data_dir}, page="idashboard") course_errors = modulestore().get_course_errors(course.id) msg += '<ul>' for cmsg, cerr in course_errors: msg += "<li>{0}: <pre>{1}</pre>".format(cmsg, escape(cerr)) msg += '</ul>' except Exception as err: # pylint: disable=broad-except msg += '<br/><p>Error: {0}</p>'.format(escape(err)) if action == 'Dump list of enrolled students' or action == 'List enrolled students': log.debug(action) datatable = get_student_grade_summary_data(request, course, get_grades=False, use_offline=use_offline) datatable['title'] = _('List of students enrolled in {course_key}').format(course_key=course_key.to_deprecated_string()) track.views.server_track(request, "list-students", {}, page="idashboard") elif 'Dump all RAW grades' in action: log.debug(action) datatable = get_student_grade_summary_data(request, course, get_grades=True, get_raw_scores=True, use_offline=use_offline) datatable['title'] = _('Raw Grades of students enrolled in {course_key}').format(course_key=course_key) track.views.server_track(request, "dump-grades-raw", {}, page="idashboard") elif 'Download CSV of all RAW grades' in action: track.views.server_track(request, "dump-grades-csv-raw", {}, page="idashboard") return return_csv('grades_{0}_raw.csv'.format(course_key.to_deprecated_string()), get_student_grade_summary_data(request, course, get_raw_scores=True, use_offline=use_offline)) elif 'Download CSV of answer distributions' in action: track.views.server_track(request, "dump-answer-dist-csv", {}, page="idashboard") return return_csv('answer_dist_{0}.csv'.format(course_key.to_deprecated_string()), get_answers_distribution(request, course_key)) #---------------------------------------- # export grades to remote gradebook elif action == 'List assignments available in remote gradebook': msg2, datatable = _do_remote_gradebook(request.user, course, 'get-assignments') msg += msg2 elif action == 'List assignments available for this course': log.debug(action) allgrades = get_student_grade_summary_data(request, course, get_grades=True, use_offline=use_offline) assignments = [[x] for x in allgrades['assignments']] datatable = {'header': [_('Assignment Name')]} datatable['data'] = assignments datatable['title'] = action msg += 'assignments=<pre>%s</pre>' % assignments elif action == 'List enrolled students matching remote gradebook': stud_data = get_student_grade_summary_data(request, course, get_grades=False, use_offline=use_offline) msg2, rg_stud_data = _do_remote_gradebook(request.user, course, 'get-membership') datatable = {'header': ['Student email', 'Match?']} rg_students = [x['email'] for x in rg_stud_data['retdata']] def domatch(student): """Returns 'yes' if student is pressent in the remote gradebook student list, else returns 'No'""" return 'yes' if student.email in rg_students else 'No' datatable['data'] = [[x.email, domatch(x)] for x in stud_data['students']] datatable['title'] = action elif action in ['Display grades for assignment', 'Export grades for assignment to remote gradebook', 'Export CSV file of grades for assignment']: log.debug(action) datatable = {} aname = request.POST.get('assignment_name', '') if not aname: msg += "<font color='red'>{text}</font>".format(text=_("Please enter an assignment name")) else: allgrades = get_student_grade_summary_data(request, course, get_grades=True, use_offline=use_offline) if aname not in allgrades['assignments']: msg += "<font color='red'>{text}</font>".format( text=_("Invalid assignment name '{name}'").format(name=aname) ) else: aidx = allgrades['assignments'].index(aname) datatable = {'header': [_('External email'), aname]} ddata = [] for student in allgrades['students']: # do one by one in case there is a student who has only partial grades try: ddata.append([student.email, student.grades[aidx]]) except IndexError: log.debug('No grade for assignment {idx} ({name}) for student {email}'.format( idx=aidx, name=aname, email=student.email) ) datatable['data'] = ddata datatable['title'] = _('Grades for assignment "{name}"').format(name=aname) if 'Export CSV' in action: # generate and return CSV file return return_csv('grades {name}.csv'.format(name=aname), datatable) elif 'remote gradebook' in action: file_pointer = StringIO() return_csv('', datatable, file_pointer=file_pointer) file_pointer.seek(0) files = {'datafile': file_pointer} msg2, __ = _do_remote_gradebook(request.user, course, 'post-grades', files=files) msg += msg2 #---------------------------------------- # DataDump elif 'Download CSV of all responses to problem' in action: problem_to_dump = request.POST.get('problem_to_dump', '') if problem_to_dump[-4:] == ".xml": problem_to_dump = problem_to_dump[:-4] try: module_state_key = course_key.make_usage_key_from_deprecated_string(problem_to_dump) smdat = StudentModule.objects.filter( course_id=course_key, module_state_key=module_state_key ) smdat = smdat.order_by('student') msg += _("Found {num} records to dump.").format(num=smdat) except Exception as err: # pylint: disable=broad-except msg += "<font color='red'>{text}</font><pre>{err}</pre>".format( text=_("Couldn't find module with that urlname."), err=escape(err) ) smdat = [] if smdat: datatable = {'header': ['username', 'state']} datatable['data'] = [[x.student.username, x.state] for x in smdat] datatable['title'] = _('Student state for problem {problem}').format(problem=problem_to_dump) return return_csv('student_state_from_{problem}.csv'.format(problem=problem_to_dump), datatable) #---------------------------------------- # enrollment elif action == 'List students who may enroll but may not have yet signed up': ceaset = CourseEnrollmentAllowed.objects.filter(course_id=course_key) datatable = {'header': ['StudentEmail']} datatable['data'] = [[x.email] for x in ceaset] datatable['title'] = action elif action == 'Enroll multiple students': is_shib_course = uses_shib(course) students = request.POST.get('multiple_students', '') auto_enroll = bool(request.POST.get('auto_enroll')) email_students = bool(request.POST.get('email_students')) secure = request.is_secure() ret = _do_enroll_students(course, course_key, students, secure=secure, auto_enroll=auto_enroll, email_students=email_students, is_shib_course=is_shib_course) datatable = ret['datatable'] elif action == 'Unenroll multiple students': students = request.POST.get('multiple_students', '') email_students = bool(request.POST.get('email_students')) ret = _do_unenroll_students(course_key, students, email_students=email_students) datatable = ret['datatable'] elif action == 'List sections available in remote gradebook': msg2, datatable = _do_remote_gradebook(request.user, course, 'get-sections') msg += msg2 elif action in ['List students in section in remote gradebook', 'Overload enrollment list using remote gradebook', 'Merge enrollment list with remote gradebook']: section = request.POST.get('gradebook_section', '') msg2, datatable = _do_remote_gradebook(request.user, course, 'get-membership', dict(section=section)) msg += msg2 if 'List' not in action: students = ','.join([x['email'] for x in datatable['retdata']]) overload = 'Overload' in action secure = request.is_secure() ret = _do_enroll_students(course, course_key, students, secure=secure, overload=overload) datatable = ret['datatable'] #---------------------------------------- # psychometrics elif action == 'Generate Histogram and IRT Plot': problem = request.POST['Problem'] nmsg, plots = psychoanalyze.generate_plots_for_problem(problem) msg += nmsg track.views.server_track(request, "psychometrics-histogram-generation", {"problem": unicode(problem)}, page="idashboard") if idash_mode == 'Psychometrics': problems = psychoanalyze.problems_with_psychometric_data(course_key) #---------------------------------------- # analytics def get_analytics_result(analytics_name): """Return data for an Analytic piece, or None if it doesn't exist. It logs and swallows errors. """ url = settings.ANALYTICS_SERVER_URL + \ u"get?aname={}&course_id={}&apikey={}".format( analytics_name, urllib.quote(unicode(course_key)), settings.ANALYTICS_API_KEY ) try: res = requests.get(url) except Exception: # pylint: disable=broad-except log.exception("Error trying to access analytics at %s", url) return None if res.status_code == codes.OK: # WARNING: do not use req.json because the preloaded json doesn't # preserve the order of the original record (hence OrderedDict). payload = json.loads(res.content, object_pairs_hook=OrderedDict) add_block_ids(payload) return payload else: log.error("Error fetching %s, code: %s, msg: %s", url, res.status_code, res.content) return None analytics_results = {} if idash_mode == 'Analytics': dashboard_analytics = [ # "StudentsAttemptedProblems", # num students who tried given problem "StudentsDailyActivity", # active students by day "StudentsDropoffPerDay", # active students dropoff by day # "OverallGradeDistribution", # overall point distribution for course # "StudentsPerProblemCorrect", # foreach problem, num students correct "ProblemGradeDistribution", # foreach problem, grade distribution ] for analytic_name in dashboard_analytics: analytics_results[analytic_name] = get_analytics_result(analytic_name) #---------------------------------------- # Metrics metrics_results = {} if settings.FEATURES.get('CLASS_DASHBOARD') and idash_mode == 'Metrics': metrics_results['section_display_name'] = dashboard_data.get_section_display_name(course_key) metrics_results['section_has_problem'] = dashboard_data.get_array_section_has_problem(course_key) #---------------------------------------- # offline grades? if use_offline: msg += "<br/><font color='orange'>{text}</font>".format( text=_("Grades from {course_id}").format( course_id=offline_grades_available(course_key) ) ) # generate list of pending background tasks if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'): instructor_tasks = get_running_instructor_tasks(course_key) else: instructor_tasks = None # determine if this is a studio-backed course so we can provide a link to edit this course in studio is_studio_course = modulestore().get_modulestore_type(course_key) != ModuleStoreEnum.Type.xml studio_url = None if is_studio_course: studio_url = get_cms_course_link(course) if bulk_email_is_enabled_for_course(course_key): show_email_tab = True # display course stats only if there is no other table to display: course_stats = None if not datatable: course_stats = get_course_stats_table() # disable buttons for large courses disable_buttons = False max_enrollment_for_buttons = settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS") if max_enrollment_for_buttons is not None: disable_buttons = enrollment_number > max_enrollment_for_buttons #---------------------------------------- # context for rendering context = { 'course': course, 'staff_access': True, 'admin_access': request.user.is_staff, 'instructor_access': instructor_access, 'forum_admin_access': forum_admin_access, 'datatable': datatable, 'course_stats': course_stats, 'msg': msg, 'modeflag': {idash_mode: 'selectedmode'}, 'studio_url': studio_url, 'show_email_tab': show_email_tab, # email 'problems': problems, # psychometrics 'plots': plots, # psychometrics 'course_errors': modulestore().get_course_errors(course.id), 'instructor_tasks': instructor_tasks, 'offline_grade_log': offline_grades_available(course_key), 'analytics_results': analytics_results, 'disable_buttons': disable_buttons, 'metrics_results': metrics_results, } context['standard_dashboard_url'] = reverse('instructor_dashboard', kwargs={'course_id': course_key.to_deprecated_string()}) return render_to_response('courseware/legacy_instructor_dashboard.html', context)
def instructor_dashboard(request, course_id): """Display the instructor dashboard for a course.""" course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_with_access(request.user, 'staff', course_key, depth=None) instructor_access = has_access(request.user, 'instructor', course) # an instructor can manage staff lists forum_admin_access = has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR) msg = '' show_email_tab = False problems = [] plots = [] datatable = {} # the instructor dashboard page is modal: grades, psychometrics, admin # keep that state in request.session (defaults to grades mode) idash_mode = request.POST.get('idash_mode', '') idash_mode_key = u'idash_mode:{0}'.format(course_id) if idash_mode: request.session[idash_mode_key] = idash_mode else: idash_mode = request.session.get(idash_mode_key, 'Grades') enrollment_number = CourseEnrollment.num_enrolled_in(course_key) # assemble some course statistics for output to instructor def get_course_stats_table(): datatable = { 'header': ['Statistic', 'Value'], 'title': _('Course Statistics At A Glance'), } data = [['Date', timezone.now().isoformat()]] data += compute_course_stats(course).items() if request.user.is_staff: for field in course.fields.values(): if getattr(field.scope, 'user', False): continue data.append([ field.name, json.dumps(field.read_json(course), cls=i4xEncoder) ]) datatable['data'] = data return datatable def return_csv(func, datatable, file_pointer=None): """Outputs a CSV file from the contents of a datatable.""" if file_pointer is None: response = HttpResponse(mimetype='text/csv') response['Content-Disposition'] = (u'attachment; filename={0}'.format(func)).encode('utf-8') else: response = file_pointer writer = csv.writer(response, dialect='excel', quotechar='"', quoting=csv.QUOTE_ALL) encoded_row = [unicode(s).encode('utf-8') for s in datatable['header']] writer.writerow(encoded_row) for datarow in datatable['data']: # 's' here may be an integer, float (eg score) or string (eg student name) encoded_row = [ # If s is already a UTF-8 string, trying to make a unicode # object out of it will fail unless we pass in an encoding to # the constructor. But we can't do that across the board, # because s is often a numeric type. So just do this. s if isinstance(s, str) else unicode(s).encode('utf-8') for s in datarow ] writer.writerow(encoded_row) return response # process actions from form POST action = request.POST.get('action', '') use_offline = request.POST.get('use_offline_grades', False) if settings.FEATURES['ENABLE_MANUAL_GIT_RELOAD']: if 'GIT pull' in action: data_dir = course.data_dir log.debug('git pull {0}'.format(data_dir)) gdir = settings.DATA_DIR / data_dir if not os.path.exists(gdir): msg += "====> ERROR in gitreload - no such directory {0}".format(gdir) else: cmd = "cd {0}; git reset --hard HEAD; git clean -f -d; git pull origin; chmod g+w course.xml".format(gdir) msg += "git pull on {0}:<p>".format(data_dir) msg += "<pre>{0}</pre></p>".format(escape(os.popen(cmd).read())) track.views.server_track(request, "git-pull", {"directory": data_dir}, page="idashboard") if 'Reload course' in action: log.debug('reloading {0} ({1})'.format(course_key, course)) try: data_dir = course.data_dir modulestore().try_load_course(data_dir) msg += "<br/><p>Course reloaded from {0}</p>".format(data_dir) track.views.server_track(request, "reload", {"directory": data_dir}, page="idashboard") course_errors = modulestore().get_course_errors(course.id) msg += '<ul>' for cmsg, cerr in course_errors: msg += "<li>{0}: <pre>{1}</pre>".format(cmsg, escape(cerr)) msg += '</ul>' except Exception as err: # pylint: disable=broad-except msg += '<br/><p>Error: {0}</p>'.format(escape(err)) if action == 'Dump list of enrolled students' or action == 'List enrolled students': log.debug(action) datatable = get_student_grade_summary_data(request, course, get_grades=False, use_offline=use_offline) datatable['title'] = _('List of students enrolled in {course_key}').format(course_key=course_key.to_deprecated_string()) track.views.server_track(request, "list-students", {}, page="idashboard") elif 'Dump all RAW grades' in action: log.debug(action) datatable = get_student_grade_summary_data(request, course, get_grades=True, get_raw_scores=True, use_offline=use_offline) datatable['title'] = _('Raw Grades of students enrolled in {course_key}').format(course_key=course_key) track.views.server_track(request, "dump-grades-raw", {}, page="idashboard") elif 'Download CSV of all RAW grades' in action: track.views.server_track(request, "dump-grades-csv-raw", {}, page="idashboard") return return_csv('grades_{0}_raw.csv'.format(course_key.to_deprecated_string()), get_student_grade_summary_data(request, course, get_raw_scores=True, use_offline=use_offline)) elif 'Download CSV of answer distributions' in action: track.views.server_track(request, "dump-answer-dist-csv", {}, page="idashboard") return return_csv('answer_dist_{0}.csv'.format(course_key.to_deprecated_string()), get_answers_distribution(request, course_key)) #---------------------------------------- # export grades to remote gradebook elif action == 'List assignments available in remote gradebook': msg2, datatable = _do_remote_gradebook(request.user, course, 'get-assignments') msg += msg2 elif action == 'List assignments available for this course': log.debug(action) allgrades = get_student_grade_summary_data(request, course, get_grades=True, use_offline=use_offline) assignments = [[x] for x in allgrades['assignments']] datatable = {'header': [_('Assignment Name')]} datatable['data'] = assignments datatable['title'] = action msg += 'assignments=<pre>%s</pre>' % assignments elif action == 'List enrolled students matching remote gradebook': stud_data = get_student_grade_summary_data(request, course, get_grades=False, use_offline=use_offline) msg2, rg_stud_data = _do_remote_gradebook(request.user, course, 'get-membership') datatable = {'header': ['Student email', 'Match?']} rg_students = [x['email'] for x in rg_stud_data['retdata']] def domatch(student): """Returns 'yes' if student is pressent in the remote gradebook student list, else returns 'No'""" return 'yes' if student.email in rg_students else 'No' datatable['data'] = [[x.email, domatch(x)] for x in stud_data['students']] datatable['title'] = action elif action in ['Display grades for assignment', 'Export grades for assignment to remote gradebook', 'Export CSV file of grades for assignment']: log.debug(action) datatable = {} aname = request.POST.get('assignment_name', '') if not aname: msg += "<font color='red'>{text}</font>".format(text=_("Please enter an assignment name")) else: allgrades = get_student_grade_summary_data(request, course, get_grades=True, use_offline=use_offline) if aname not in allgrades['assignments']: msg += "<font color='red'>{text}</font>".format( text=_("Invalid assignment name '{name}'").format(name=aname) ) else: aidx = allgrades['assignments'].index(aname) datatable = {'header': [_('External email'), aname]} ddata = [] for student in allgrades['students']: # do one by one in case there is a student who has only partial grades try: ddata.append([student.email, student.grades[aidx]]) except IndexError: log.debug('No grade for assignment {idx} ({name}) for student {email}'.format( idx=aidx, name=aname, email=student.email) ) datatable['data'] = ddata datatable['title'] = _('Grades for assignment "{name}"').format(name=aname) if 'Export CSV' in action: # generate and return CSV file return return_csv('grades {name}.csv'.format(name=aname), datatable) elif 'remote gradebook' in action: file_pointer = StringIO() return_csv('', datatable, file_pointer=file_pointer) file_pointer.seek(0) files = {'datafile': file_pointer} msg2, __ = _do_remote_gradebook(request.user, course, 'post-grades', files=files) msg += msg2 #---------------------------------------- # DataDump elif 'Download CSV of all responses to problem' in action: problem_to_dump = request.POST.get('problem_to_dump', '') if problem_to_dump[-4:] == ".xml": problem_to_dump = problem_to_dump[:-4] try: module_state_key = course_key.make_usage_key_from_deprecated_string(problem_to_dump) smdat = StudentModule.objects.filter( course_id=course_key, module_state_key=module_state_key ) smdat = smdat.order_by('student') msg += _("Found {num} records to dump.").format(num=smdat) except Exception as err: # pylint: disable=broad-except msg += "<font color='red'>{text}</font><pre>{err}</pre>".format( text=_("Couldn't find module with that urlname."), err=escape(err) ) smdat = [] if smdat: datatable = {'header': ['username', 'state']} datatable['data'] = [[x.student.username, x.state] for x in smdat] datatable['title'] = _('Student state for problem {problem}').format(problem=problem_to_dump) return return_csv('student_state_from_{problem}.csv'.format(problem=problem_to_dump), datatable) #---------------------------------------- # enrollment elif action == 'List students who may enroll but may not have yet signed up': ceaset = CourseEnrollmentAllowed.objects.filter(course_id=course_key) datatable = {'header': ['StudentEmail']} datatable['data'] = [[x.email] for x in ceaset] datatable['title'] = action elif action == 'Enroll multiple students': is_shib_course = uses_shib(course) students = request.POST.get('multiple_students', '') auto_enroll = bool(request.POST.get('auto_enroll')) email_students = bool(request.POST.get('email_students')) secure = request.is_secure() ret = _do_enroll_students(course, course_key, students, secure=secure, auto_enroll=auto_enroll, email_students=email_students, is_shib_course=is_shib_course) datatable = ret['datatable'] elif action == 'Unenroll multiple students': students = request.POST.get('multiple_students', '') email_students = bool(request.POST.get('email_students')) ret = _do_unenroll_students(course_key, students, email_students=email_students) datatable = ret['datatable'] elif action == 'List sections available in remote gradebook': msg2, datatable = _do_remote_gradebook(request.user, course, 'get-sections') msg += msg2 elif action in ['List students in section in remote gradebook', 'Overload enrollment list using remote gradebook', 'Merge enrollment list with remote gradebook']: section = request.POST.get('gradebook_section', '') msg2, datatable = _do_remote_gradebook(request.user, course, 'get-membership', dict(section=section)) msg += msg2 if not 'List' in action: students = ','.join([x['email'] for x in datatable['retdata']]) overload = 'Overload' in action secure = request.is_secure() ret = _do_enroll_students(course, course_key, students, secure=secure, overload=overload) datatable = ret['datatable'] #---------------------------------------- # psychometrics elif action == 'Generate Histogram and IRT Plot': problem = request.POST['Problem'] nmsg, plots = psychoanalyze.generate_plots_for_problem(problem) msg += nmsg track.views.server_track(request, "psychometrics-histogram-generation", {"problem": unicode(problem)}, page="idashboard") if idash_mode == 'Psychometrics': problems = psychoanalyze.problems_with_psychometric_data(course_key) #---------------------------------------- # analytics def get_analytics_result(analytics_name): """Return data for an Analytic piece, or None if it doesn't exist. It logs and swallows errors. """ url = settings.ANALYTICS_SERVER_URL + \ u"get?aname={}&course_id={}&apikey={}".format( analytics_name, urllib.quote(unicode(course_key)), settings.ANALYTICS_API_KEY ) try: res = requests.get(url) except Exception: # pylint: disable=broad-except log.exception("Error trying to access analytics at %s", url) return None if res.status_code == codes.OK: # WARNING: do not use req.json because the preloaded json doesn't # preserve the order of the original record (hence OrderedDict). payload = json.loads(res.content, object_pairs_hook=OrderedDict) add_block_ids(payload) return payload else: log.error("Error fetching %s, code: %s, msg: %s", url, res.status_code, res.content) return None analytics_results = {} if idash_mode == 'Analytics': dashboard_analytics = [ # "StudentsAttemptedProblems", # num students who tried given problem "StudentsDailyActivity", # active students by day "StudentsDropoffPerDay", # active students dropoff by day # "OverallGradeDistribution", # overall point distribution for course # "StudentsPerProblemCorrect", # foreach problem, num students correct "ProblemGradeDistribution", # foreach problem, grade distribution ] for analytic_name in dashboard_analytics: analytics_results[analytic_name] = get_analytics_result(analytic_name) #---------------------------------------- # Metrics metrics_results = {} if settings.FEATURES.get('CLASS_DASHBOARD') and idash_mode == 'Metrics': metrics_results['section_display_name'] = dashboard_data.get_section_display_name(course_key) metrics_results['section_has_problem'] = dashboard_data.get_array_section_has_problem(course_key) #---------------------------------------- # offline grades? if use_offline: msg += "<br/><font color='orange'>{text}</font>".format( text=_("Grades from {course_id}").format( course_id=offline_grades_available(course_key) ) ) # generate list of pending background tasks if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS'): instructor_tasks = get_running_instructor_tasks(course_key) else: instructor_tasks = None # determine if this is a studio-backed course so we can provide a link to edit this course in studio is_studio_course = modulestore().get_modulestore_type(course_key) != ModuleStoreEnum.Type.xml studio_url = None if is_studio_course: studio_url = get_cms_course_link(course) if bulk_email_is_enabled_for_course(course_key): show_email_tab = True # display course stats only if there is no other table to display: course_stats = None if not datatable: course_stats = get_course_stats_table() # disable buttons for large courses disable_buttons = False max_enrollment_for_buttons = settings.FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS") if max_enrollment_for_buttons is not None: disable_buttons = enrollment_number > max_enrollment_for_buttons #---------------------------------------- # context for rendering context = { 'course': course, 'staff_access': True, 'admin_access': request.user.is_staff, 'instructor_access': instructor_access, 'forum_admin_access': forum_admin_access, 'datatable': datatable, 'course_stats': course_stats, 'msg': msg, 'modeflag': {idash_mode: 'selectedmode'}, 'studio_url': studio_url, 'show_email_tab': show_email_tab, # email 'problems': problems, # psychometrics 'plots': plots, # psychometrics 'course_errors': modulestore().get_course_errors(course.id), 'instructor_tasks': instructor_tasks, 'offline_grade_log': offline_grades_available(course_key), 'analytics_results': analytics_results, 'disable_buttons': disable_buttons, 'metrics_results': metrics_results, } context['standard_dashboard_url'] = reverse('instructor_dashboard', kwargs={'course_id': course_key.to_deprecated_string()}) return render_to_response('courseware/legacy_instructor_dashboard.html', context)
def metrics_section_data(course_key): enrollment = CourseEnrollment.num_enrolled_in(course_key) data = { 'enrollment': enrollment, } return data