def test_completed_programs_no_id_professional(self, mock_completed_course_runs, mock_get_programs): """ Verify the method treats no-id-professional enrollments as professional enrollments. """ course_runs = CourseRunFactory.create_batch(2, type='no-id-professional') program = ProgramFactory( courses=[CourseFactory(course_runs=course_runs)]) mock_get_programs.return_value = [program] # Verify that no programs are complete. meter = ProgramProgressMeter(self.user) self.assertEqual(meter.completed_programs, []) # Complete all programs. for course_run in course_runs: CourseEnrollmentFactory(user=self.user, course_id=course_run['key'], mode='no-id-professional') mock_completed_course_runs.return_value = [{ 'course_run_id': course_run['key'], 'type': MODES.professional } for course_run in course_runs] # Verify that all programs are complete. meter = ProgramProgressMeter(self.user) self.assertEqual(meter.completed_programs, [program['uuid']])
def render_to_fragment(self, request, **kwargs): """ Render the program listing fragment. """ user = request.user try: mobile_only = json.loads(request.GET.get('mobile_only', 'false')) except ValueError: mobile_only = False programs_config = kwargs.get('programs_config') or ProgramsApiConfig.current() if not programs_config.enabled or not user.is_authenticated: raise Http404 meter = ProgramProgressMeter(request.site, user, mobile_only=mobile_only) context = { 'marketing_url': get_program_marketing_url(programs_config), 'programs': meter.engaged_programs, 'progress': meter.progress() } html = render_to_string('learner_dashboard/programs_fragment.html', context) programs_fragment = Fragment(html) self.add_fragment_resource_urls(programs_fragment) return programs_fragment
def render_to_fragment(self, request, **kwargs): """ Render the program listing fragment. """ user = request.user try: mobile_only = json.loads(request.GET.get('mobile_only', 'false')) except ValueError: mobile_only = False programs_config = kwargs.get( 'programs_config') or ProgramsApiConfig.current() if not programs_config.enabled or not user.is_authenticated: raise Http404 meter = ProgramProgressMeter(request.site, user, mobile_only=mobile_only) context = { 'marketing_url': get_program_marketing_url(programs_config, mobile_only), 'programs': meter.engaged_programs, 'progress': meter.progress() } html = render_to_string('learner_dashboard/programs_fragment.html', context) programs_fragment = Fragment(html) self.add_fragment_resource_urls(programs_fragment) return programs_fragment
def test_program_completion_with_no_id_professional( self, mock_get_certificates_for_user, mock_get_programs): """ Verify that 'no-id-professional' certificates are treated as if they were 'professional' certificates when determining program completion. """ # Create serialized course runs like the ones we expect to receive from # the discovery service's API. These runs are of type 'professional'. course_runs = CourseRunFactory.create_batch(2, type='professional') program = ProgramFactory( courses=[CourseFactory(course_runs=course_runs)]) mock_get_programs.return_value = [program] # Verify that the test program is not complete. meter = ProgramProgressMeter(self.site, self.user) self.assertEqual(meter.completed_programs, []) # Grant a 'no-id-professional' certificate for one of the course runs, # thereby completing the program. mock_get_certificates_for_user.return_value = [ self._make_certificate_result(status='downloadable', type='no-id-professional', course_key=course_runs[0]['key']) ] # Verify that the program is complete. meter = ProgramProgressMeter(self.site, self.user) self.assertEqual(meter.completed_programs, [program['uuid']])
def test_completed_programs(self, mock_completed_course_runs, mock_get_programs): """Verify that completed programs are correctly identified.""" data = ProgramFactory.create_batch(3) mock_get_programs.return_value = data program_uuids = [] course_run_keys = [] for program in data: program_uuids.append(program['uuid']) for course in program['courses']: for course_run in course['course_runs']: course_run_keys.append(course_run['key']) # Verify that no programs are complete. meter = ProgramProgressMeter(self.user) self.assertEqual(meter.completed_programs, []) # Complete all programs. self._create_enrollments(*course_run_keys) mock_completed_course_runs.return_value = [ {'course_run_id': course_run_key, 'type': MODES.verified} for course_run_key in course_run_keys ] # Verify that all programs are complete. meter = ProgramProgressMeter(self.user) self.assertEqual(meter.completed_programs, program_uuids)
def test_no_id_professional_in_progress(self, mock_get_programs): """ Verify that the progress meter treats no-id-professional enrollments as professional. """ course_run_key = generate_course_run_key() data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key, type=CourseMode.PROFESSIONAL), ]), ] ) ] mock_get_programs.return_value = data CourseEnrollmentFactory( user=self.user, course_id=course_run_key, mode=CourseMode.NO_ID_PROFESSIONAL_MODE ) meter = ProgramProgressMeter(self.site, self.user) program = data[0] expected = [ ProgressFactory( uuid=program['uuid'], completed=[], in_progress=[program['courses'][0]], not_started=[] ) ] self.assertEqual(meter.progress(count_only=False), expected)
def test_no_id_professional_in_progress(self, mock_get_programs): """ Verify that the progress meter treats no-id-professional enrollments as professional. """ course_run_key = generate_course_run_key() data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key, type=CourseMode.PROFESSIONAL), ]), ] ) ] mock_get_programs.return_value = data CourseEnrollmentFactory( user=self.user, course_id=course_run_key, mode=CourseMode.NO_ID_PROFESSIONAL_MODE ) meter = ProgramProgressMeter(self.user) program = data[0] expected = [ ProgressFactory( uuid=program['uuid'], completed=[], in_progress=[program['courses'][0]], not_started=[] ) ] self.assertEqual(meter.progress(count_only=False), expected)
def test_course_progress(self, mock_get_programs): """ Verify that the progress meter can represent progress in terms of serialized courses. """ course_run_key = generate_course_run_key() data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key), ]), ] ) ] mock_get_programs.return_value = data self._create_enrollments(course_run_key) meter = ProgramProgressMeter(self.site, self.user) program = data[0] expected = [ ProgressFactory( uuid=program['uuid'], completed=[], in_progress=[program['courses'][0]], not_started=[], grades={course_run_key: 0.0}, ) ] self.assertEqual(meter.progress(count_only=False), expected)
def test_course_progress(self, mock_get_programs): """ Verify that the progress meter can represent progress in terms of serialized courses. """ course_run_key = generate_course_run_key() data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key), ]), ] ) ] mock_get_programs.return_value = data self._create_enrollments(course_run_key) meter = ProgramProgressMeter(self.user) program = data[0] expected = [ ProgressFactory( uuid=program['uuid'], completed=[], in_progress=[program['courses'][0]], not_started=[] ) ] self.assertEqual(meter.progress(count_only=False), expected)
def test_empty_programs(self, mock_get_programs): """Verify that programs with no courses do not count as completed.""" program = ProgramFactory() program['courses'] = [] meter = ProgramProgressMeter(self.site, self.user) program_complete = meter._is_program_complete(program) self.assertFalse(program_complete)
def test_course_grade_results(self, mock_get_programs): grade_percent = .8 with mock_passing_grade(percent=grade_percent): course_run_key = generate_course_run_key() data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key), ]), ] ) ] mock_get_programs.return_value = data self._create_enrollments(course_run_key) meter = ProgramProgressMeter(self.site, self.user) program = data[0] expected = [ ProgressFactory( uuid=program['uuid'], completed=[], in_progress=[program['courses'][0]], not_started=[], grades={course_run_key: grade_percent}, ) ] self.assertEqual(meter.progress(count_only=False), expected)
def handle(self, *args, **options): """ Command's entry point. """ should_commit = not options['no_commit'] email_sent_records = [] site = Site.objects.get_current() course_to_users_maps = self.get_passed_course_to_users_maps() for completed_course_id, users in course_to_users_maps.items(): course_linked_programs = get_programs(course=completed_course_id) course_linked_programs = self.sort_programs(course_linked_programs) if course_linked_programs: for user in users: meter = ProgramProgressMeter(site=site, user=user, include_course_entitlements=False) programs_progress = meter.progress(programs=course_linked_programs, count_only=False) suggested_program_progress, suggested_course_run = self.get_course_run_to_suggest( programs_progress, completed_course_id ) if suggested_course_run: suggested_program = self.get_program(course_linked_programs, suggested_program_progress) completed_course_run = self.get_course_run(suggested_program, completed_course_id) if should_commit: self.emit_event(user, suggested_program, suggested_course_run, completed_course_run) email_sent_records.append( f'User: {user.username}, Completed Course: {completed_course_id}, ' f'Suggested Course: {suggested_course_run["key"]}' ) LOGGER.info( '[Program Course Nudge Email] %s Emails sent. Records: %s', len(email_sent_records), email_sent_records, )
def related_programs(self): """ Returns related program data if the effective_user is enrolled. Note: related programs can be None depending on the course. """ if self.effective_user.is_anonymous: return meter = ProgramProgressMeter(self.request.site, self.effective_user) inverted_programs = meter.invert_programs() related_programs = inverted_programs.get(str(self.course_key)) if related_programs is None: return related_progress = meter.progress(programs=related_programs) progress_by_program = { progress['uuid']: progress for progress in related_progress } programs = [{ 'progress': progress_by_program[program['uuid']], 'title': program['title'], 'slug': program['type_attrs']['slug'], 'url': program['detail_url'], 'uuid': program['uuid'] } for program in related_programs] return programs
def test_empty_programs(self, mock_get_programs): """Verify that programs with no courses do not count as completed.""" program = ProgramFactory() program['courses'] = [] meter = ProgramProgressMeter(self.site, self.user) program_complete = meter._is_program_complete(program) self.assertFalse(program_complete)
def test_course_grade_results(self, mock_get_programs): grade_percent = .8 with mock_passing_grade(percent=grade_percent): course_run_key = generate_course_run_key() data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key), ]), ] ) ] mock_get_programs.return_value = data self._create_enrollments(course_run_key) meter = ProgramProgressMeter(self.site, self.user) program = data[0] expected = [ ProgressFactory( uuid=program['uuid'], completed=[], in_progress=[program['courses'][0]], not_started=[], grades={course_run_key: grade_percent}, ) ] self.assertEqual(meter.progress(count_only=False), expected)
def render_to_fragment(self, request, program_uuid, **kwargs): """View details about a specific program.""" programs_config = kwargs.get( 'programs_config') or ProgramsApiConfig.current() if not programs_config.enabled or not request.user.is_authenticated(): raise Http404 meter = ProgramProgressMeter(request.site, request.user, uuid=program_uuid) program_data = meter.programs[0] if not program_data: raise Http404 try: mobile_only = json.loads(request.GET.get('mobile_only', 'false')) except ValueError: mobile_only = False program_data = ProgramDataExtender(program_data, request.user, mobile_only=mobile_only).extend() course_data = meter.progress(programs=[program_data], count_only=False)[0] certificate_data = get_certificates(request.user, program_data) program_data.pop('courses') skus = program_data.get('skus') ecommerce_service = EcommerceService() urls = { 'program_listing_url': reverse('program_listing_view'), 'track_selection_url': strip_course_id( reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY})), 'commerce_api_url': reverse('commerce_api:v0:baskets:create'), 'buy_button_url': ecommerce_service.get_checkout_page_url(*skus) } context = { 'urls': urls, 'user_preferences': get_user_preferences(request.user), 'program_data': program_data, 'course_data': course_data, 'certificate_data': certificate_data } html = render_to_string( 'learner_dashboard/program_details_fragment.html', context) program_details_fragment = Fragment(html) self.add_fragment_resource_urls(program_details_fragment) return program_details_fragment
def render_to_fragment(self, request, program_uuid, **kwargs): """View details about a specific program.""" programs_config = kwargs.get('programs_config') or ProgramsApiConfig.current() if not programs_config.enabled or not request.user.is_authenticated: raise Http404 meter = ProgramProgressMeter(request.site, request.user, uuid=program_uuid) program_data = meter.programs[0] if not program_data: raise Http404 try: mobile_only = json.loads(request.GET.get('mobile_only', 'false')) except ValueError: mobile_only = False program_data = ProgramDataExtender(program_data, request.user, mobile_only=mobile_only).extend() course_data = meter.progress(programs=[program_data], count_only=False)[0] certificate_data = get_certificates(request.user, program_data) program_data.pop('courses') skus = program_data.get('skus') ecommerce_service = EcommerceService() # TODO: Don't have business logic of course-certificate==record-available here in LMS. # Eventually, the UI should ask Credentials if there is a record available and get a URL from it. # But this is here for now so that we can gate this URL behind both this business logic and # a waffle flag. This feature is in active developoment. program_record_url = get_credentials_records_url(program_uuid=program_uuid) if not certificate_data: program_record_url = None urls = { 'program_listing_url': reverse('program_listing_view'), 'track_selection_url': strip_course_id( reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY}) ), 'commerce_api_url': reverse('commerce_api:v0:baskets:create'), 'buy_button_url': ecommerce_service.get_checkout_page_url(*skus), 'program_record_url': program_record_url, } context = { 'urls': urls, 'user_preferences': get_user_preferences(request.user), 'program_data': program_data, 'course_data': course_data, 'certificate_data': certificate_data } html = render_to_string('learner_dashboard/program_details_fragment.html', context) program_details_fragment = Fragment(html) self.add_fragment_resource_urls(program_details_fragment) return program_details_fragment
def render_to_fragment(self, request, program_uuid, **kwargs): """View details about a specific program.""" programs_config = kwargs.get('programs_config') or ProgramsApiConfig.current() if not programs_config.enabled or not request.user.is_authenticated: raise Http404 meter = ProgramProgressMeter(request.site, request.user, uuid=program_uuid) program_data = meter.programs[0] if not program_data: raise Http404 try: mobile_only = json.loads(request.GET.get('mobile_only', 'false')) except ValueError: mobile_only = False program_data = ProgramDataExtender(program_data, request.user, mobile_only=mobile_only).extend() course_data = meter.progress(programs=[program_data], count_only=False)[0] certificate_data = get_certificates(request.user, program_data) program_data.pop('courses') skus = program_data.get('skus') ecommerce_service = EcommerceService() # TODO: Don't have business logic of course-certificate==record-available here in LMS. # Eventually, the UI should ask Credentials if there is a record available and get a URL from it. # But this is here for now so that we can gate this URL behind both this business logic and # a waffle flag. This feature is in active developoment. program_record_url = get_credentials_records_url(program_uuid=program_uuid) if not certificate_data: program_record_url = None urls = { 'program_listing_url': reverse('program_listing_view'), 'track_selection_url': strip_course_id( reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY}) ), 'commerce_api_url': reverse('commerce_api:v0:baskets:create'), 'buy_button_url': ecommerce_service.get_checkout_page_url(*skus), 'program_record_url': program_record_url, } context = { 'urls': urls, 'user_preferences': get_user_preferences(request.user), 'program_data': program_data, 'course_data': course_data, 'certificate_data': certificate_data } html = render_to_string('learner_dashboard/program_details_fragment.html', context) program_details_fragment = Fragment(html) self.add_fragment_resource_urls(program_details_fragment) return program_details_fragment
def program_details(request, program_uuid): """View details about a specific program.""" programs_config = ProgramsApiConfig.current() if not programs_config.enabled: raise Http404 meter = ProgramProgressMeter(request.user, uuid=program_uuid) program_data = meter.programs[0] if not program_data: raise Http404 program_data = ProgramDataExtender(program_data, request.user).extend() urls = { 'program_listing_url': reverse('program_listing_view'), 'track_selection_url': strip_course_id( reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY})), 'commerce_api_url': reverse('commerce_api:v0:baskets:create'), } context = { 'urls': urls, 'show_program_listing': programs_config.enabled, 'nav_hidden': True, 'disable_courseware_js': True, 'uses_pattern_library': True, 'user_preferences': get_user_preferences(request.user) } if waffle.switch_is_active('new_program_progress'): course_data = meter.progress(programs=[program_data], count_only=False)[0] certificate_data = get_certificates(request.user, program_data) program_data.pop('courses') context.update({ 'program_data': program_data, 'course_data': course_data, 'certificate_data': certificate_data, }) return render_to_response( 'learner_dashboard/program_details_2017.html', context) else: context.update({'program_data': program_data}) return render_to_response('learner_dashboard/program_details.html', context)
def test_credit_course_counted_complete_for_verified(self, mock_completed_course_runs, mock_get_programs): """ Verify that 'credit' course certificate type are treated as if they were "verified" when checking for course completion status. """ course_run_key = generate_course_run_key() course = CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key, type='credit'), ]) program = ProgramFactory(courses=[course]) mock_get_programs.return_value = [program] self._create_enrollments(course_run_key) meter = ProgramProgressMeter(self.site, self.user) mock_completed_course_runs.return_value = [{'course_run_id': course_run_key, 'type': 'verified'}] self.assertEqual(meter._is_course_complete(course), True)
def test_credit_course_counted_complete_for_verified(self, mock_completed_course_runs, mock_get_programs): """ Verify that 'credit' course certificate type are treated as if they were "verified" when checking for course completion status. """ course_run_key = generate_course_run_key() course = CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key, type='credit'), ]) program = ProgramFactory(courses=[course]) mock_get_programs.return_value = [program] self._create_enrollments(course_run_key) meter = ProgramProgressMeter(self.user) mock_completed_course_runs.return_value = [{'course_run_id': course_run_key, 'type': 'verified'}] self.assertEqual(meter._is_course_complete(course), True)
def program_details(request, program_uuid): """View details about a specific program.""" programs_config = ProgramsApiConfig.current() if not programs_config.enabled: raise Http404 meter = ProgramProgressMeter(request.user, uuid=program_uuid) program_data = meter.programs[0] if not program_data: raise Http404 program_data = ProgramDataExtender(program_data, request.user).extend() urls = { 'program_listing_url': reverse('program_listing_view'), 'track_selection_url': strip_course_id( reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY}) ), 'commerce_api_url': reverse('commerce_api:v0:baskets:create'), } context = { 'urls': urls, 'show_program_listing': programs_config.enabled, 'nav_hidden': True, 'disable_courseware_js': True, 'uses_pattern_library': True, 'user_preferences': get_user_preferences(request.user) } if waffle.switch_is_active('new_program_progress'): course_data = meter.progress(programs=[program_data], count_only=False)[0] certificate_data = get_certificates(request.user, program_data) program_data.pop('courses') context.update({ 'program_data': program_data, 'course_data': course_data, 'certificate_data': certificate_data, }) return render_to_response('learner_dashboard/program_details_2017.html', context) else: context.update({'program_data': program_data}) return render_to_response('learner_dashboard/program_details.html', context)
def program_details(request, program_uuid): """View details about a specific program.""" programs_config = ProgramsApiConfig.current() if not programs_config.enabled: raise Http404 meter = ProgramProgressMeter(request.site, request.user, uuid=program_uuid) program_data = meter.programs[0] if not program_data: raise Http404 program_data = ProgramDataExtender(program_data, request.user).extend() course_data = meter.progress(programs=[program_data], count_only=False)[0] certificate_data = get_certificates(request.user, program_data) program_data.pop('courses') skus = program_data.get('skus') ecommerce_service = EcommerceService() urls = { 'program_listing_url': reverse('program_listing_view'), 'track_selection_url': strip_course_id( reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY})), 'commerce_api_url': reverse('commerce_api:v0:baskets:create'), 'buy_button_url': ecommerce_service.get_checkout_page_url(*skus) } context = { 'urls': urls, 'show_program_listing': programs_config.enabled, 'show_dashboard_tabs': True, 'nav_hidden': True, 'disable_courseware_js': True, 'uses_pattern_library': True, 'user_preferences': get_user_preferences(request.user), 'program_data': program_data, 'course_data': course_data, 'certificate_data': certificate_data } return render_to_response('learner_dashboard/program_details.html', context)
def view_programs(request): """View programs in which the user is engaged.""" show_program_listing = ProgramsApiConfig.current().show_program_listing if not show_program_listing: raise Http404 enrollments = list(get_course_enrollments(request.user, None, [])) meter = ProgramProgressMeter(request.user, enrollments) programs = meter.engaged_programs # TODO: Pull 'xseries' string from configuration model. marketing_root = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries').strip('/') for program in programs: program['display_category'] = get_display_category(program) program['marketing_url'] = '{root}/{slug}'.format( root=marketing_root, slug=program['marketing_slug'] ) context = { 'programs': programs, 'progress': meter.progress, 'xseries_url': marketing_root if ProgramsApiConfig.current().show_xseries_ad else None, 'nav_hidden': True, 'show_program_listing': show_program_listing, 'credentials': get_programs_credentials(request.user, category='xseries'), 'disable_courseware_js': True, 'uses_pattern_library': True } return render_to_response('learner_dashboard/programs.html', context)
def test_mutiple_program_engagement(self, mock_get_programs): """ Verify that correct programs are returned in the correct order when the user is enrolled in course runs appearing in programs. """ newer_course_run_key, older_course_run_key = ( generate_course_run_key() for __ in range(2)) data = [ ProgramFactory(courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=newer_course_run_key), ]), ]), ProgramFactory(courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=older_course_run_key), ]), ]), ProgramFactory(), ] mock_get_programs.return_value = data # The creation time of the enrollments matters to the test. We want # the first_course_run_key to represent the newest enrollment. self._create_enrollments(older_course_run_key, newer_course_run_key) meter = ProgramProgressMeter(self.user) self._attach_detail_url(data) programs = data[:2] self.assertEqual(meter.engaged_programs, programs) self._assert_progress( meter, *(ProgressFactory(uuid=program['uuid'], in_progress=1) for program in programs)) self.assertEqual(meter.completed_programs, [])
def test_completed_course_runs(self, mock_get_certificates_for_user, _mock_get_programs): """ Verify that the method can find course run certificates when not mocked out. """ def make_certificate_result(**kwargs): """Helper to create dummy results from the certificates API.""" result = { 'username': '******', 'course_key': 'dummy-course', 'type': 'dummy-type', 'status': 'dummy-status', 'download_url': 'http://www.example.com/cert.pdf', 'grade': '0.98', 'created': '2015-07-31T00:00:00Z', 'modified': '2015-07-31T00:00:00Z', } result.update(**kwargs) return result mock_get_certificates_for_user.return_value = [ make_certificate_result(status='downloadable', type='verified', course_key='downloadable-course'), make_certificate_result(status='generating', type='honor', course_key='generating-course'), make_certificate_result(status='unknown', course_key='unknown-course'), ] meter = ProgramProgressMeter(self.user) self.assertEqual( meter.completed_course_runs, [ {'course_run_id': 'downloadable-course', 'type': 'verified'}, {'course_run_id': 'generating-course', 'type': 'honor'}, ] ) mock_get_certificates_for_user.assert_called_with(self.user.username)
def test_completed_course_runs(self, mock_get_certificates_for_user, _mock_get_programs): """ Verify that the method can find course run certificates when not mocked out. """ mock_get_certificates_for_user.return_value = [ self._make_certificate_result(status='downloadable', type='verified', course_key='downloadable-course'), self._make_certificate_result(status='generating', type='honor', course_key='generating-course'), self._make_certificate_result(status='unknown', course_key='unknown-course'), ] meter = ProgramProgressMeter(self.site, self.user) self.assertEqual(meter.completed_course_runs, [ { 'course_run_id': 'downloadable-course', 'type': 'verified' }, { 'course_run_id': 'generating-course', 'type': 'honor' }, ]) mock_get_certificates_for_user.assert_called_with(self.user.username)
def program_listing(request): """View a list of programs in which the user is engaged.""" programs_config = ProgramsApiConfig.current() if not programs_config.show_program_listing: raise Http404 meter = ProgramProgressMeter(request.user) engaged_programs = [ munge_catalog_program(program) for program in meter.engaged_programs ] progress = [ munge_progress_map(progress_map) for progress_map in meter.progress ] context = { 'credentials': get_programs_credentials(request.user), 'disable_courseware_js': True, 'marketing_url': get_program_marketing_url(programs_config), 'nav_hidden': True, 'programs': engaged_programs, 'progress': progress, 'show_program_listing': programs_config.show_program_listing, 'uses_pattern_library': True, } return render_to_response('learner_dashboard/programs.html', context)
def test_nonverified_course_run_completion(self, mock_completed_course_runs, mock_get_programs): """ Course runs aren't necessarily of type verified. Verify that a program can still be completed when this is the case. """ course_run_key = generate_course_run_key() data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key, type='honor'), CourseRunFactory(), ]), ] ), ProgramFactory(), ] mock_get_programs.return_value = data self._create_enrollments(course_run_key) mock_completed_course_runs.return_value = [ {'course_run_id': course_run_key, 'type': MODES.honor}, ] meter = ProgramProgressMeter(self.user) program, program_uuid = data[0], data[0]['uuid'] self._assert_progress( meter, ProgressFactory(uuid=program_uuid, completed=1) ) self.assertEqual(meter.completed_programs, [program_uuid])
def test_single_program_engagement(self, mock_get_programs): """ Verify that correct program is returned when the user is enrolled in a course run appearing in one program. """ course_run_key = generate_course_run_key() data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key), ]), ] ), ProgramFactory(), ] mock_get_programs.return_value = data self._create_enrollments(course_run_key) meter = ProgramProgressMeter(self.user) self._attach_detail_url(data) program = data[0] self.assertEqual(meter.engaged_programs, [program]) self._assert_progress( meter, ProgressFactory(uuid=program['uuid'], in_progress=1) ) self.assertEqual(meter.completed_programs, [])
def get_inverted_programs(student): """ Get programs keyed by course run ID. Args: student (User): Representing the student whose programs to check for. Returns: dict, programs keyed by course run ID """ inverted_programs = {} for site in Site.objects.all(): meter = ProgramProgressMeter(site, student) inverted_programs.update(meter.invert_programs()) return inverted_programs
def program_listing(request): """View a list of programs in which the user is engaged.""" programs_config = ProgramsApiConfig.current() if not programs_config.enabled: raise Http404 meter = ProgramProgressMeter(request.user) context = { 'disable_courseware_js': True, 'marketing_url': get_program_marketing_url(programs_config), 'nav_hidden': True, 'programs': meter.engaged_programs, 'progress': meter.progress(), 'show_program_listing': programs_config.enabled, 'uses_pattern_library': True, } return render_to_response('learner_dashboard/programs.html', context)
def test_no_enrollments(self, mock_get_programs): """Verify behavior when programs exist, but no relevant enrollments do.""" data = [ProgramFactory()] mock_get_programs.return_value = data meter = ProgramProgressMeter(self.user) self.assertEqual(meter.engaged_programs, []) self._assert_progress(meter) self.assertEqual(meter.completed_programs, [])
def program_details(request, program_uuid): """View details about a specific program.""" programs_config = ProgramsApiConfig.current() if not programs_config.enabled: raise Http404 meter = ProgramProgressMeter(request.site, request.user, uuid=program_uuid) program_data = meter.programs[0] if not program_data: raise Http404 program_data = ProgramDataExtender(program_data, request.user).extend() course_data = meter.progress(programs=[program_data], count_only=False)[0] certificate_data = get_certificates(request.user, program_data) program_data.pop('courses') skus = program_data.get('skus') ecommerce_service = EcommerceService() urls = { 'program_listing_url': reverse('program_listing_view'), 'track_selection_url': strip_course_id( reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY}) ), 'commerce_api_url': reverse('commerce_api:v0:baskets:create'), 'buy_button_url': ecommerce_service.get_checkout_page_url(*skus) } context = { 'urls': urls, 'show_program_listing': programs_config.enabled, 'show_dashboard_tabs': True, 'nav_hidden': True, 'disable_courseware_js': True, 'uses_pattern_library': True, 'user_preferences': get_user_preferences(request.user), 'program_data': program_data, 'course_data': course_data, 'certificate_data': certificate_data } return render_to_response('learner_dashboard/program_details.html', context)
def test_in_progress_course_upgrade_deadline_check(self, offset, mock_get_programs): """ Verify that if the user's enrollment is not of the same type as the course run, the course will only count as in progress if there is another available seat with the right type for which the upgrade deadline has not passed. """ course_run_key = generate_course_run_key() now = datetime.datetime.now(utc) upgrade_deadline = None if not offset else str(now + datetime.timedelta( days=offset)) required_seat = SeatFactory(type='verified', upgrade_deadline=upgrade_deadline) enrolled_seat = SeatFactory(type='audit') seats = [required_seat, enrolled_seat] data = [ ProgramFactory(courses=[ CourseFactory(course_runs=[ CourseRunFactory( key=course_run_key, type='verified', seats=seats), ]), ]) ] mock_get_programs.return_value = data CourseEnrollmentFactory(user=self.user, course_id=course_run_key, mode='audit') meter = ProgramProgressMeter(self.site, self.user) program = data[0] expected = [ ProgressFactory(uuid=program['uuid'], completed=0, in_progress=1 if offset in [None, 1] else 0, not_started=1 if offset in [-1] else 0) ] self.assertEqual(meter.progress(count_only=True), expected)
def test_no_programs(self, mock_get_programs): """Verify behavior when enrollments exist, but no matching programs do.""" mock_get_programs.return_value = [] course_run_id = generate_course_run_key() self._create_enrollments(course_run_id) meter = ProgramProgressMeter(self.user) self.assertEqual(meter.engaged_programs, []) self._assert_progress(meter) self.assertEqual(meter.completed_programs, [])
def render_to_fragment(self, request, program_uuid, **kwargs): """View details about a specific program.""" programs_config = kwargs.get('programs_config') or ProgramsApiConfig.current() if not programs_config.enabled or not request.user.is_authenticated(): raise Http404 meter = ProgramProgressMeter(request.site, request.user, uuid=program_uuid) program_data = meter.programs[0] if not program_data: raise Http404 program_data = ProgramDataExtender(program_data, request.user).extend() course_data = meter.progress(programs=[program_data], count_only=False)[0] certificate_data = get_certificates(request.user, program_data) program_data.pop('courses') skus = program_data.get('skus') ecommerce_service = EcommerceService() urls = { 'program_listing_url': reverse('program_listing_view'), 'track_selection_url': strip_course_id( reverse('course_modes_choose', kwargs={'course_id': FAKE_COURSE_KEY}) ), 'commerce_api_url': reverse('commerce_api:v0:baskets:create'), 'buy_button_url': ecommerce_service.get_checkout_page_url(*skus) } context = { 'urls': urls, 'user_preferences': get_user_preferences(request.user), 'program_data': program_data, 'course_data': course_data, 'certificate_data': certificate_data } html = render_to_string('learner_dashboard/program_details_fragment.html', context) program_details_fragment = Fragment(html) self.add_fragment_resource_urls(program_details_fragment) return program_details_fragment
def render_to_fragment(self, request, **kwargs): """ Render the program listing fragment. """ user = request.user programs_config = kwargs.get('programs_config') or ProgramsApiConfig.current() if not programs_config.enabled or not user.is_authenticated(): raise Http404 meter = ProgramProgressMeter(request.site, user) context = { 'marketing_url': get_program_marketing_url(programs_config), 'programs': meter.engaged_programs, 'progress': meter.progress() } html = render_to_string('learner_dashboard/programs_fragment.html', context) programs_fragment = Fragment(html) self.add_fragment_resource_urls(programs_fragment) return programs_fragment
def test_in_progress_course_upgrade_deadline_check(self, offset, mock_get_programs): """ Verify that if the user's enrollment is not of the same type as the course run, the course will only count as in progress if there is another available seat with the right type for which the upgrade deadline has not passed. """ course_run_key = generate_course_run_key() now = datetime.datetime.now(utc) upgrade_deadline = None if not offset else str(now + datetime.timedelta(days=offset)) required_seat = SeatFactory(type='verified', upgrade_deadline=upgrade_deadline) enrolled_seat = SeatFactory(type='audit') seats = [required_seat, enrolled_seat] data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key, type='verified', seats=seats), ]), ] ) ] mock_get_programs.return_value = data CourseEnrollmentFactory(user=self.user, course_id=course_run_key, mode='audit') meter = ProgramProgressMeter(self.site, self.user) program = data[0] expected = [ ProgressFactory( uuid=program['uuid'], completed=0, in_progress=1 if offset in [None, 1] else 0, not_started=1 if offset in [-1] else 0, grades={course_run_key: 0.0}, ) ] self.assertEqual(meter.progress(count_only=True), expected)
def get_completed_programs(student): """ Given a set of completed courses, determine which programs are completed. Args: student (User): Representing the student whose completed programs to check for. Returns: list of program UUIDs """ meter = ProgramProgressMeter(student) return meter.completed_programs
def test_in_progress_course_upgrade_deadline_check(self, modifier, mock_get_programs): """ Verify that if the user's enrollment is not of the same type as the course run, the course will only count as in progress if there is another available seat with the right type, where the upgrade deadline has not expired. """ course_run_key = generate_course_run_key() now = datetime.datetime.now(utc) date_modifier = modifier * datetime.timedelta(days=1) seat_with_upgrade_deadline = SeatFactory(type='test', upgrade_deadline=str(now + date_modifier)) enrolled_seat = SeatFactory(type='verified') seats = [seat_with_upgrade_deadline, enrolled_seat] data = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=course_run_key, type='test', seats=seats), ]), ] ) ] mock_get_programs.return_value = data self._create_enrollments(course_run_key) meter = ProgramProgressMeter(self.user) program = data[0] expected = [ ProgressFactory( uuid=program['uuid'], completed=0, in_progress=1 if modifier == 1 else 0, not_started=1 if modifier == -1 else 0 ) ] self.assertEqual(meter.progress(count_only=True), expected)
def test_shared_enrollment_engagement(self, mock_get_programs): """ Verify that correct programs are returned when the user is enrolled in a single course run appearing in multiple programs. """ shared_course_run_key, solo_course_run_key = (generate_course_run_key() for __ in range(2)) batch = [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=shared_course_run_key), ]), ] ) for __ in range(2) ] joint_programs = sorted(batch, key=lambda program: program['title']) data = joint_programs + [ ProgramFactory( courses=[ CourseFactory(course_runs=[ CourseRunFactory(key=solo_course_run_key), ]), ] ), ProgramFactory(), ] mock_get_programs.return_value = data # Enrollment for the shared course run created last (most recently). self._create_enrollments(solo_course_run_key, shared_course_run_key) meter = ProgramProgressMeter(self.site, self.user) self._attach_detail_url(data) programs = data[:3] self.assertEqual(meter.engaged_programs, programs) grades = { solo_course_run_key: 0.0, shared_course_run_key: 0.0, } self._assert_progress( meter, *(ProgressFactory(uuid=program['uuid'], in_progress=1, grades=grades) for program in programs) ) self.assertEqual(meter.completed_programs, [])
def get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) modes = CourseMode.modes_for_course_dict(course_key) ecommerce_service = EcommerceService() # We assume that, if 'professional' is one of the modes, it should be the *only* mode. # If there are both modes, default to non-id-professional. has_enrolled_professional = (CourseMode.is_professional_slug(enrollment_mode) and is_active) if CourseMode.has_professional_mode(modes) and not has_enrolled_professional: purchase_workflow = request.GET.get("purchase_workflow", "single") verify_url = reverse('verify_student_start_flow', kwargs={'course_id': unicode(course_key)}) redirect_url = "{url}?purchase_workflow={workflow}".format(url=verify_url, workflow=purchase_workflow) if ecommerce_service.is_enabled(request.user): professional_mode = modes.get(CourseMode.NO_ID_PROFESSIONAL_MODE) or modes.get(CourseMode.PROFESSIONAL) if purchase_workflow == "single" and professional_mode.sku: redirect_url = ecommerce_service.get_checkout_page_url(professional_mode.sku) if purchase_workflow == "bulk" and professional_mode.bulk_sku: redirect_url = ecommerce_service.get_checkout_page_url(professional_mode.bulk_sku) return redirect(redirect_url) course = modulestore().get_course(course_key) # If there isn't a verified mode available, then there's nothing # to do on this page. Send the user to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse('dashboard')) # If a user has already paid, redirect them to the dashboard. if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]): # If the course has started redirect to course home instead if course.has_started(): return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key})) return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) if CourseEnrollment.is_enrollment_closed(request.user, course): locale = to_locale(get_language()) enrollment_end_date = format_datetime(course.enrollment_end, 'short', locale=locale) params = urllib.urlencode({'course_closed': enrollment_end_date}) return redirect('{0}?{1}'.format(reverse('dashboard'), params)) # When a credit mode is available, students will be given the option # to upgrade from a verified mode to a credit mode at the end of the course. # This allows students who have completed photo verification to be eligible # for univerity credit. # Since credit isn't one of the selectable options on the track selection page, # we need to check *all* available course modes in order to determine whether # a credit mode is available. If so, then we show slightly different messaging # for the verified track. has_credit_upsell = any( CourseMode.is_credit_mode(mode) for mode in CourseMode.modes_for_course(course_key, only_selectable=False) ) course_id = text_type(course_key) bundle_data = {} bundles_on_track_selection = WaffleFlag(WaffleFlagNamespace(name=u'experiments'), u'bundles_on_track_selection') if bundles_on_track_selection.is_enabled(): # enrollment in the course on this page current_enrollment = list(CourseEnrollment.enrollments_for_user(request.user).filter(course_id=course_key)) if current_enrollment: meter = ProgramProgressMeter(request.site, request.user, enrollments=current_enrollment) meter_inverted_programs = meter.invert_programs() if len(meter_inverted_programs) > 0: # program for the course on this page programs_for_course = meter_inverted_programs.get(course_id) if programs_for_course: program_for_course = programs_for_course[0] program_uuid = program_for_course.get('uuid') if program_for_course: # program data with bundle info program_data = ProgramDataExtender(program_for_course, request.user, mobile_only=False).extend() skus = program_data.get('skus') ecommerce_service = EcommerceService() program_bundle_url = ecommerce_service.get_checkout_page_url(*skus, program_uuid=program_uuid) bundle_data = { 'program_marketing_site_url': program_data.get('marketing_url'), 'program_bundle_url': program_bundle_url, 'discount_data': program_data.get('discount_data'), 'program_type': program_data.get('type'), 'program_title': program_data.get('title'), 'program_price': program_data.get('full_program_price'), } context = { "bundle_data": bundle_data, "course_modes_choose_url": reverse( "course_modes_choose", kwargs={'course_id': course_id} ), "modes": modes, "has_credit_upsell": has_credit_upsell, "course_name": course.display_name_with_default, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "responsive": True, "nav_hidden": True, "content_gating_enabled": ContentTypeGatingConfig.enabled_for_course(course_key=course_key), } context.update( get_experiment_user_metadata_context( course, request.user, ) ) title_content = _("Congratulations! You are now enrolled in {course_name}").format( course_name=course.display_name_with_default ) context["title_content"] = title_content if "verified" in modes: verified_mode = modes["verified"] context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in verified_mode.suggested_prices.split(",") if x.strip() ] context["currency"] = verified_mode.currency.upper() context["min_price"] = verified_mode.min_price context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description if verified_mode.sku: context["use_ecommerce_payment_flow"] = ecommerce_service.is_enabled(request.user) context["ecommerce_payment_page"] = ecommerce_service.payment_page_url() context["sku"] = verified_mode.sku context["bulk_sku"] = verified_mode.bulk_sku context['currency_data'] = [] if waffle.switch_is_active('local_currency'): if 'edx-price-l10n' not in request.COOKIES: currency_data = get_currency_data() try: context['currency_data'] = json.dumps(currency_data) except TypeError: pass return render_to_response("course_modes/choose.html", context)
def student_dashboard(request): """ Provides the LMS dashboard view TODO: This is lms specific and does not belong in common code. Arguments: request: The request object. Returns: The dashboard response. """ user = request.user if not UserProfile.objects.filter(user=user).exists(): return redirect(reverse('account_settings')) platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME) enable_verified_certificates = configuration_helpers.get_value( 'ENABLE_VERIFIED_CERTIFICATES', settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES') ) display_course_modes_on_dashboard = configuration_helpers.get_value( 'DISPLAY_COURSE_MODES_ON_DASHBOARD', settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True) ) activation_email_support_link = configuration_helpers.get_value( 'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK ) or settings.SUPPORT_SITE_LINK # Get the org whitelist or the org blacklist for the current site site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site() course_enrollments = list(get_course_enrollments(user, site_org_whitelist, site_org_blacklist)) # Get the entitlements for the user and a mapping to all available sessions for that entitlement # If an entitlement has no available sessions, pass through a mock course overview object (course_entitlements, course_entitlement_available_sessions, unfulfilled_entitlement_pseudo_sessions) = get_filtered_course_entitlements( user, site_org_whitelist, site_org_blacklist ) # Record how many courses there are so that we can get a better # understanding of usage patterns on prod. monitoring_utils.accumulate('num_courses', len(course_enrollments)) # Sort the enrollment pairs by the enrollment date course_enrollments.sort(key=lambda x: x.created, reverse=True) # Retrieve the course modes for each course enrolled_course_ids = [enrollment.course_id for enrollment in course_enrollments] __, unexpired_course_modes = CourseMode.all_and_unexpired_modes_for_courses(enrolled_course_ids) course_modes_by_course = { course_id: { mode.slug: mode for mode in modes } for course_id, modes in iteritems(unexpired_course_modes) } # Check to see if the student has recently enrolled in a course. # If so, display a notification message confirming the enrollment. enrollment_message = _create_recent_enrollment_message( course_enrollments, course_modes_by_course ) course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True) sidebar_account_activation_message = '' banner_account_activation_message = '' display_account_activation_message_on_sidebar = configuration_helpers.get_value( 'DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR', settings.FEATURES.get('DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR', False) ) # Display activation message in sidebar if DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR # flag is active. Otherwise display existing message at the top. if display_account_activation_message_on_sidebar and not user.is_active: sidebar_account_activation_message = render_to_string( 'registration/account_activation_sidebar_notice.html', { 'email': user.email, 'platform_name': platform_name, 'activation_email_support_link': activation_email_support_link } ) elif not user.is_active: banner_account_activation_message = render_to_string( 'registration/activate_account_notice.html', {'email': user.email} ) enterprise_message = get_dashboard_consent_notification(request, user, course_enrollments) # Disable lookup of Enterprise consent_required_course due to ENT-727 # Will re-enable after fixing WL-1315 consent_required_courses = set() enterprise_customer_name = None # Account activation message account_activation_messages = [ message for message in messages.get_messages(request) if 'account-activation' in message.tags ] # Global staff can see what courses encountered an error on their dashboard staff_access = False errored_courses = {} if has_access(user, 'staff', 'global'): # Show any courses that encountered an error on load staff_access = True errored_courses = modulestore().get_errored_courses() show_courseware_links_for = frozenset( enrollment.course_id for enrollment in course_enrollments if has_access(request.user, 'load', enrollment.course_overview) ) # Find programs associated with course runs being displayed. This information # is passed in the template context to allow rendering of program-related # information on the dashboard. meter = ProgramProgressMeter(request.site, user, enrollments=course_enrollments) ecommerce_service = EcommerceService() inverted_programs = meter.invert_programs() urls, programs_data = {}, {} bundles_on_dashboard_flag = WaffleFlag(WaffleFlagNamespace(name=u'student.experiments'), u'bundles_on_dashboard') # TODO: Delete this code and the relevant HTML code after testing LEARNER-3072 is complete if bundles_on_dashboard_flag.is_enabled() and inverted_programs and inverted_programs.items(): if len(course_enrollments) < 4: for program in inverted_programs.values(): try: program_uuid = program[0]['uuid'] program_data = get_programs(request.site, uuid=program_uuid) program_data = ProgramDataExtender(program_data, request.user).extend() skus = program_data.get('skus') checkout_page_url = ecommerce_service.get_checkout_page_url(*skus) program_data['completeProgramURL'] = checkout_page_url + '&bundle=' + program_data.get('uuid') programs_data[program_uuid] = program_data except: # pylint: disable=bare-except pass # Construct a dictionary of course mode information # used to render the course list. We re-use the course modes dict # we loaded earlier to avoid hitting the database. course_mode_info = { enrollment.course_id: complete_course_mode_info( enrollment.course_id, enrollment, modes=course_modes_by_course[enrollment.course_id] ) for enrollment in course_enrollments } # Determine the per-course verification status # This is a dictionary in which the keys are course locators # and the values are one of: # # VERIFY_STATUS_NEED_TO_VERIFY # VERIFY_STATUS_SUBMITTED # VERIFY_STATUS_APPROVED # VERIFY_STATUS_MISSED_DEADLINE # # Each of which correspond to a particular message to display # next to the course on the dashboard. # # If a course is not included in this dictionary, # there is no verification messaging to display. verify_status_by_course = check_verify_status_by_course(user, course_enrollments) cert_statuses = { enrollment.course_id: cert_info(request.user, enrollment.course_overview) for enrollment in course_enrollments } # only show email settings for Mongo course and when bulk email is turned on show_email_settings_for = frozenset( enrollment.course_id for enrollment in course_enrollments if ( BulkEmailFlag.feature_enabled(enrollment.course_id) ) ) # Verification Attempts # Used to generate the "you must reverify for course x" banner verification_status = IDVerificationService.user_status(user) verification_errors = get_verification_error_reasons_for_display(verification_status['error']) # Gets data for midcourse reverifications, if any are necessary or have failed statuses = ["approved", "denied", "pending", "must_reverify"] reverifications = reverification_info(statuses) block_courses = frozenset( enrollment.course_id for enrollment in course_enrollments if is_course_blocked( request, CourseRegistrationCode.objects.filter( course_id=enrollment.course_id, registrationcoderedemption__redeemed_by=request.user ), enrollment.course_id ) ) enrolled_courses_either_paid = frozenset( enrollment.course_id for enrollment in course_enrollments if enrollment.is_paid_course() ) # If there are *any* denied reverifications that have not been toggled off, # we'll display the banner denied_banner = any(item.display for item in reverifications["denied"]) # Populate the Order History for the side-bar. order_history_list = order_history( user, course_org_filter=site_org_whitelist, org_filter_out_set=site_org_blacklist ) # get list of courses having pre-requisites yet to be completed courses_having_prerequisites = frozenset( enrollment.course_id for enrollment in course_enrollments if enrollment.course_overview.pre_requisite_courses ) courses_requirements_not_met = get_pre_requisite_courses_not_completed(user, courses_having_prerequisites) if 'notlive' in request.GET: redirect_message = _("The course you are looking for does not start until {date}.").format( date=request.GET['notlive'] ) elif 'course_closed' in request.GET: redirect_message = _("The course you are looking for is closed for enrollment as of {date}.").format( date=request.GET['course_closed'] ) else: redirect_message = '' valid_verification_statuses = ['approved', 'must_reverify', 'pending', 'expired'] display_sidebar_on_dashboard = (len(order_history_list) or (verification_status['status'] in valid_verification_statuses and verification_status['should_display'])) # Filter out any course enrollment course cards that are associated with fulfilled entitlements for entitlement in [e for e in course_entitlements if e.enrollment_course_run is not None]: course_enrollments = [ enr for enr in course_enrollments if entitlement.enrollment_course_run.course_id != enr.course_id ] context = { 'urls': urls, 'programs_data': programs_data, 'enterprise_message': enterprise_message, 'consent_required_courses': consent_required_courses, 'enterprise_customer_name': enterprise_customer_name, 'enrollment_message': enrollment_message, 'redirect_message': redirect_message, 'account_activation_messages': account_activation_messages, 'course_enrollments': course_enrollments, 'course_entitlements': course_entitlements, 'course_entitlement_available_sessions': course_entitlement_available_sessions, 'unfulfilled_entitlement_pseudo_sessions': unfulfilled_entitlement_pseudo_sessions, 'course_optouts': course_optouts, 'banner_account_activation_message': banner_account_activation_message, 'sidebar_account_activation_message': sidebar_account_activation_message, 'staff_access': staff_access, 'errored_courses': errored_courses, 'show_courseware_links_for': show_courseware_links_for, 'all_course_modes': course_mode_info, 'cert_statuses': cert_statuses, 'credit_statuses': _credit_statuses(user, course_enrollments), 'show_email_settings_for': show_email_settings_for, 'reverifications': reverifications, 'verification_display': verification_status['should_display'], 'verification_status': verification_status['status'], 'verification_status_by_course': verify_status_by_course, 'verification_errors': verification_errors, 'block_courses': block_courses, 'denied_banner': denied_banner, 'billing_email': settings.PAYMENT_SUPPORT_EMAIL, 'user': user, 'logout_url': reverse('logout'), 'platform_name': platform_name, 'enrolled_courses_either_paid': enrolled_courses_either_paid, 'provider_states': [], 'order_history_list': order_history_list, 'courses_requirements_not_met': courses_requirements_not_met, 'nav_hidden': True, 'inverted_programs': inverted_programs, 'show_program_listing': ProgramsApiConfig.is_enabled(), 'show_dashboard_tabs': True, 'disable_courseware_js': True, 'display_course_modes_on_dashboard': enable_verified_certificates and display_course_modes_on_dashboard, 'display_sidebar_on_dashboard': display_sidebar_on_dashboard, } if ecommerce_service.is_enabled(request.user): context.update({ 'use_ecommerce_payment_flow': True, 'ecommerce_payment_page': ecommerce_service.payment_page_url(), }) # Gather urls for course card resume buttons. resume_button_urls = _get_urls_for_resume_buttons(user, course_enrollments) # There must be enough urls for dashboard.html. Template creates course # cards for "enrollments + entitlements". resume_button_urls += ['' for entitlement in course_entitlements] context.update({ 'resume_button_urls': resume_button_urls }) response = render_to_response('dashboard.html', context) set_user_info_cookie(response, request) return response