def setUp(self): """ Create a test course. """ super(AdminCourseModeFormTest, self).setUp() self.course = CourseFactory.create() CourseOverview.load_from_module_store(self.course.id)
def setup_course(self, default_store=None): """ Helper method to create the course. """ if not default_store: default_store = self.store.default_modulestore.get_modulestore_type() with self.store.default_store(default_store): self.course = CourseFactory.create(**self.course_options()) chapter = ItemFactory.create(parent=self.course, category='chapter') self.vertical_block = ItemFactory.create( parent_location=chapter.location, category='vertical', display_name="Vertical" ) self.html_block = ItemFactory.create( parent=self.vertical_block, category='html', data="<p>Test HTML Content<p>" ) CourseOverview.load_from_module_store(self.course.id) # block_name_to_be_tested can be `html_block` or `vertical_block`. # These attributes help ensure the positive and negative tests are in sync. self.block_to_be_tested = getattr(self, self.block_name_to_be_tested) self.block_specific_chrome_html_elements = self.BLOCK_SPECIFIC_CHROME_HTML_ELEMENTS[ self.block_name_to_be_tested ]
def handle(self, *args, **options): course_keys = [] if options['all']: course_keys = [course.id for course in modulestore().get_courses()] else: if len(args) < 1: raise CommandError('At least one course or --all must be specified.') try: course_keys = [CourseKey.from_string(arg) for arg in args] except InvalidKeyError: log.fatal('Invalid key specified.') if not course_keys: log.fatal('No courses specified.') log.info('Generating course overview for %d courses.', len(course_keys)) log.debug('Generating course overview(s) for the following courses: %s', course_keys) for course_key in course_keys: try: CourseOverview.get_from_id(course_key) except Exception as ex: # pylint: disable=broad-except log.exception('An error occurred while generating course overview for %s: %s', unicode( course_key), ex.message) log.info('Finished generating course overviews.')
def test_bulk_convert_with_org(self, from_mode, to_mode, mock_tracker): """Verify that enrollments are changed correctly when org was given.""" self._enroll_users(self.course, self.users, from_mode) CourseModeFactory(course_id=self.course.id, mode_slug=to_mode) # Create a second course under the same org course_2 = CourseFactory.create(org=self.org) CourseModeFactory(course_id=course_2.id, mode_slug=to_mode) CourseOverview.load_from_module_store(course_2.id) self._enroll_users(course_2, self.users, from_mode) # Verify that no users are in the `to` mode yet. self.assertEqual(len(CourseEnrollment.objects.filter(mode=to_mode, course_id=self.course.id)), 0) self.assertEqual(len(CourseEnrollment.objects.filter(mode=to_mode, course_id=course_2.id)), 0) call_command( 'bulk_change_enrollment', org=self.org, from_mode=from_mode, to_mode=to_mode, commit=True, ) # Verify that all users have been moved -- if not, this will # raise CourseEnrollment.DoesNotExist for user in self.users: for course in [self.course, course_2]: CourseEnrollment.objects.get(mode=to_mode, course_id=course.id, user=user) self._assert_mode_changed(mock_tracker, course, user, to_mode)
def test_with_org_and_invalid_to_mode(self, mock_tracker): """Verify that enrollments are changed correctly when org was given.""" from_mode = 'audit' to_mode = 'no-id-professional' self._enroll_users(self.course, self.users, from_mode) # Create a second course under the same org course_2 = CourseFactory.create(org=self.org) CourseModeFactory(course_id=course_2.id, mode_slug=to_mode) CourseOverview.load_from_module_store(course_2.id) self._enroll_users(course_2, self.users, from_mode) # Verify that no users are in the `to` mode yet. self.assertEqual(len(CourseEnrollment.objects.filter(mode=to_mode, course_id=self.course.id)), 0) self.assertEqual(len(CourseEnrollment.objects.filter(mode=to_mode, course_id=course_2.id)), 0) call_command( 'bulk_change_enrollment', org=self.org, from_mode=from_mode, to_mode=to_mode, commit=True, ) # Verify that users were not moved for the invalid course/mode combination for user in self.users: with self.assertRaises(CourseEnrollment.DoesNotExist): CourseEnrollment.objects.get(mode=to_mode, course_id=self.course.id, user=user) # Verify that all users have been moved -- if not, this will # raise CourseEnrollment.DoesNotExist for user in self.users: CourseEnrollment.objects.get(mode=to_mode, course_id=course_2.id, user=user) self._assert_mode_changed(mock_tracker, course_2, user, to_mode)
def test_enrollments_not_deleted(self): """ Recreating a CourseOverview with an outdated version should not delete the associated enrollment. """ course = CourseFactory(self_paced=True) CourseModeFactory( course_id=course.id, mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30), ) # Create a CourseOverview with an outdated version course_overview = CourseOverview.load_from_module_store(course.id) course_overview.version = CourseOverview.VERSION - 1 course_overview.save() # Create an inactive enrollment with this course overview enrollment = CourseEnrollmentFactory( user=self.user, course_id=course.id, mode=CourseMode.AUDIT, course=course_overview, ) # Re-fetch the CourseOverview record. # As a side effect, this will recreate the record, and update the version. course_overview_new = CourseOverview.get_from_id(course.id) self.assertEqual(course_overview_new.version, CourseOverview.VERSION) # Ensure that the enrollment record was unchanged during this re-creation enrollment_refetched = CourseEnrollment.objects.filter(id=enrollment.id) self.assertTrue(enrollment_refetched.exists()) self.assertEqual(enrollment_refetched.all()[0], enrollment)
def setUp(self): super(TestSyncCourseRunsCommand, self).setUp() # create mongo course self.course = CourseFactory.create() # load this course into course overview CourseOverview.get_from_id(self.course.id) # create a catalog course run with the same course id. self.catalog_course_run = CourseRunFactory( key=unicode(self.course.id), marketing_url='test_marketing_url' )
def course_run(self): """ The course run specified by the `course_id` URL parameter. """ try: CourseOverview.get_from_id(self.course_key) except CourseOverview.DoesNotExist: raise Http404() for course in self.program["courses"]: for course_run in course["course_runs"]: if self.course_key == CourseKey.from_string(course_run["key"]): return course_run raise Http404()
def handle(self, *args, **options): if options['all']: course_keys = [course.id for course in modulestore().get_course_summaries()] else: if len(args) < 1: raise CommandError('At least one course or --all must be specified.') try: course_keys = [CourseKey.from_string(arg) for arg in args] except InvalidKeyError: raise CommandError('Invalid key specified.') CourseOverview.update_select_courses(course_keys, force_update=options.get('force_update'))
def test_course_overview_deleted(self): """Check that course overview is deleted after course published signal is sent """ course_key = CCXLocator.from_course_locator(self.course.id, self.ccx.id) overview = CourseOverview(id=course_key) overview.version = 1 overview.save() overview = CourseOverview.objects.filter(id=course_key) self.assertEqual(len(overview), 1) with mock_signal_receiver(SignalHandler.course_published) as receiver: self.call_fut(self.course.id) self.assertEqual(receiver.call_count, 3) overview = CourseOverview.objects.filter(id=course_key) self.assertEqual(len(overview), 0)
def get_visible_courses(org=None, filter_=None): """ Yield the CourseOverviews that should be visible in this branded instance. Arguments: org (string): Optional parameter that allows case-insensitive filtering by organization. filter_ (dict): Optional parameter that allows custom filtering by fields on the course. """ # Import is placed here to avoid model import at project startup. from openedx.core.djangoapps.content.course_overviews.models import CourseOverview current_site_orgs = configuration_helpers.get_current_site_orgs() courses = CourseOverview.objects.none() if org: # Check the current site's orgs to make sure the org's courses should be displayed if not current_site_orgs or org in current_site_orgs: courses = CourseOverview.get_all_courses(orgs=[org], filter_=filter_) elif current_site_orgs: # Only display courses that should be displayed on this site courses = CourseOverview.get_all_courses(orgs=current_site_orgs, filter_=filter_) else: courses = CourseOverview.get_all_courses(filter_=filter_) courses = courses.order_by('id') # Filtering can stop here. if current_site_orgs: return courses # See if we have filtered course listings in this domain filtered_visible_ids = None # this is legacy format, which also handle dev case, which should not filter subdomain = configuration_helpers.get_value('subdomain', 'default') if hasattr(settings, 'COURSE_LISTINGS') and subdomain in settings.COURSE_LISTINGS and not settings.DEBUG: filtered_visible_ids = frozenset( [CourseKey.from_string(c) for c in settings.COURSE_LISTINGS[subdomain]] ) if filtered_visible_ids: return courses.filter(id__in=filtered_visible_ids) else: # Filter out any courses based on current org, to avoid leaking these. orgs = configuration_helpers.get_all_orgs() return courses.exclude(org__in=orgs)
def get_visible_courses(org=None, filter_=None): """ Return the set of CourseOverviews that should be visible in this branded instance. Arguments: org (string): Optional parameter that allows case-insensitive filtering by organization. filter_ (dict): Optional parameter that allows custom filtering by fields on the course. """ microsite_org = microsite.get_value('course_org_filter') if org and microsite_org: # When called in the context of a microsite, return an empty result if the org # passed by the caller does not match the designated microsite org. courses = CourseOverview.get_all_courses( org=org, filter_=filter_, ) if org == microsite_org else [] else: # We only make it to this point if one of org or microsite_org is defined. # If both org and microsite_org were defined, the code would have fallen into the # first branch of the conditional above, wherein an equality check is performed. target_org = org or microsite_org courses = CourseOverview.get_all_courses(org=target_org, filter_=filter_) courses = sorted(courses, key=lambda course: course.number) # When called in the context of a microsite, filtering can stop here. if microsite_org: return courses # See if we have filtered course listings in this domain filtered_visible_ids = None # this is legacy format which is outside of the microsite feature -- also handle dev case, which should not filter subdomain = microsite.get_value('subdomain', 'default') if hasattr(settings, 'COURSE_LISTINGS') and subdomain in settings.COURSE_LISTINGS and not settings.DEBUG: filtered_visible_ids = frozenset( [SlashSeparatedCourseKey.from_deprecated_string(c) for c in settings.COURSE_LISTINGS[subdomain]] ) if filtered_visible_ids: return [course for course in courses if course.id in filtered_visible_ids] else: # Filter out any courses belonging to a microsite, to avoid leaking these. microsite_orgs = microsite.get_all_orgs() return [course for course in courses if course.location.org not in microsite_orgs]
def generate_certificate_for_user(request): """ Generate certificates for a user. This is meant to be used by support staff through the UI in lms/djangoapps/support Arguments: request (HttpRequest): The request object Returns: HttpResponse Example Usage: POST /certificates/generate * username: "******" * course_key: "edX/DemoX/Demo_Course" Response: 200 OK """ # Check the POST parameters, returning a 400 response if they're not valid. params, response = _validate_post_params(request.POST) if response is not None: return response try: # Check that the course exists CourseOverview.get_from_id(params["course_key"]) except CourseOverview.DoesNotExist: msg = _("The course {course_key} does not exist").format(course_key=params["course_key"]) return HttpResponseBadRequest(msg) else: # Check that the user is enrolled in the course if not CourseEnrollment.is_enrolled(params["user"], params["course_key"]): msg = _("User {username} is not enrolled in the course {course_key}").format( username=params["user"].username, course_key=params["course_key"] ) return HttpResponseBadRequest(msg) # Attempt to generate certificate generate_certificates_for_students( request, params["course_key"], student_set="specific_student", specific_student_id=params["user"].id ) return HttpResponse(200)
def get_days_until_expiration(self, entitlement): """ Returns an integer of number of days until the entitlement expires. Includes the logic for regaining an entitlement. """ now_timestamp = now() expiry_date = entitlement.created + self.expiration_period days_until_expiry = (expiry_date - now_timestamp).days if not entitlement.enrollment_course_run: return days_until_expiry course_overview = CourseOverview.get_from_id(entitlement.enrollment_course_run.course_id) # Compute the days left for the regain days_since_course_start = (now_timestamp - course_overview.start).days days_since_enrollment = (now_timestamp - entitlement.enrollment_course_run.created).days days_since_entitlement_created = (now_timestamp - entitlement.created).days # We want to return whichever days value is less since it is then the more recent one days_until_regain_ends = (self.regain_period.days - # pylint: disable=no-member min(days_since_course_start, days_since_enrollment, days_since_entitlement_created)) # If the base days until expiration is less than the days until the regain period ends, use that instead if days_until_expiry < days_until_regain_ends: return days_until_expiry return days_until_regain_ends # pylint: disable=no-member
def test_student_has_access(self): """ Tests course student have right access to content w/o preview. """ course_key = self.course.id chapter = ItemFactory.create(category="chapter", parent_location=self.course.location) overview = CourseOverview.get_from_id(course_key) # Enroll student to the course CourseEnrollmentFactory(user=self.student, course_id=self.course.id) modules = [ self.course, overview, chapter, ] with patch('courseware.access.in_preview_mode') as mock_preview: mock_preview.return_value = False for obj in modules: self.assertTrue(bool(access.has_access(self.student, 'load', obj, course_key=self.course.id))) with patch('courseware.access.in_preview_mode') as mock_preview: mock_preview.return_value = True for obj in modules: self.assertFalse(bool(access.has_access(self.student, 'load', obj, course_key=self.course.id)))
def _assert_courses_in_overview(self, *courses): """ Assert courses exists in course overviews. """ course_keys = CourseOverview.get_all_course_keys() for expected_course_key in courses: self.assertIn(expected_course_key, course_keys)
def _assert_courses_not_in_overview(self, *courses): """ Assert that courses doesn't exist in the course overviews. """ course_keys = CourseOverview.get_all_course_keys() for expected_course_key in courses: self.assertNotIn(expected_course_key, course_keys)
def _assert_supplemented(self, actual, **kwargs): """DRY helper used to verify that program data is extended correctly.""" course_overview = CourseOverview.get_from_id(self.course.id) # pylint: disable=no-member run_mode = dict( factories.RunMode( certificate_url=None, course_image_url=course_overview.course_image_url, course_key=unicode(self.course.id), # pylint: disable=no-member course_url=reverse('course_root', args=[self.course.id]), # pylint: disable=no-member end_date=strftime_localized(self.course.end, 'SHORT_DATE'), enrollment_open_date=strftime_localized(utils.DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), is_course_ended=self.course.end < timezone.now(), is_enrolled=False, is_enrollment_open=True, marketing_url=MARKETING_URL, start_date=strftime_localized(self.course.start, 'SHORT_DATE'), upgrade_url=None, ), **kwargs ) course_code = factories.CourseCode(display_name=self.course_code['display_name'], run_modes=[run_mode]) expected = copy.deepcopy(self.program) expected['course_codes'] = [course_code] self.assertEqual(actual, expected)
def get_course_enrollment_info(course_id, include_expired=False): """Returns all course enrollment information for the given course. Based on the course id, return all related course information. Args: course_id (str): The course to retrieve enrollment information for. include_expired (bool): Boolean denoting whether expired course modes should be included in the returned JSON data. Returns: A serializable dictionary representing the course's enrollment information. Raises: CourseNotFoundError """ course_key = CourseKey.from_string(course_id) try: course = CourseOverview.get_from_id(course_key) except CourseOverview.DoesNotExist: msg = u"Requested enrollment information for unknown course {course}".format(course=course_id) log.warning(msg) raise CourseNotFoundError(msg) else: return CourseSerializer(course, include_expired=include_expired).data
def certificate_downloadable_status(student, course_key): """ Check the student existing certificates against a given course. if status is not generating and not downloadable or error then user can view the generate button. Args: student (user object): logged-in user course_key (CourseKey): ID associated with the course Returns: Dict containing student passed status also download url, uuid for cert if available """ current_status = certificate_status_for_student(student, course_key) # If the certificate status is an error user should view that status is "generating". # On the back-end, need to monitor those errors and re-submit the task. response_data = { 'is_downloadable': False, 'is_generating': True if current_status['status'] in [CertificateStatuses.generating, CertificateStatuses.error] else False, 'is_unverified': True if current_status['status'] == CertificateStatuses.unverified else False, 'download_url': None, 'uuid': None, } may_view_certificate = CourseOverview.get_from_id(course_key).may_certify() if current_status['status'] == CertificateStatuses.downloadable and may_view_certificate: response_data['is_downloadable'] = True response_data['download_url'] = current_status['download_url'] or get_certificate_url(student.id, course_key) response_data['uuid'] = current_status['uuid'] return response_data
def delete(self, request, ccx_course_id=None): # pylint: disable=unused-argument """ Deletes a CCX course. Args: request (Request): Django request object. ccx_course_id (string): URI element specifying the CCX course location. """ ccx_course_object, ccx_course_key, error_code, http_status = self.get_object(ccx_course_id, is_ccx=True) if ccx_course_object is None: return Response( status=http_status, data={ 'error_code': error_code } ) ccx_course_overview = CourseOverview.get_from_id(ccx_course_key) # clean everything up with a single transaction with transaction.atomic(): CcxFieldOverride.objects.filter(ccx=ccx_course_object).delete() # remove all users enrolled in the CCX from the CourseEnrollment model CourseEnrollment.objects.filter(course_id=ccx_course_key).delete() ccx_course_overview.delete() ccx_course_object.delete() return Response( status=status.HTTP_204_NO_CONTENT, )
def test_generate_force_update(self): self.command.handle(all=True) # update each course updated_course_name = u'test_generate_course_overview.course_edit' for course_key in (self.course_key_1, self.course_key_2): course = self.store.get_course(course_key) course.display_name = updated_course_name self.store.update_item(course, self.user.id) # force_update course_key_1, but not course_key_2 self.command.handle(unicode(self.course_key_1), all=False, force_update=True) self.command.handle(unicode(self.course_key_2), all=False, force_update=False) self.assertEquals(CourseOverview.get_from_id(self.course_key_1).display_name, updated_course_name) self.assertNotEquals(CourseOverview.get_from_id(self.course_key_2).display_name, updated_course_name)
def test_expired_course(self): """ Ensure that a user accessing an expired course sees a redirect to the student dashboard, not a 404. """ CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2010, 1, 1)) course = CourseFactory.create(start=THREE_YEARS_AGO) url = course_home_url(course) for mode in [CourseMode.AUDIT, CourseMode.VERIFIED]: CourseModeFactory.create(course_id=course.id, mode_slug=mode) # assert that an if an expired audit user tries to access the course they are redirected to the dashboard audit_user = UserFactory(password=self.TEST_PASSWORD) self.client.login(username=audit_user.username, password=self.TEST_PASSWORD) audit_enrollment = CourseEnrollment.enroll(audit_user, course.id, mode=CourseMode.AUDIT) ScheduleFactory(start=THREE_YEARS_AGO, enrollment=audit_enrollment) response = self.client.get(url) expiration_date = strftime_localized(course.start + timedelta(weeks=4), 'SHORT_DATE') expected_params = QueryDict(mutable=True) course_name = CourseOverview.get_from_id(course.id).display_name_with_default expected_params['access_response_error'] = 'Access to {run} expired on {expiration_date}'.format( run=course_name, expiration_date=expiration_date ) expected_url = '{url}?{params}'.format( url=reverse('dashboard'), params=expected_params.urlencode() ) self.assertRedirects(response, expected_url)
def test_masquerade_expired(self, mock_get_course_run_details): mock_get_course_run_details.return_value = {'weeks_to_complete': 1} audit_student = UserFactory(username='******') enrollment = CourseEnrollmentFactory.create( user=audit_student, course_id=self.course.id, mode='audit', ) enrollment.created = self.course.start enrollment.save() CourseDurationLimitConfig.objects.create( enabled=True, course=CourseOverview.get_from_id(self.course.id), enabled_as_of=self.course.start, ) instructor = UserFactory.create(username='******') CourseEnrollmentFactory.create( user=instructor, course_id=self.course.id, mode='audit' ) CourseInstructorRole(self.course.id).add_users(instructor) self.client.login(username=instructor.username, password='******') self.update_masquerade(username='******') course_home_url = reverse('openedx.course_experience.course_home', args=[six.text_type(self.course.id)]) response = self.client.get(course_home_url, follow=True) self.assertEqual(response.status_code, 200) self.assertItemsEqual(response.redirect_chain, []) banner_text = 'This learner does not have access to this course. Their access expired on' self.assertIn(banner_text, response.content)
def setUp(self): """ Create a course and user, then log in. """ super(EnrollmentTest, self).setUp() self.rate_limit_config = RateLimitConfiguration.current() self.rate_limit_config.enabled = False self.rate_limit_config.save() throttle = EnrollmentUserThrottle() self.rate_limit, rate_duration = throttle.parse_rate(throttle.rate) self.course = CourseFactory.create() # Load a CourseOverview. This initial load should result in a cache # miss; the modulestore is queried and course metadata is cached. __ = CourseOverview.get_from_id(self.course.id) self.user = UserFactory.create( username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD, ) self.other_user = UserFactory.create( username=self.OTHER_USERNAME, email=self.OTHER_EMAIL, password=self.PASSWORD, ) self.client.login(username=self.USERNAME, password=self.PASSWORD)
def test_course_catalog_access_num_queries(self, user_attr_name, action, course_attr_name): course = getattr(self, course_attr_name) # get a fresh user object that won't have any cached role information if user_attr_name == 'user_anonymous': user = AnonymousUserFactory() else: user = getattr(self, user_attr_name) user = User.objects.get(id=user.id) if (user_attr_name == 'user_staff' and action == 'see_exists' and course_attr_name in ['course_default', 'course_not_started']): # checks staff role num_queries = 1 elif user_attr_name == 'user_normal' and action == 'see_exists' and course_attr_name != 'course_started': # checks staff role and enrollment data num_queries = 2 else: num_queries = 0 course_overview = CourseOverview.get_from_id(course.id) with self.assertNumQueries(num_queries, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): bool(access.has_access(user, action, course_overview, course_key=course.id))
def handle(self, *args, **options): course_id = options.get('course') org = options.get('org') from_mode = options.get('from_mode') to_mode = options.get('to_mode') commit = options.get('commit') if (not course_id and not org) or (course_id and org): raise CommandError('You must provide either a course ID or an org, but not both.') if from_mode is None or to_mode is None: raise CommandError('Both `from` and `to` course modes must be given.') course_keys = [] if course_id: try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: raise CommandError('Course ID {} is invalid.'.format(course_id)) if modulestore().get_course(course_key) is None: raise CommandError('The given course {} does not exist.'.format(course_id)) course_keys.append(course_key) else: course_keys = [course.id for course in CourseOverview.get_all_courses(orgs=[org])] if not course_keys: raise CommandError('No courses exist for the org "{}".'.format(org)) for course_key in course_keys: self.move_users_for_course(course_key, from_mode, to_mode, commit) if not commit: logger.info('Dry run, changes have not been saved. Run again with "commit" argument to save changes')
def get_visible_courses(): """ Return the set of CourseDescriptors that should be visible in this branded instance """ filtered_by_org = microsite.get_value('course_org_filter') courses = CourseOverview.get_all_courses(org=filtered_by_org) courses = sorted(courses, key=lambda course: course.number) # See if we have filtered course listings in this domain filtered_visible_ids = None # this is legacy format which is outside of the microsite feature -- also handle dev case, which should not filter subdomain = microsite.get_value('subdomain', 'default') if hasattr(settings, 'COURSE_LISTINGS') and subdomain in settings.COURSE_LISTINGS and not settings.DEBUG: filtered_visible_ids = frozenset( [SlashSeparatedCourseKey.from_deprecated_string(c) for c in settings.COURSE_LISTINGS[subdomain]] ) if filtered_by_org: return [course for course in courses if course.location.org == filtered_by_org] if filtered_visible_ids: return [course for course in courses if course.id in filtered_visible_ids] else: # Let's filter out any courses in an "org" that has been declared to be # in a Microsite org_filter_out_set = microsite.get_all_orgs() return [course for course in courses if course.location.org not in org_filter_out_set]
def test_get_course_details_course_dates(self, start_datetime, end_datetime, expected_start, expected_end): course = CourseFactory.create(start=start_datetime, end=end_datetime) # Load a CourseOverview. This initial load should result in a cache # miss; the modulestore is queried and course metadata is cached. __ = CourseOverview.get_from_id(course.id) self.assert_enrollment_status(course_id=unicode(course.id)) # Check course details url = reverse('courseenrollmentdetails', kwargs={"course_id": unicode(course.id)}) resp = self.client.get(url) self.assertEqual(resp.status_code, status.HTTP_200_OK) data = json.loads(resp.content) self.assertEqual(data['course_start'], expected_start) self.assertEqual(data['course_end'], expected_end) # Check enrollment course details url = reverse('courseenrollment', kwargs={"course_id": unicode(course.id)}) resp = self.client.get(url) self.assertEqual(resp.status_code, status.HTTP_200_OK) data = json.loads(resp.content) self.assertEqual(data['course_details']['course_start'], expected_start) self.assertEqual(data['course_details']['course_end'], expected_end) # Check enrollment list course details resp = self.client.get(reverse('courseenrollments')) self.assertEqual(resp.status_code, status.HTTP_200_OK) data = json.loads(resp.content) self.assertEqual(data[0]['course_details']['course_start'], expected_start) self.assertEqual(data[0]['course_details']['course_end'], expected_end)
def supplement_program_data(program_data, user): """Supplement program course codes with CourseOverview and CourseEnrollment data. Arguments: program_data (dict): Representation of a program. user (User): The user whose enrollments to inspect. """ for course_code in program_data['course_codes']: for run_mode in course_code['run_modes']: course_key = CourseKey.from_string(run_mode['course_key']) course_overview = CourseOverview.get_from_id(course_key) run_mode['course_url'] = reverse('course_root', args=[course_key]) run_mode['course_image_url'] = course_overview.course_image_url human_friendly_format = '%x' start_date = course_overview.start or DEFAULT_START_DATE end_date = course_overview.end or datetime.datetime.max.replace(tzinfo=pytz.UTC) run_mode['start_date'] = start_date.strftime(human_friendly_format) run_mode['end_date'] = end_date.strftime(human_friendly_format) run_mode['is_enrolled'] = CourseEnrollment.is_enrolled(user, course_key) enrollment_start = course_overview.enrollment_start or datetime.datetime.min.replace(tzinfo=pytz.UTC) enrollment_end = course_overview.enrollment_end or datetime.datetime.max.replace(tzinfo=pytz.UTC) is_enrollment_open = enrollment_start <= timezone.now() < enrollment_end run_mode['is_enrollment_open'] = is_enrollment_open # TODO: Currently unavailable on LMS. run_mode['marketing_url'] = '' return program_data
def test_student_has_access(self): """ Tests course student have right access to content w/o preview. """ course_key = self.course.id chapter = ItemFactory.create(category="chapter", parent_location=self.course.location) overview = CourseOverview.get_from_id(course_key) # Enroll student to the course CourseEnrollmentFactory(user=self.student, course_id=self.course.id) modules = [ self.course, overview, chapter, ] with patch('lms.djangoapps.courseware.access.in_preview_mode' ) as mock_preview: mock_preview.return_value = False for obj in modules: self.assertTrue( bool( access.has_access(self.student, 'load', obj, course_key=self.course.id))) with patch('lms.djangoapps.courseware.access.in_preview_mode' ) as mock_preview: mock_preview.return_value = True for obj in modules: self.assertFalse( bool( access.has_access(self.student, 'load', obj, course_key=self.course.id)))
def test_expired_course(self): """ Ensure that a user accessing an expired course sees a redirect to the student dashboard, not a 404. """ CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime( 2010, 1, 1)) course = CourseFactory.create(start=THREE_YEARS_AGO) url = course_home_url(course) for mode in [CourseMode.AUDIT, CourseMode.VERIFIED]: CourseModeFactory.create(course_id=course.id, mode_slug=mode) # assert that an if an expired audit user tries to access the course they are redirected to the dashboard audit_user = UserFactory(password=self.TEST_PASSWORD) self.client.login(username=audit_user.username, password=self.TEST_PASSWORD) audit_enrollment = CourseEnrollment.enroll(audit_user, course.id, mode=CourseMode.AUDIT) ScheduleFactory(start_date=THREE_YEARS_AGO + timedelta(days=1), enrollment=audit_enrollment) response = self.client.get(url) expiration_date = strftime_localized( course.start + timedelta(weeks=4) + timedelta(days=1), u'%b %-d, %Y') expected_params = QueryDict(mutable=True) course_name = CourseOverview.get_from_id( course.id).display_name_with_default expected_params[ 'access_response_error'] = u'Access to {run} expired on {expiration_date}'.format( run=course_name, expiration_date=expiration_date) expected_url = '{url}?{params}'.format( url=reverse('dashboard'), params=expected_params.urlencode()) self.assertRedirects(response, expected_url)
def _get_courses_with_access_type(self, user, access_type): # Check the application cache and update if not present. The application # cache is useful since there are calls to different endpoints in close # succession, for example the id_token and user_info endpoints. key = '-'.join([str(self.__class__), str(user.id), access_type]) course_ids = cache.get(key) if not course_ids: course_keys = CourseOverview.get_all_course_keys() # Global staff have access to all courses. Filter courses for non-global staff. if not GlobalStaff().has_user(user): course_keys = [ course_key for course_key in course_keys if has_access(user, access_type, course_key) ] course_ids = [unicode(course_key) for course_key in course_keys] cache.set(key, course_ids, self.COURSE_CACHE_TIMEOUT) return course_ids
def get_first_purchase_offer_banner_fragment_from_key(user, course_key): """ Like `get_first_purchase_offer_banner_fragment`, but using a CourseKey instead of a CourseOverview and using request-level caching. Either returns WebFragment to inject XBlock content into, or None if we shouldn't show a first purchase offer message for this user. """ request_cache = RequestCache( 'get_first_purchase_offer_banner_fragment_from_key') cache_key = f'html:{user.id},{course_key}' cache_response = request_cache.get_cached_response(cache_key) if cache_response.is_found: cached_html = cache_response.value if cached_html is None: return None return Fragment(cached_html) course = CourseOverview.get_from_id(course_key) offer_html = generate_offer_html(user, course) request_cache.set(cache_key, offer_html) return Fragment(offer_html)
def certificate_info_for_user(user, course_id, grade, user_is_allowlisted, user_certificate): """ Returns the certificate info for a user for grade report. """ from common.djangoapps.student.models import CourseEnrollment certificate_is_delivered = 'N' certificate_type = 'N/A' status = certificate_status(user_certificate) certificate_generated = status['status'] == CertificateStatuses.downloadable can_have_certificate = CourseOverview.get_from_id(course_id).may_certify() enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_id) mode_is_verified = enrollment_mode in CourseMode.VERIFIED_MODES user_is_verified = grade is not None and mode_is_verified eligible_for_certificate = 'Y' if (user_is_allowlisted or user_is_verified or certificate_generated) \ else 'N' if certificate_generated and can_have_certificate: certificate_is_delivered = 'Y' certificate_type = status['mode'] return [eligible_for_certificate, certificate_is_delivered, certificate_type]
def __init__(self, user, course, expiration_date): error_code = "audit_expired" developer_message = u"User {} had access to {} until {}".format( user, course, expiration_date) language = get_language() expiration_date = strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR) user_message = _(u"Access expired on {expiration_date}").format( expiration_date=expiration_date) try: course_name = CourseOverview.get_from_id( course.id).display_name_with_default additional_context_user_message = _( u"Access to {course_name} expired on {expiration_date}" ).format(course_name=course_name, expiration_date=expiration_date) except CourseOverview.DoesNotExist: additional_context_user_message = _( u"Access to the course you were looking" u" for expired on {expiration_date}").format( expiration_date=expiration_date) super(AuditExpiredError, self).__init__(error_code, developer_message, user_message, additional_context_user_message)
def handle(self, *args, **options): courses = modulestore().get_courses() # Find the largest auto-generated course, and pick the next sequence id to generate the next # course with. max_org_sequence_id = max(int(course.org[4:]) for course in courses if course.org.startswith('org.')) XMODULE_FACTORY_LOCK.enable() CourseFactory.reset_sequence(max_org_sequence_id + 1, force=True) course = CourseFactory.create( start=datetime.datetime.today() - datetime.timedelta(days=30), end=datetime.datetime.today() + datetime.timedelta(days=30), number=factory.Sequence('schedules_test_course_{}'.format), display_name=factory.Sequence(u'Schedules Test Course {}'.format), ) XMODULE_FACTORY_LOCK.disable() course_overview = CourseOverview.load_from_module_store(course.id) ThreeDayNudgeSchedule.create(enrollment__course=course_overview) TenDayNudgeSchedule.create(enrollment__course=course_overview) UpgradeReminderSchedule.create(enrollment__course=course_overview) ContentHighlightSchedule.create(enrollment__course=course_overview) ScheduleConfigFactory.create(site=Site.objects.get(name='example.com'))
def test_certificate_visibility_with_no_cert_config(self): """ Verify that certificates are not displayed until there is an active certificate configuration. """ # Add new certificate cert = self._create_certificate(enrollment_mode=CourseMode.VERIFIED) cert.download_url = '' cert.save() response = self.client.get(f'/u/{self.user.username}') self.assertNotContains( response, f'card certificate-card mode-{CourseMode.VERIFIED}' ) course_overview = CourseOverview.get_from_id(self.course.id) course_overview.has_any_active_web_certificate = True course_overview.save() response = self.client.get(f'/u/{self.user.username}') self.assertContains( response, f'card certificate-card mode-{CourseMode.VERIFIED}' )
def course_expiration_wrapper(user, block, view, frag, context): # pylint: disable=W0613 """ An XBlock wrapper that prepends a message to the beginning of a vertical if a user's course is about to expire. """ if block.category != "vertical": return frag course = CourseOverview.get_from_id(block.course_id) course_expiration_fragment = generate_course_expired_fragment(user, course) if not course_expiration_fragment: return frag # Course content must be escaped to render correctly due to the way the # way the XBlock rendering works. Transforming the safe markup to unicode # escapes correctly. course_expiration_fragment.content = unicode(course_expiration_fragment.content) course_expiration_fragment.add_content(frag.content) course_expiration_fragment.add_fragment_resources(frag) return course_expiration_fragment
def _get_ordered_certificates_for_user(self, request, username): """ Returns a user's certificates sorted by course name. """ course_certificates = certificate_api.get_certificates_for_user( username) passing_certificates = [] for course_certificate in course_certificates: if course_certificate.get('is_passing', False): course_key = course_certificate['course_key'] try: course_overview = CourseOverview.get_from_id(course_key) course_certificate['course'] = course_overview if certificates_viewable_for_course(course_overview): passing_certificates.append(course_certificate) except CourseOverview.DoesNotExist: # This is unlikely to fail as the course should exist. # Ideally the cert should have all the information that # it needs. This might be solved by the Credentials API. pass passing_certificates.sort(key=lambda certificate: certificate['course'] .display_name_with_default) return passing_certificates
def test_course_overview_access(self, user_attr_name, action, course_attr_name): """ Check that a user's access to a course is equal to the user's access to the corresponding course overview. Instead of taking a user and course directly as arguments, we have to take their attribute names, as ddt doesn't allow us to reference self. Arguments: user_attr_name (str): the name of the attribute on self that is the User to test with. action (str): action to test with. course_attr_name (str): the name of the attribute on self that is the CourseDescriptor to test with. """ user = getattr(self, user_attr_name) course = getattr(self, course_attr_name) course_overview = CourseOverview.get_from_id(course.id) self.assertEqual( bool(access.has_access(user, action, course, course_key=course.id)), bool(access.has_access(user, action, course_overview, course_key=course.id)) )
def post(self, request, course_key_string: str, **_kwargs) -> Response: """ Handle HTTP/POST requests """ course_key = validate_course_key(course_key_string) configuration = DiscussionsConfiguration.get(course_key) course = CourseOverview.get_from_id(course_key) serializer = DiscussionsConfigurationSerializer( configuration, context={ 'user_id': request.user.id, }, data=request.data, partial=True, ) if serializer.is_valid(raise_exception=True): if serializer.validated_data[ 'provider_type'] != configuration.provider_type: check_course_permissions(course, request.user, 'change_provider') serializer.save() return Response(serializer.data)
def offer_banner_wrapper(user, block, view, frag, context): # pylint: disable=W0613 """ A wrapper that prepends the First Purchase Discount banner if the user hasn't upgraded yet. """ if block.category != "vertical": return frag course = CourseOverview.get_from_id(block.course_id) offer_banner_fragment = get_first_purchase_offer_banner_fragment(user, course) if not offer_banner_fragment: return frag # Course content must be escaped to render correctly due to the way the # way the XBlock rendering works. Transforming the safe markup to unicode # escapes correctly. offer_banner_fragment.content = six.text_type(offer_banner_fragment.content) offer_banner_fragment.add_content(frag.content) offer_banner_fragment.add_fragment_resources(frag) return offer_banner_fragment
def test_masquerade_in_holdback(self, mock_get_course_run_details): mock_get_course_run_details.return_value = {'weeks_to_complete': 12} audit_student = UserFactory(username='******') CourseEnrollmentFactory.create( user=audit_student, course_id=self.course.id, mode='audit' ) ExperimentData.objects.create( user=audit_student, experiment_id=EXPERIMENT_ID, key=EXPERIMENT_DATA_HOLDBACK_KEY, value='True' ) CourseDurationLimitConfig.objects.create( enabled=True, course=CourseOverview.get_from_id(self.course.id), enabled_as_of=self.course.start, ) instructor = UserFactory.create(username='******') CourseEnrollmentFactory.create( user=instructor, course_id=self.course.id, mode='audit' ) CourseInstructorRole(self.course.id).add_users(instructor) self.client.login(username=instructor.username, password='******') self.update_masquerade(username='******') course_home_url = reverse('openedx.course_experience.course_home', args=[six.text_type(self.course.id)]) response = self.client.get(course_home_url, follow=True) self.assertEqual(response.status_code, 200) self.assertItemsEqual(response.redirect_chain, []) banner_text = 'You lose all access to this course, including your progress,' self.assertNotIn(banner_text, response.content)
def test_course_expiration_banner_with_unicode(self, mock_strftime_localized, mock_get_date_string): """ Ensure that switching to other languages that have unicode in their date representations will not cause the course home page to 404. """ fake_unicode_start_time = u"üñîçø∂é_ßtå®t_tîµé" mock_strftime_localized.return_value = fake_unicode_start_time date_string = u'<span class="localized-datetime" data-format="shortDate" \ data-datetime="{formatted_date}" data-language="{language}">{formatted_date_localized}</span>' mock_get_date_string.return_value = date_string config = CourseDurationLimitConfig( course=CourseOverview.get_from_id(self.course.id), enabled=True, enabled_as_of=datetime(2018, 1, 1) ) config.save() url = course_home_url(self.course) user = self.create_user_for_course(self.course, CourseUserType.UNENROLLED) CourseEnrollment.enroll(user, self.course.id) language = 'eo' DarkLangConfig( released_languages=language, changed_by=user, enabled=True ).save() response = self.client.get(url, HTTP_ACCEPT_LANGUAGE=language) self.assertEqual(response.status_code, 200) self.assertEqual(response['Content-Language'], language) # Check that if the string is incorrectly not marked as unicode we still get the error with mock.patch("openedx.features.course_duration_limits.access.get_date_string", return_value=date_string.encode('utf-8')): response = self.client.get(url, HTTP_ACCEPT_LANGUAGE=language) self.assertEqual(response.status_code, 500)
def supplement_program_data(program_data, user): """Supplement program course codes with CourseOverview and CourseEnrollment data. Arguments: program_data (dict): Representation of a program. user (User): The user whose enrollments to inspect. """ for course_code in program_data['course_codes']: for run_mode in course_code['run_modes']: course_key = CourseKey.from_string(run_mode['course_key']) course_overview = CourseOverview.get_from_id(course_key) run_mode['course_url'] = reverse('course_root', args=[course_key]) run_mode['course_image_url'] = course_overview.course_image_url human_friendly_format = '%x' start_date = course_overview.start or DEFAULT_START_DATE end_date = course_overview.end or datetime.datetime.max.replace( tzinfo=pytz.UTC) run_mode['start_date'] = start_date.strftime(human_friendly_format) run_mode['end_date'] = end_date.strftime(human_friendly_format) run_mode['is_enrolled'] = CourseEnrollment.is_enrolled( user, course_key) enrollment_start = course_overview.enrollment_start or datetime.datetime.min.replace( tzinfo=pytz.UTC) enrollment_end = course_overview.enrollment_end or datetime.datetime.max.replace( tzinfo=pytz.UTC) is_enrollment_open = enrollment_start <= timezone.now( ) < enrollment_end run_mode['is_enrollment_open'] = is_enrollment_open # TODO: Currently unavailable on LMS. run_mode['marketing_url'] = '' return program_data
def course_runs_with_state(self): """ Determine which course runs have been completed and failed by the user. A course run is considered completed for a user if they have a certificate in the correct state and the certificate is available. Returns: dict with a list of completed and failed runs """ course_run_certificates = certificate_api.get_certificates_for_user( self.user.username) completed_runs, failed_runs = [], [] for certificate in course_run_certificates: course_key = certificate['course_key'] course_data = { 'course_run_id': str(course_key), 'type': self._certificate_mode_translation(certificate['type']), } try: course_overview = CourseOverview.get_from_id(course_key) except CourseOverview.DoesNotExist: may_certify = True else: may_certify = certificate_api.certificates_viewable_for_course( course_overview) if (CertificateStatuses.is_passing_status(certificate['status']) and may_certify): completed_runs.append(course_data) else: failed_runs.append(course_data) return {'completed': completed_runs, 'failed': failed_runs}
def _get_certificates_for_user(self, username): """ Returns a user's viewable certificates sorted by course name. """ course_certificates = get_certificates_for_user(username) passing_certificates = {} for course_certificate in course_certificates: if course_certificate.get('is_passing', False): course_key = course_certificate['course_key'] passing_certificates[course_key] = course_certificate viewable_certificates = [] for course_key, course_overview in CourseOverview.get_from_ids( list(passing_certificates.keys())).items(): if not course_overview: # For deleted XML courses in which learners have a valid certificate. # i.e. MITx/7.00x/2013_Spring course_overview = self._get_pseudo_course_overview(course_key) if certificates_viewable_for_course(course_overview): course_certificate = passing_certificates[course_key] # add certificate into viewable certificate list only if it's a PDF certificate # or there is an active certificate configuration. if course_certificate[ 'is_pdf_certificate'] or course_overview.has_any_active_web_certificate: course_display_name = course_overview.display_name if not course_overview.pk: course_display_name = course_overview.display_name_with_default course_certificate[ 'course_display_name'] = course_display_name course_certificate[ 'course_organization'] = course_overview.display_org_with_default viewable_certificates.append(course_certificate) viewable_certificates.sort( key=lambda certificate: certificate['created']) return viewable_certificates
def get(self, request, course_key_string): """ Return the discount percent, if the user has appropriate permissions. """ course_key = CourseKey.from_string(course_key_string) course = CourseOverview.get_from_id(course_key) discount_applicable = can_receive_discount(user=request.user, course=course) discount_percent = discount_percentage(course) payload = {'discount_applicable': discount_applicable, 'discount_percent': discount_percent} # Record whether the last basket loaded for this course had a discount try: ExperimentData.objects.update_or_create( user=request.user, experiment_id=REV1008_EXPERIMENT_ID, key='discount_' + str(course), value=discount_applicable ) except Exception as e: # pylint: disable=broad-except log.exception(str(e)) return Response({ 'discount_applicable': discount_applicable, 'jwt': create_jwt_for_user(request.user, additional_claims=payload)})
def test_upgrade_deadline_with_schedule(self): """ The property should use either the CourseMode or related Schedule to determine the deadline. """ course = CourseFactory(self_paced=True) CourseModeFactory( course_id=course.id, mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30), ) course_overview = CourseOverview.load_from_module_store(course.id) CourseEnrollmentFactory( course_id=course.id, mode=CourseMode.AUDIT, course=course_overview, ) Schedule.objects.update( upgrade_deadline=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=5)) enrollment = CourseEnrollment.objects.first() # The schedule's upgrade deadline should be used if a schedule exists DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) assert enrollment.upgrade_deadline == enrollment.schedule.upgrade_deadline
def get(self, request, course_key_string, user_id): """ Return the discount percent, if the user has appropriate permissions. """ course_key = CourseKey.from_string(course_key_string) course = CourseOverview.get_from_id(course_key) user = User.objects.get(id=user_id) # Below code in try/except is temporarily replacing this call # discount_applicable = can_receive_discount(user=user, course=course) # Only show a discount on the order if the last basket loaded for this course had a discount # Do not check any of the discount requirements try: discount_applicable = ExperimentData.objects.get( user=user, experiment_id=REV1008_EXPERIMENT_ID, key='discount_' + str(course) ).value == 'True' except ExperimentData.DoesNotExist: discount_applicable = False discount_percent = discount_percentage(course) payload = {'discount_applicable': discount_applicable, 'discount_percent': discount_percent} return Response({ 'discount_applicable': discount_applicable, 'jwt': create_jwt_for_user(request.user, additional_claims=payload)})
def handle(self, *args, **options): course_id = options['course'] org = options['org'] from_mode = options['from_mode'] to_mode = options['to_mode'] commit = options['commit'] course_keys = [] if course_id: try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: raise CommandError( 'Course ID {} is invalid.'.format(course_id)) if modulestore().get_course(course_key) is None: raise CommandError( 'The given course {} does not exist.'.format(course_id)) course_keys.append(course_key) else: course_keys = [ course.id for course in CourseOverview.get_all_courses(orgs=[org]) ] if not course_keys: raise CommandError( 'No courses exist for the org "{}".'.format(org)) for course_key in course_keys: self.move_users_for_course(course_key, from_mode, to_mode, commit) if not commit: logger.info( 'Dry run, changes have not been saved. Run again with "commit" argument to save changes' )
def test_masquerade(self, masquerade_config, show_expiration_banner, mock_get_course_run_details): mock_get_course_run_details.return_value = {'weeks_to_complete': 12} audit_student = UserFactory(username='******') CourseEnrollmentFactory.create(user=audit_student, course_id=self.course.id, mode='audit') verified_student = UserFactory(username='******') CourseEnrollmentFactory.create(user=verified_student, course_id=self.course.id, mode='verified') CourseDurationLimitConfig.objects.create( enabled=True, course=CourseOverview.get_from_id(self.course.id), enabled_as_of=self.course.start, ) instructor = UserFactory.create(username='******') CourseEnrollmentFactory.create(user=instructor, course_id=self.course.id, mode='audit') CourseInstructorRole(self.course.id).add_users(instructor) self.client.login(username=instructor.username, password='******') self.update_masquerade(**masquerade_config) course_home_url = reverse('openedx.course_experience.course_home', args=[str(self.course.id)]) response = self.client.get(course_home_url, follow=True) assert response.status_code == 200 self.assertCountEqual(response.redirect_chain, []) banner_text = 'You lose all access to this course, including your progress,' if show_expiration_banner: self.assertContains(response, banner_text) else: self.assertNotContains(response, banner_text)
def has_html_certificates_enabled(course_key, course=None): """ Determine if a course has html certificates enabled. Arguments: course_key (CourseKey|str): A course key or a string representation of one. course (CourseDescriptor|CourseOverview): A course. """ # If the feature is disabled, then immediately return a False if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False): return False # If we don't have a course object, we'll need to assemble one if not course: # Initialize a course key if necessary if not isinstance(course_key, CourseKey): try: course_key = CourseKey.from_string(course_key) except InvalidKeyError: log.warning( ('Unable to parse course_key "%s"', course_key), exc_info=True ) return False # Pull the course data from the cache try: course = CourseOverview.get_from_id(course_key) except: # pylint: disable=bare-except log.warning( ('Unable to load CourseOverview object for course_key "%s"', unicode(course_key)), exc_info=True ) # Return the flag on the course object return course.cert_html_view_enabled if course else False
def test_all_courses_with_weeks_to_complete( self, weeks_to_complete, access_duration, self_paced, mock_get_course_run_details, ): """ Test that access_duration for a course is equal to the value of the weeks_to_complete field in discovery. If weeks_to_complete is None, access_duration will be the MIN_DURATION constant. """ if self_paced: self.course.self_paced = True mock_get_course_run_details.return_value = { 'weeks_to_complete': weeks_to_complete } enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) result = get_user_course_expiration_date( self.user, CourseOverview.get_from_id(self.course.id), ) self.assertEqual(result, enrollment.created + access_duration)
def _assert_supplemented(self, actual, **kwargs): """DRY helper used to verify that program data is extended correctly.""" course_overview = CourseOverview.get_from_id(self.course.id) # pylint: disable=no-member run_mode = dict( factories.RunMode( course_key=unicode(self.course.id), # pylint: disable=no-member course_url=reverse('course_root', args=[self.course.id]), # pylint: disable=no-member course_image_url=course_overview.course_image_url, start_date=self.course.start.strftime( self.human_friendly_format), end_date=self.course.end.strftime(self.human_friendly_format), is_enrolled=False, is_enrollment_open=True, marketing_url='', ), **kwargs) course_code = factories.CourseCode( display_name=self.course_code['display_name'], run_modes=[run_mode]) expected = copy.deepcopy(self.program) expected['course_codes'] = [course_code] self.assertEqual(actual, expected)
def get_days_until_expiration(self, entitlement): """ Returns an integer of number of days until the entitlement expires. Includes the logic for regaining an entitlement. """ now = datetime.now(tz=pytz.UTC) expiry_date = entitlement.created + self.expiration_period days_until_expiry = (expiry_date - now).days if not entitlement.enrollment_course_run: return days_until_expiry course_overview = CourseOverview.get_from_id(entitlement.enrollment_course_run.course_id) # Compute the days left for the regain days_since_course_start = (now - course_overview.start).days days_since_enrollment = (now - entitlement.enrollment_course_run.created).days # We want to return whichever days value is less since it is then the more recent one days_until_regain_ends = (self.regain_period.days - # pylint: disable=no-member min(days_since_course_start, days_since_enrollment)) # If the base days until expiration is less than the days until the regain period ends, use that instead if days_until_expiry < days_until_regain_ends: return days_until_expiry return days_until_regain_ends # pylint: disable=no-member
def search_certificates(request): """ Search for certificates for a particular user OR along with the given course. Supports search by either username or email address along with course id. First filter the records for the given username/email and then filter against the given course id (if given). Show the 'Regenerate' button if a record found in 'generatedcertificate' model otherwise it will show the Generate button. Arguments: request (HttpRequest): The request object. Returns: JsonResponse Example Usage: GET /certificates/[email protected] GET /certificates/[email protected]&course_id=xyz Response: 200 OK Content-Type: application/json [ { "username": "******", "course_key": "edX/DemoX/Demo_Course", "type": "verified", "status": "downloadable", "download_url": "http://www.example.com/cert.pdf", "grade": "0.98", "created": 2015-07-31T00:00:00Z, "modified": 2015-07-31T00:00:00Z } ] """ unbleached_filter = urllib.parse.unquote( urllib.parse.quote_plus(request.GET.get("user", ""))) user_filter = bleach.clean(unbleached_filter) if not user_filter: msg = _("user is not given.") return HttpResponseBadRequest(msg) try: user = User.objects.get(Q(email=user_filter) | Q(username=user_filter)) except User.DoesNotExist: return HttpResponseBadRequest( _("user '{user}' does not exist").format(user=user_filter)) certificates = get_certificates_for_user(user.username) for cert in certificates: cert["course_key"] = str(cert["course_key"]) cert["created"] = cert["created"].isoformat() cert["modified"] = cert["modified"].isoformat() cert["regenerate"] = not cert['is_pdf_certificate'] course_id = urllib.parse.quote_plus(request.GET.get("course_id", ""), safe=':/') if course_id: try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: return HttpResponseBadRequest( _("Course id '{course_id}' is not valid").format( course_id=course_id)) else: try: if CourseOverview.get_from_id(course_key): certificates = [ certificate for certificate in certificates if certificate['course_key'] == course_id ] if not certificates: return JsonResponse([{ 'username': user.username, 'course_key': course_id, 'regenerate': False }]) except CourseOverview.DoesNotExist: msg = _( "The course does not exist against the given key '{course_key}'" ).format(course_key=course_key) return HttpResponseBadRequest(msg) return JsonResponse(certificates)
def _course_from_key(course_key): return CourseOverview.get_from_id(_safe_course_key(course_key))
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) course_usage_key = modulestore().make_course_usage_key(course_key) if not course_home_mfe_outline_tab_is_active(course_key): raise Http404 # Enable NR tracing for this view based on course monitoring_utils.set_custom_attribute('course_id', course_key_string) monitoring_utils.set_custom_attribute('user_id', request.user.id) monitoring_utils.set_custom_attribute('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) masquerade_object, request.user = setup_masquerade( request, course_key, staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True, ) user_is_masquerading = is_masquerading( request.user, course_key, course_masquerade=masquerade_object) course_overview = CourseOverview.get_from_id(course_key) enrollment = CourseEnrollment.get_enrollment(request.user, course_key) allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled( course_key) allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] dates_tab_link = request.build_absolute_uri( reverse('dates', args=[course.id])) if course_home_mfe_dates_tab_is_active(course.id): dates_tab_link = get_learning_mfe_home_url(course_key=course.id, view_name='dates') # Set all of the defaults access_expiration = None course_blocks = None course_goals = {'goal_options': [], 'selected_goal': None} course_tools = CourseToolsPluginManager.get_enabled_course_tools( request, course_key) dates_widget = { 'course_date_blocks': [], 'dates_tab_link': dates_tab_link, 'user_timezone': user_timezone, } enroll_alert = { 'can_enroll': True, 'extra_text': None, } handouts_html = None offer_data = None resume_course = { 'has_visited_course': False, 'url': None, } welcome_message_html = None is_enrolled = enrollment and enrollment.is_active is_staff = bool(has_access(request.user, 'staff', course_key)) show_enrolled = is_enrolled or is_staff if show_enrolled: course_blocks = get_course_outline_block_tree( request, course_key_string, request.user) date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1) dates_widget['course_date_blocks'] = [ block for block in date_blocks if not isinstance(block, TodaysDate) ] handouts_html = get_course_info_section(request, request.user, course, 'handouts') welcome_message_html = get_current_update_for_user(request, course) offer_data = generate_offer_data(request.user, course_overview) access_expiration = get_access_expiration_data( request.user, course_overview) # Only show the set course goal message for enrolled, unverified # users in a course that allows for verified statuses. is_already_verified = CourseEnrollment.is_enrolled_as_verified( request.user, course_key) if not is_already_verified and has_course_goal_permission( request, course_key_string, {'is_enrolled': is_enrolled}): course_goals = { 'goal_options': valid_course_goals_ordered(include_unsure=True), 'selected_goal': None } selected_goal = get_course_goal(request.user, course_key) if selected_goal: course_goals['selected_goal'] = { 'key': selected_goal.goal_key, 'text': get_course_goal_text(selected_goal.goal_key), } try: resume_block = get_key_to_last_completed_block( request.user, course.id) resume_course['has_visited_course'] = True resume_path = reverse('jump_to', kwargs={ 'course_id': course_key_string, 'location': str(resume_block) }) resume_course['url'] = request.build_absolute_uri(resume_path) except UnavailableCompletionData: start_block = get_start_block(course_blocks) resume_course['url'] = start_block['lms_web_url'] elif allow_public_outline or allow_public or user_is_masquerading: course_blocks = get_course_outline_block_tree( request, course_key_string, None) if allow_public or user_is_masquerading: handouts_html = get_course_info_section( request, request.user, course, 'handouts') if not show_enrolled: if CourseMode.is_masters_only(course_key): enroll_alert['can_enroll'] = False enroll_alert['extra_text'] = _( 'Please contact your degree administrator or ' 'edX Support if you have questions.') elif course.invitation_only: enroll_alert['can_enroll'] = False data = { 'access_expiration': access_expiration, 'course_blocks': course_blocks, 'course_goals': course_goals, 'course_tools': course_tools, 'dates_widget': dates_widget, 'enroll_alert': enroll_alert, 'handouts_html': handouts_html, 'has_ended': course.has_ended(), 'offer': offer_data, 'resume_course': resume_course, 'welcome_message_html': welcome_message_html, } context = self.get_serializer_context() context['course_overview'] = course_overview context['enable_links'] = show_enrolled or allow_public context['enrollment'] = enrollment serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)