def test_remove_master_course_staff_from_ccx_idempotent(self): """ Test remove staff of master course from ccx course """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() self.assertEqual(len(outbox), 0) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email) # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx( self.course, self.ccx_locator, self.ccx.display_name, send_email=True ) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course) # Run again remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course)
def test_remove_master_course_staff_from_ccx_idempotent(self): """ Test remove staff of master course from ccx course """ staff = self.make_staff() assert CourseStaffRole(self.course.id).has_user(staff) # adding instructor to master course. instructor = self.make_instructor() assert CourseInstructorRole(self.course.id).has_user(instructor) outbox = self.get_outbox() assert len(outbox) == 0 add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course.id, 'staff') list_instructor_master_course = list_with_level(self.course.id, 'instructor') with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') assert len(list_staff_master_course) == len(list_staff_ccx_course) assert list_staff_master_course[0].email == list_staff_ccx_course[0].email list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') assert len(list_instructor_ccx_course) == len(list_instructor_master_course) assert list_instructor_ccx_course[0].email == list_instructor_master_course[0].email # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx( self.course, self.ccx_locator, self.ccx.display_name, send_email=True ) assert len(outbox) == (len(list_staff_master_course) + len(list_instructor_master_course)) list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') assert len(list_staff_master_course) != len(list_staff_ccx_course) list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') assert len(list_instructor_ccx_course) != len(list_instructor_master_course) for user in list_staff_master_course: assert user not in list_staff_ccx_course for user in list_instructor_master_course: assert user not in list_instructor_ccx_course # Run again remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name) assert len(outbox) == (len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx.id, 'staff') assert len(list_staff_master_course) != len(list_staff_ccx_course) list_instructor_ccx_course = list_with_level(course_ccx.id, 'instructor') assert len(list_instructor_ccx_course) != len(list_instructor_master_course) for user in list_staff_master_course: assert user not in list_staff_ccx_course for user in list_instructor_master_course: assert user not in list_instructor_ccx_course
def test_add_master_course_staff_to_ccx_idempotent(self): """ Test add staff of master course to ccx course multiple time will not result in multiple enrollments. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue( CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level( self.course, 'instructor') self.assertEqual(len(outbox), 0) # run the assignment the first time add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual( len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level( course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course) # run the assignment again add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # there are no new duplicated email self.assertEqual( len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) # there are no duplicated staffs with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level( course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course)
def test_add_master_course_staff_to_ccx(self): """ Test add staff of master course to ccx course """ # adding staff to master course. staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # assert that staff and instructors of master course has staff and instructor roles on ccx list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email)
def test_add_master_course_staff_to_ccx(self): """ Test add staff of master course to ccx course """ # adding staff to master course. staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue( CourseInstructorRole(self.course.id).has_user(instructor)) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # assert that staff and instructors of master course has staff and instructor roles on ccx list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level( self.course, 'instructor') with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level( course_ccx, 'instructor') self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email)
def ccx_gradebook(request, course, ccx=None): """ Show the gradebook for this CCX. """ if not ccx: raise Http404 ccx_key = CCXLocator.from_course_locator(course.id, unicode(ccx.id)) with ccx_course(ccx_key) as course: prep_course_for_grading(course, request) student_info, page = get_grade_book_page( request, course, course_key=ccx_key) return render_to_response( 'courseware/gradebook.html', { 'page': page, 'page_url': reverse('ccx_gradebook', kwargs={'course_id': ccx_key}), 'students': student_info, 'course': course, 'course_id': course.id, 'staff_access': request.user.is_staff, 'ordered_grades': sorted( course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True), })
def ccx_gradebook(request, course, ccx=None): """ Show the gradebook for this CCX. """ if not ccx: raise Http404 ccx_key = CCXLocator.from_course_locator(course.id, unicode(ccx.id)) with ccx_course(ccx_key) as course: prep_course_for_grading(course, request) student_info, page = get_grade_book_page(request, course, course_key=ccx_key) return render_to_response( 'courseware/gradebook.html', { 'page': page, 'page_url': reverse('ccx_gradebook', kwargs={'course_id': ccx_key}), 'students': student_info, 'course': course, 'course_id': course.id, 'staff_access': request.user.is_staff, 'ordered_grades': sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True), })
def ccx_gradebook(request, course, ccx=None): """ Show the gradebook for this CCX. """ if not ccx: raise Http404 ccx_key = CCXLocator.from_course_locator(course.id, str(ccx.id)) with ccx_course(ccx_key) as course: # lint-amnesty, pylint: disable=redefined-argument-from-local student_info, page = get_grade_book_page(request, course, course_key=ccx_key) return render_to_response( 'courseware/gradebook.html', { 'page': page, 'page_url': reverse('ccx_gradebook', kwargs={'course_id': ccx_key}), 'students': student_info, 'course': course, 'course_id': course.id, 'staff_access': request.user.is_staff, 'ordered_grades': sorted(list(course.grade_cutoffs.items()), key=lambda i: i[1], reverse=True), })
def change_existing_ccx_coaches_to_staff(apps, schema_editor): """ Modify all coaches of CCX courses so that they have the staff role on the CCX course they coach, but retain the CCX Coach role on the parent course. Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema (unused) """ CustomCourseForEdX = apps.get_model('ccx', 'CustomCourseForEdX') db_alias = schema_editor.connection.alias if not db_alias == 'default': # This migration is not intended to run against the student_module_history database and # will fail if it does. Ensure that it'll only run against the default database. return list_ccx = CustomCourseForEdX.objects.using(db_alias).all() for ccx in list_ccx: ccx_locator = CCXLocator.from_course_locator(ccx.course_id, unicode(ccx.id)) with ccx_course(ccx_locator) as course: coach = User.objects.get(id=ccx.coach.id) allow_access(course, coach, 'staff', send_email=False) revoke_access(course, coach, 'ccx_coach', send_email=False) log.info( 'The CCX coach of CCX %s has been switched from "CCX Coach" to "Staff".', unicode(ccx_locator))
def revert_ccx_staff_to_coaches(apps, schema_editor): """ Modify all staff on CCX courses so that they no longer have the staff role on the course that they coach. Arguments: apps (Applications): Apps in edX platform. schema_editor (SchemaEditor): For editing database schema (unused) """ CustomCourseForEdX = apps.get_model('ccx', 'CustomCourseForEdX') db_alias = schema_editor.connection.alias if not db_alias == 'default': return list_ccx = CustomCourseForEdX.objects.using(db_alias).all() for ccx in list_ccx: ccx_locator = CCXLocator.from_course_locator(ccx.course_id, unicode(ccx.id)) with ccx_course(ccx_locator) as course: coach = User.objects.get(id=ccx.coach.id) allow_access(course, coach, 'ccx_coach', send_email=False) revoke_access(course, coach, 'staff', send_email=False) log.info( 'The CCX coach of CCX %s has been switched from "Staff" to "CCX Coach".', unicode(ccx_locator))
def test_add_master_course_staff_to_ccx_idempotent(self): """ Test add staff of master course to ccx course multiple time will not result in multiple enrollments. """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) outbox = self.get_outbox() list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') self.assertEqual(len(outbox), 0) # run the assignment the first time add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course) # run the assignment again add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name) # there are no new duplicated email self.assertEqual(len(outbox), len(list_staff_master_course) + len(list_instructor_master_course)) # there are no duplicated staffs with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) for user in list_staff_master_course: self.assertIn(user, list_staff_ccx_course) self.assertEqual(len(list_instructor_master_course), len(list_instructor_ccx_course)) for user in list_instructor_master_course: self.assertIn(user, list_instructor_ccx_course)
def ccx_grades_csv(request, course, ccx=None): """ Download grades as CSV. """ if not ccx: raise Http404 ccx_key = CCXLocator.from_course_locator(course.id, str(ccx.id)) with ccx_course(ccx_key) as course: # lint-amnesty, pylint: disable=redefined-argument-from-local enrolled_students = User.objects.filter( courseenrollment__course_id=ccx_key, courseenrollment__is_active=1).order_by('username').select_related( "profile") grades = CourseGradeFactory().iter(enrolled_students, course) header = None rows = [] for student, course_grade, __ in grades: if course_grade: # We were able to successfully grade this student for this # course. if not header: # Encode the header row in utf-8 encoding in case there are # unicode characters header = [ section['label'].encode('utf-8') if six.PY2 else section['label'] for section in course_grade.summary['section_breakdown'] ] rows.append(["id", "email", "username", "grade"] + header) percents = { section['label']: section.get('percent', 0.0) for section in course_grade.summary['section_breakdown'] if 'label' in section } row_percents = [percents.get(label, 0.0) for label in header] rows.append([ student.id, student.email.encode('utf-8'), student.username.encode('utf-8'), course_grade.percent ] + row_percents) buf = StringIO() writer = csv.writer(buf) for row in rows: writer.writerow(row) response = HttpResponse(buf.getvalue(), content_type='text/csv') response['Content-Disposition'] = 'attachment' return response
def ccx_grades_csv(request, course, ccx=None): """ Download grades as CSV. """ if not ccx: raise Http404 ccx_key = CCXLocator.from_course_locator(course.id, unicode(ccx.id)) with ccx_course(ccx_key) as course: prep_course_for_grading(course, request) enrolled_students = User.objects.filter( courseenrollment__course_id=ccx_key, courseenrollment__is_active=1).order_by('username').select_related( "profile") grades = CourseGradeFactory().iter(course, enrolled_students) header = None rows = [] for student, course_grade, __ in grades: if course_grade: # We were able to successfully grade this student for this # course. if not header: # Encode the header row in utf-8 encoding in case there are # unicode characters header = [ section['label'].encode('utf-8') for section in course_grade.summary[u'section_breakdown'] ] rows.append(["id", "email", "username", "grade"] + header) percents = { section['label']: section.get('percent', 0.0) for section in course_grade.summary[u'section_breakdown'] if 'label' in section } row_percents = [percents.get(label, 0.0) for label in header] rows.append([ student.id, student.email, student.username, course_grade. percent ] + row_percents) buf = StringIO() writer = csv.writer(buf) for row in rows: writer.writerow(row) response = HttpResponse(buf.getvalue(), content_type='text/csv') response['Content-Disposition'] = 'attachment' return response
def ccx_grades_csv(request, course, ccx=None): """ Download grades as CSV. """ if not ccx: raise Http404 ccx_key = CCXLocator.from_course_locator(course.id, ccx.id) with ccx_course(ccx_key) as course: prep_course_for_grading(course, request) enrolled_students = User.objects.filter( courseenrollment__course_id=ccx_key, courseenrollment__is_active=1).order_by('username').select_related( "profile") grades = iterate_grades_for(course, enrolled_students) header = None rows = [] for student, gradeset, __ in grades: if gradeset: # We were able to successfully grade this student for this # course. if not header: # Encode the header row in utf-8 encoding in case there are # unicode characters header = [ section['label'].encode('utf-8') for section in gradeset[u'section_breakdown'] ] rows.append(["id", "email", "username", "grade"] + header) percents = { section['label']: section.get('percent', 0.0) for section in gradeset[u'section_breakdown'] if 'label' in section } row_percents = [percents.get(label, 0.0) for label in header] rows.append([ student.id, student.email, student.username, gradeset['percent'] ] + row_percents) buf = StringIO() writer = csv.writer(buf) for row in rows: writer.writerow(row) response = HttpResponse(buf.getvalue(), content_type='text/csv') response['Content-Disposition'] = 'attachment' return response
def test_ccx_tab_visibility_for_staff_ccx_course(self): """ Staff can access coach dashboard on ccx course. """ self.make_coach() ccx = self.make_ccx() ccx_key = CCXLocator.from_course_locator(self.course.id, unicode(ccx.id)) staff = self.make_staff() with ccx_course(ccx_key) as course_ccx: allow_access(course_ccx, staff, 'staff') self.assertTrue(self.check_ccx_tab(course_ccx, staff))
def test_ccx_tab_visibility_for_instructor_ccx_course(self): """ Instructor can access coach dashboard on ccx course. """ self.make_coach() ccx = self.make_ccx() ccx_key = CCXLocator.from_course_locator(self.course.id, unicode(ccx.id)) instructor = self.make_instructor() with ccx_course(ccx_key) as course_ccx: allow_access(course_ccx, instructor, 'instructor') self.assertTrue(self.check_ccx_tab(course_ccx, instructor))
def test_ccx_tab_visibility_for_staff_ccx_course(self): """ Staff can access coach dashboard on ccx course. """ self.make_coach() ccx = self.make_ccx() ccx_key = CCXLocator.from_course_locator(self.course.id, str(ccx.id)) staff = self.make_staff() with ccx_course(ccx_key) as course_ccx: allow_access(course_ccx, staff, 'staff') assert self.check_ccx_tab(course_ccx, staff)
def test_ccx_tab_visibility_for_instructor_ccx_course(self): """ Instructor can access coach dashboard on ccx course. """ self.make_coach() ccx = self.make_ccx() ccx_key = CCXLocator.from_course_locator(self.course.id, str(ccx.id)) instructor = self.make_instructor() with ccx_course(ccx_key) as course_ccx: allow_access(course_ccx, instructor, 'instructor') assert self.check_ccx_tab(course_ccx, instructor)
def dashboard(request, course, ccx=None): """ Display the CCX Coach Dashboard. """ # right now, we can only have one ccx per user and course # so, if no ccx is passed in, we can sefely redirect to that if ccx is None: ccx = get_ccx_for_coach(course, request.user) if ccx: url = reverse('ccx_coach_dashboard', kwargs={ 'course_id': CCXLocator.from_course_locator( course.id, ccx.id) }) return redirect(url) context = { 'course': course, 'ccx': ccx, } context.update(get_ccx_creation_dict(course)) if ccx: ccx_locator = CCXLocator.from_course_locator(course.id, unicode(ccx.id)) # At this point we are done with verification that current user is ccx coach. assign_coach_role_to_ccx(ccx_locator, request.user, course.id) schedule = get_ccx_schedule(course, ccx) grading_policy = get_override_for_ccx(ccx, course, 'grading_policy', course.grading_policy) context['schedule'] = json.dumps(schedule, indent=4) context['save_url'] = reverse('save_ccx', kwargs={'course_id': ccx_locator}) context['ccx_members'] = CourseEnrollment.objects.filter( course_id=ccx_locator, is_active=True) context['gradebook_url'] = reverse('ccx_gradebook', kwargs={'course_id': ccx_locator}) context['grades_csv_url'] = reverse('ccx_grades_csv', kwargs={'course_id': ccx_locator}) context['grading_policy'] = json.dumps(grading_policy, indent=4) context['grading_policy_url'] = reverse( 'ccx_set_grading_policy', kwargs={'course_id': ccx_locator}) with ccx_course(ccx_locator) as course: context['course'] = course else: context['create_ccx_url'] = reverse('create_ccx', kwargs={'course_id': course.id}) return render_to_response('ccx/coach_dashboard.html', context)
def dashboard(request, course, ccx=None): """ Display the CCX Coach Dashboard. """ # right now, we can only have one ccx per user and course # so, if no ccx is passed in, we can sefely redirect to that if ccx is None: ccx = get_ccx_for_coach(course, request.user) if ccx: url = reverse( 'ccx_coach_dashboard', kwargs={ 'course_id': CCXLocator.from_course_locator(course.id, unicode(ccx.id)) }) return redirect(url) context = { 'course': course, 'ccx': ccx, } context.update(get_ccx_creation_dict(course)) if ccx: ccx_locator = CCXLocator.from_course_locator(course.id, unicode(ccx.id)) # At this point we are done with verification that current user is ccx coach. assign_staff_role_to_ccx(ccx_locator, request.user, course.id) schedule = get_ccx_schedule(course, ccx) grading_policy = get_override_for_ccx(ccx, course, 'grading_policy', course.grading_policy) context['schedule'] = json.dumps(schedule, indent=4) context['save_url'] = reverse( 'save_ccx', kwargs={'course_id': ccx_locator}) context['ccx_members'] = CourseEnrollment.objects.filter( course_id=ccx_locator, is_active=True) context['gradebook_url'] = reverse( 'ccx_gradebook', kwargs={'course_id': ccx_locator}) context['grades_csv_url'] = reverse( 'ccx_grades_csv', kwargs={'course_id': ccx_locator}) context['grading_policy'] = json.dumps(grading_policy, indent=4) context['grading_policy_url'] = reverse( 'ccx_set_grading_policy', kwargs={'course_id': ccx_locator}) with ccx_course(ccx_locator) as course: context['course'] = course else: context['create_ccx_url'] = reverse( 'create_ccx', kwargs={'course_id': course.id}) return render_to_response('ccx/coach_dashboard.html', context)
def edit_ccx_context(course, ccx, user, **kwargs): ccx_locator = CCXLocator.from_course_locator(course.id, unicode(ccx.pk)) schedule = get_ccx_schedule(course, ccx) grading_policy = get_override_for_ccx(ccx, course, 'grading_policy', course.grading_policy) context = {} context['ccx_locator'] = ccx_locator context['modify_access_url'] = reverse('modify_access', kwargs={'course_id': ccx_locator}) context['schedule'] = json.dumps(schedule, indent=4) context['save_url'] = reverse('save_ccx', kwargs={'course_id': ccx_locator}) non_student_user_ids = CourseAccessRole.objects.filter( course_id=ccx_locator).values_list('user_id', flat=True) ccx_student_enrollments = CourseEnrollment.objects.filter( course_id=ccx_locator, is_active=True).exclude(user_id__in=non_student_user_ids) context['ccx_student_enrollments'] = ccx_student_enrollments context['gradebook_url'] = reverse('ccx_gradebook', kwargs={'course_id': ccx_locator}) context['grades_csv_url'] = reverse('ccx_grades_csv', kwargs={'course_id': ccx_locator}) context['grading_policy'] = json.dumps(grading_policy, indent=4) context['grading_policy_url'] = reverse('ccx_set_grading_policy', kwargs={'course_id': ccx_locator}) context['STATE_CHOICES'] = STATE_CHOICES all_facilitators = get_facilitators(ccx.affiliate) added_facilitator_user_ids = CourseAccessRole.objects.filter( course_id=ccx_locator, role=AffiliateMembership.CCX_COACH).values_list('user_id', flat=True) context['added_facilitators'] = all_facilitators.filter( id__in=added_facilitator_user_ids) context['not_added_facilitators'] = all_facilitators.exclude( id__in=added_facilitator_user_ids) with ccx_course(ccx_locator) as course: context['course'] = course context['edit_ccx_url'] = reverse('edit_ccx', kwargs={'course_id': ccx_locator}) context['edit_ccx_dasboard_url'] = reverse( 'ccx_edit_course_view', kwargs={'course_id': ccx_locator}) return context
def ccx_grades_csv(request, course, ccx=None): """ Download grades as CSV. """ if not ccx: raise Http404 ccx_key = CCXLocator.from_course_locator(course.id, ccx.id) with ccx_course(ccx_key) as course: prep_course_for_grading(course, request) enrolled_students = ( User.objects.filter(courseenrollment__course_id=ccx_key, courseenrollment__is_active=1) .order_by("username") .select_related("profile") ) grades = iterate_grades_for(course, enrolled_students) header = None rows = [] for student, gradeset, __ in grades: if gradeset: # We were able to successfully grade this student for this # course. if not header: # Encode the header row in utf-8 encoding in case there are # unicode characters header = [section["label"].encode("utf-8") for section in gradeset[u"section_breakdown"]] rows.append(["id", "email", "username", "grade"] + header) percents = { section["label"]: section.get("percent", 0.0) for section in gradeset[u"section_breakdown"] if "label" in section } row_percents = [percents.get(label, 0.0) for label in header] rows.append([student.id, student.email, student.username, gradeset["percent"]] + row_percents) buf = StringIO() writer = csv.writer(buf) for row in rows: writer.writerow(row) response = HttpResponse(buf.getvalue(), content_type="text/csv") response["Content-Disposition"] = "attachment" return response
def test_create_ccx(self, ccx_name='New CCX'): """ Create CCX. Follow redirect to coach dashboard, confirm we see the coach dashboard for the new CCX. """ self.make_coach() url = reverse( 'create_ccx', kwargs={'course_id': unicode(self.course.id)}) response = self.client.post(url, {'name': ccx_name}) self.assertEqual(response.status_code, 302) url = response.get('location') # pylint: disable=no-member response = self.client.get(url) self.assertEqual(response.status_code, 200) # Get the ccx_key path = urlparse.urlparse(url).path resolver = resolve(path) ccx_key = resolver.kwargs['course_id'] course_key = CourseKey.from_string(ccx_key) self.assertTrue(CourseEnrollment.is_enrolled(self.coach, course_key)) self.assertTrue(re.search('id="ccx-schedule"', response.content)) # check if the max amount of student that can be enrolled has been overridden ccx = CustomCourseForEdX.objects.get() course_enrollments = get_override_for_ccx(ccx, self.course, 'max_student_enrollments_allowed') self.assertEqual(course_enrollments, settings.CCX_MAX_STUDENTS_ALLOWED) # assert ccx creator has role=ccx_coach role = CourseCcxCoachRole(course_key) self.assertTrue(role.has_user(self.coach)) # assert that staff and instructors of master course has staff and instructor roles on ccx list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') with ccx_course(course_key) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email)
def test_remove_master_course_staff_from_ccx(self): """ Test remove staff of master course to ccx course """ staff = self.make_staff() self.assertTrue(CourseStaffRole(self.course.id).has_user(staff)) # adding instructor to master course. instructor = self.make_instructor() self.assertTrue(CourseInstructorRole(self.course.id).has_user(instructor)) add_master_course_staff_to_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_master_course = list_with_level(self.course, "staff") list_instructor_master_course = list_with_level(self.course, "instructor") with ccx_course(self.ccx_locator) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, "staff") self.assertEqual(len(list_staff_master_course), len(list_staff_ccx_course)) self.assertEqual(list_staff_master_course[0].email, list_staff_ccx_course[0].email) list_instructor_ccx_course = list_with_level(course_ccx, "instructor") self.assertEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) self.assertEqual(list_instructor_ccx_course[0].email, list_instructor_master_course[0].email) # assert that role of staff and instructors of master course removed from ccx. remove_master_course_staff_from_ccx(self.course, self.ccx_locator, self.ccx.display_name, send_email=False) list_staff_ccx_course = list_with_level(course_ccx, "staff") self.assertNotEqual(len(list_staff_master_course), len(list_staff_ccx_course)) list_instructor_ccx_course = list_with_level(course_ccx, "instructor") self.assertNotEqual(len(list_instructor_ccx_course), len(list_instructor_master_course)) for user in list_staff_master_course: self.assertNotIn(user, list_staff_ccx_course) for user in list_instructor_master_course: self.assertNotIn(user, list_instructor_ccx_course)
def ccx_gradebook(request, course, ccx=None): """ Show the gradebook for this CCX. """ if not ccx: raise Http404 ccx_key = CCXLocator.from_course_locator(course.id, ccx.id) with ccx_course(ccx_key) as course: prep_course_for_grading(course, request) student_info, page = get_grade_book_page(request, course, course_key=ccx_key) return render_to_response( "courseware/gradebook.html", { "page": page, "page_url": reverse("ccx_gradebook", kwargs={"course_id": ccx_key}), "students": student_info, "course": course, "course_id": course.id, "staff_access": request.user.is_staff, "ordered_grades": sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True), }, )
def test_create_ccx(self, ccx_name='New CCX'): """ Create CCX. Follow redirect to coach dashboard, confirm we see the coach dashboard for the new CCX. """ self.make_coach() url = reverse( 'create_ccx', kwargs={'course_id': str(self.course.id)}) response = self.client.post(url, {'name': ccx_name}) assert response.status_code == 302 url = response.get('location') response = self.client.get(url) assert response.status_code == 200 # Get the ccx_key path = six.moves.urllib.parse.urlparse(url).path resolver = resolve(path) ccx_key = resolver.kwargs['course_id'] course_key = CourseKey.from_string(ccx_key) assert CourseEnrollment.is_enrolled(self.coach, course_key) assert re.search('id="ccx-schedule"', response.content.decode('utf-8')) # check if the max amount of student that can be enrolled has been overridden ccx = CustomCourseForEdX.objects.get() course_enrollments = get_override_for_ccx(ccx, self.course, 'max_student_enrollments_allowed') assert course_enrollments == settings.CCX_MAX_STUDENTS_ALLOWED # check if the course display name is properly set course_display_name = get_override_for_ccx(ccx, self.course, 'display_name') assert course_display_name == ccx_name # check if the course display name is properly set in modulestore course_display_name = self.mstore.get_course(ccx.locator).display_name assert course_display_name == ccx_name # assert ccx creator has role=staff role = CourseStaffRole(course_key) assert role.has_user(self.coach) # assert that staff and instructors of master course has staff and instructor roles on ccx list_staff_master_course = list_with_level(self.course, 'staff') list_instructor_master_course = list_with_level(self.course, 'instructor') # assert that forum roles are seeded assert are_permissions_roles_seeded(course_key) assert has_forum_access(self.coach.username, course_key, FORUM_ROLE_ADMINISTRATOR) with ccx_course(course_key) as course_ccx: list_staff_ccx_course = list_with_level(course_ccx, 'staff') # The "Coach" in the parent course becomes "Staff" on the CCX, so the CCX should have 1 "Staff" # user more than the parent course assert (len(list_staff_master_course) + 1) == len(list_staff_ccx_course) assert list_staff_master_course[0].email in [ccx_staff.email for ccx_staff in list_staff_ccx_course] # Make sure the "Coach" on the parent course is "Staff" on the CCX assert self.coach in list_staff_ccx_course list_instructor_ccx_course = list_with_level(course_ccx, 'instructor') assert len(list_instructor_ccx_course) == len(list_instructor_master_course) assert list_instructor_ccx_course[0].email == list_instructor_master_course[0].email