Пример #1
0
 def setUp(self):
     """
     Create a test course.
     """
     super(AdminCourseModeFormTest, self).setUp()
     self.course = CourseFactory.create()
     CourseOverview.load_from_module_store(self.course.id)
Пример #2
0
    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)
Пример #6
0
    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'
     )
Пример #8
0
 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'))
Пример #10
0
 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)
Пример #11
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)
Пример #12
0
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]
Пример #13
0
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)
Пример #14
0
    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
Пример #15
0
    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)
Пример #18
0
    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)
Пример #19
0
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
Пример #20
0
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
Пример #21
0
    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)
Пример #23
0
    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)
Пример #25
0
    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)
Пример #26
0
    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')
Пример #28
0
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]
Пример #29
0
    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)
Пример #30
0
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
Пример #31
0
    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)))
Пример #32
0
    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)
Пример #33
0
    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
Пример #34
0
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)
Пример #35
0
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]
Пример #36
0
 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)
Пример #37
0
    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'))
Пример #38
0
    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}'
        )
Пример #39
0
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
Пример #40
0
 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
Пример #41
0
    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))
        )
Пример #42
0
    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)
Пример #43
0
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
Пример #44
0
    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)
Пример #45
0
    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)
Пример #46
0
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
Пример #47
0
    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}
Пример #48
0
    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
Пример #49
0
    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)})
Пример #50
0
    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
Пример #51
0
    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'
            )
Пример #53
0
    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)
Пример #54
0
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
Пример #55
0
    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)
Пример #56
0
    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)
Пример #57
0
    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
Пример #58
0
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)
Пример #59
0
def _course_from_key(course_key):
    return CourseOverview.get_from_id(_safe_course_key(course_key))
Пример #60
0
    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)