Beispiel #1
0
    def access_denied_message(self, block_key, user, user_group, allowed_groups):
        course_key = block_key.course_key
        modes = CourseMode.modes_for_course_dict(course_key)
        verified_mode = modes.get(CourseMode.VERIFIED)
        if (verified_mode is None or user_group == FULL_ACCESS or
                user_group in allowed_groups):
            return None

        request = crum.get_current_request()
        if request and is_request_from_mobile_app(request):
            return _("Graded assessments are available to Verified Track learners.")
        else:
            return _("Graded assessments are available to Verified Track learners. Upgrade to Unlock.")
def unlink_program_enrollment(program_enrollment):
    """
    Unlinks CourseEnrollments from the ProgramEnrollment by doing the following for
    each ProgramCourseEnrollment associated with the Program Enrollment.
        1. unenrolling the corresponding user from the course
        2. moving the user into the audit track, if the track exists
        3. removing the link between the ProgramCourseEnrollment and the CourseEnrollment

    Arguments:
        program_enrollment: the ProgramEnrollment object
    """
    program_course_enrollments = program_enrollment.program_course_enrollments.all(
    )

    for pce in program_course_enrollments:
        course_key = pce.course_enrollment.course.id
        modes = CourseMode.modes_for_course_dict(course_key)

        update_enrollment_kwargs = {
            'is_active': False,
            'skip_refund': True,
        }

        if CourseMode.contains_audit_mode(modes):
            # if the course contains an audit mode, move the
            # learner's enrollment into the audit mode
            update_enrollment_kwargs['mode'] = 'audit'

        # deactive the learner's course enrollment and move them into the
        # audit track, if it exists
        pce.course_enrollment.update_enrollment(**update_enrollment_kwargs)

        # sever ties to the user from the ProgramCourseEnrollment
        pce.course_enrollment = None
        pce.save()

    program_enrollment.user = None
    program_enrollment.save()
Beispiel #3
0
    def test_default_mode_creation(self):
        # Hit the mode creation endpoint with no querystring params, to create an honor mode
        url = reverse('create_mode', args=[str(self.course.id)])
        response = self.client.get(url)

        assert response.status_code == 200

        expected_mode = [
            Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None,
                 None, None)
        ]
        course_mode = CourseMode.modes_for_course(self.course.id)

        assert course_mode == expected_mode
Beispiel #4
0
    def test_update_professional_expiration(self, mode_slug,
                                            expiration_datetime_name):
        """ Verify that pushing a mode with a professional certificate and an expiration datetime
        will be rejected (this is not allowed). """
        expiration_datetime = self.DATES[expiration_datetime_name]
        mode = self._serialize_course_mode(
            CourseMode(mode_slug=mode_slug,
                       min_price=500,
                       currency='USD',
                       sku='ABC123',
                       bulk_sku='BULK-ABC123',
                       expiration_datetime=expiration_datetime))
        course_id = str(self.course.id)
        payload = {'id': course_id, 'modes': [mode]}
        path = reverse('commerce_api:v1:courses:retrieve_update',
                       args=[course_id])

        expected_status = 400 if CourseMode.is_professional_slug(
            mode_slug) and expiration_datetime is not None else 200
        response = self.client.put(path,
                                   json.dumps(payload),
                                   content_type=JSON_CONTENT_TYPE)
        assert response.status_code == expected_status
Beispiel #5
0
    def test_default_mode_creation(self):
        # Hit the mode creation endpoint with no querystring params, to create an honor mode
        url = reverse('create_mode', args=[six.text_type(self.course.id)])
        response = self.client.get(url)

        self.assertEqual(response.status_code, 200)

        expected_mode = [
            Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None, None,
                 None, None)
        ]
        course_mode = CourseMode.modes_for_course(self.course.id)

        self.assertEqual(course_mode, expected_mode)
Beispiel #6
0
    def get_group_for_user(cls, course_key, user, user_partition, **kwargs):  # pylint: disable=unused-argument
        """
        Returns the Group from the specified user partition to which the user
        is assigned, via enrollment mode. If a user is in a Credit mode, the Verified or
        Professional mode for the course is returned instead.

        If a course is using the Verified Track Cohorting pilot feature, this method
        returns None regardless of the user's enrollment mode.
        """
        if is_course_using_cohort_instead(course_key):
            return None

        # First, check if we have to deal with masquerading.
        # If the current user is masquerading as a specific student, use the
        # same logic as normal to return that student's group. If the current
        # user is masquerading as a generic student in a specific group, then
        # return that group.
        if get_course_masquerade(user, course_key) and not is_masquerading_as_specific_student(user, course_key):
            return get_masquerading_user_group(course_key, user, user_partition)

        mode_slug, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_key)
        if mode_slug and is_active:
            course_mode = CourseMode.mode_for_course(
                course_key,
                mode_slug,
                modes=CourseMode.modes_for_course(course_key, include_expired=True, only_selectable=False),
            )
            if course_mode and CourseMode.is_credit_mode(course_mode):
                # We want the verified track even if the upgrade deadline has passed, since we
                # are determining what content to show the user, not whether the user can enroll
                # in the verified track.
                course_mode = CourseMode.verified_mode_for_course(course_key, include_expired=True)
            if not course_mode:
                course_mode = CourseMode.DEFAULT_MODE
            return Group(ENROLLMENT_GROUP_IDS[course_mode.slug]["id"], six.text_type(course_mode.name))
        else:
            return None
Beispiel #7
0
 def test_course_without_sku_honor(self):
     """
     If the course does not have an SKU and has an honor mode, the user
     should be enrolled as honor. This ensures backwards
     compatibility with courses existing before the removal of
     honor certificates.
     """
     # Remove all existing course modes
     CourseMode.objects.filter(course_id=self.course.id).delete()
     # Ensure that honor mode exists
     CourseMode(mode_slug=CourseMode.HONOR,
                mode_display_name="Honor Cert",
                course_id=self.course.id).save()
     # We should be enrolled in honor mode
     self._test_course_without_sku(enrollment_mode=CourseMode.HONOR)
Beispiel #8
0
    def test_min_course_price_for_currency(self):
        """
        Get the min course price for a course according to currency
        """
        # no modes, should get 0
        assert 0 == CourseMode.min_course_price_for_currency(
            self.course_key, 'usd')

        # create some modes
        mode1 = Mode('honor', 'Honor Code Certificate', 10, '', 'usd', None,
                     None, None, None)
        mode2 = Mode('verified', 'Verified Certificate', 20, '', 'usd', None,
                     None, None, None)
        mode3 = Mode('honor', 'Honor Code Certificate', 80, '', 'cny', None,
                     None, None, None)
        set_modes = [mode1, mode2, mode3]
        for mode in set_modes:
            self.create_mode(mode.slug, mode.name, mode.min_price,
                             mode.suggested_prices, mode.currency)

        assert 10 == CourseMode.min_course_price_for_currency(
            self.course_key, 'usd')
        assert 80 == CourseMode.min_course_price_for_currency(
            self.course_key, 'cny')
Beispiel #9
0
 def test_invalid_mode_expiration(self, mode_slug, exp_dt_name):
     exp_dt = self.DATES[exp_dt_name]
     is_error_expected = CourseMode.is_professional_slug(
         mode_slug) and exp_dt is not None
     try:
         self.create_mode(mode_slug=mode_slug,
                          mode_name=mode_slug.title(),
                          expiration_datetime=exp_dt,
                          min_price=10)
         assert not is_error_expected, 'Expected a ValidationError to be thrown.'
     except ValidationError as exc:
         assert is_error_expected, 'Did not expect a ValidationError to be thrown.'
         assert exc.messages == [
             'Professional education modes are not allowed to have expiration_datetime set.'
         ]
def _section_send_email(course, access):
    """ Provide data for the corresponding bulk email section """
    course_key = course.id

    # Monkey-patch applicable_aside_types to return no asides for the duration of this render
    with patch.object(course.runtime, 'applicable_aside_types', null_applicable_aside_types):
        # This HtmlBlock is only being used to generate a nice text editor.
        html_module = HtmlBlock(
            course.system,
            DictFieldData({'data': ''}),
            ScopeIds(None, None, None, course_key.make_usage_key('html', 'fake'))
        )
        fragment = course.system.render(html_module, 'studio_view')
    fragment = wrap_xblock(
        'LmsRuntime', html_module, 'studio_view', fragment, None,
        extra_data={"course-id": str(course_key)},
        usage_id_serializer=lambda usage_id: quote_slashes(str(usage_id)),
        # Generate a new request_token here at random, because this module isn't connected to any other
        # xblock rendering.
        request_token=uuid.uuid1().hex
    )
    cohorts = []
    if is_course_cohorted(course_key):
        cohorts = get_course_cohorts(course)
    course_modes = []
    if not VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(course_key):
        course_modes = CourseMode.modes_for_course(course_key, include_expired=True, only_selectable=False)
    email_editor = fragment.content
    section_data = {
        'section_key': 'send_email',
        'section_display_name': _('Email'),
        'access': access,
        'send_email': reverse('send_email', kwargs={'course_id': str(course_key)}),
        'editor': email_editor,
        'cohorts': cohorts,
        'course_modes': course_modes,
        'default_cohort_name': DEFAULT_COHORT_NAME,
        'list_instructor_tasks_url': reverse(
            'list_instructor_tasks', kwargs={'course_id': str(course_key)}
        ),
        'email_background_tasks_url': reverse(
            'list_background_email_tasks', kwargs={'course_id': str(course_key)}
        ),
        'email_content_history_url': reverse(
            'list_email_content', kwargs={'course_id': str(course_key)}
        ),
    }
    return section_data
Beispiel #11
0
    def _get_update_response_and_expected_data(self, mode_expiration, verification_deadline):
        """ Returns expected data and response for course update. """
        expected_course_mode = CourseMode(
            mode_slug=u'verified',
            min_price=200,
            currency=u'USD',
            sku=u'ABC123',
            bulk_sku=u'BULK-ABC123',
            expiration_datetime=mode_expiration
        )
        expected = self._serialize_course(self.course, [expected_course_mode], verification_deadline)

        # Sanity check: The API should return HTTP status 200 for updates
        response = self.client.put(self.path, json.dumps(expected), content_type=JSON_CONTENT_TYPE)

        return response, expected
Beispiel #12
0
def is_course_run_entitlement_fulfillable(
        course_run_key,
        entitlement,
        compare_date=timezone.now(),
):
    """
    Checks that the current run meets the following criteria for an entitlement

    1) A User can enroll in or is currently enrolled
    2) A User can upgrade to the entitlement mode

    Arguments:
        course_run_key (CourseKey): The id of the Course run that is being checked.
        entitlement: The Entitlement that we are checking against.
        compare_date: The date and time that we are comparing against.  Defaults to timezone.now()

    Returns:
        bool: True if the Course Run is fullfillable for the CourseEntitlement.
    """
    try:
        course_overview = CourseOverview.get_from_id(course_run_key)
    except CourseOverview.DoesNotExist:
        log.error((
            u'There is no CourseOverview entry available for {course_run_id}, '
            u'course run cannot be applied to entitlement').format(
                course_run_id=str(course_run_key)))
        return False

    # Verify that the course run can currently be enrolled
    enrollment_start = course_overview.enrollment_start
    enrollment_end = course_overview.enrollment_end
    can_enroll = ((not enrollment_start or enrollment_start < compare_date)
                  and (not enrollment_end or enrollment_end > compare_date))

    # Is the user already enrolled in the Course Run
    is_enrolled = CourseEnrollment.is_enrolled(entitlement.user,
                                               course_run_key)

    # Ensure the course run is upgradeable and the mode matches the entitlement's mode
    unexpired_paid_modes = [
        mode.slug for mode in CourseMode.paid_modes_for_course(course_run_key)
    ]
    can_upgrade = unexpired_paid_modes and entitlement.mode in unexpired_paid_modes

    return course_overview.start and can_upgrade and (is_enrolled
                                                      or can_enroll)
    def get_course_modes(course_key):
        """
        Returns a list of all modes including expired modes for a given course id

        Arguments:
            course_id (CourseKey): Search for course modes for this course.

        Returns:
            list of `Mode`

        """
        course_modes = CourseMode.modes_for_course(
            course_key,
            include_expired=True,
            only_selectable=False,
        )
        return [ModeSerializer(mode).data for mode in course_modes]
Beispiel #14
0
    def _attach_course_run_upgrade_url(self, run_mode):
        required_mode_slug = run_mode['type']
        enrolled_mode_slug, _ = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_run_key)
        is_mode_mismatch = required_mode_slug != enrolled_mode_slug
        is_upgrade_required = is_mode_mismatch and CourseEnrollment.is_enrolled(self.user, self.course_run_key)

        if is_upgrade_required:
            # Requires that the ecommerce service be in use.
            required_mode = CourseMode.mode_for_course(self.course_run_key, required_mode_slug)
            ecommerce = EcommerceService()
            sku = getattr(required_mode, 'sku', None)
            if ecommerce.is_enabled(self.user) and sku:
                run_mode['upgrade_url'] = ecommerce.get_checkout_page_url(required_mode.sku)
            else:
                run_mode['upgrade_url'] = None
        else:
            run_mode['upgrade_url'] = None
Beispiel #15
0
    def update(self, attrs):
        """ Update the model with external data (usually passed via API call). """
        # There are possible downstream effects of settings self.verification_deadline to null,
        # so don't assign it a value here unless it is specifically included in attrs.
        if 'verification_deadline' in attrs:
            self.verification_deadline = attrs.get('verification_deadline')

        existing_modes = {mode.mode_slug: mode for mode in self.modes}
        merged_modes = set()
        merged_mode_keys = set()

        for posted_mode in attrs.get('modes', []):
            merged_mode = existing_modes.get(posted_mode.mode_slug,
                                             CourseMode())

            merged_mode.course_id = self.id
            merged_mode.mode_slug = posted_mode.mode_slug
            merged_mode.mode_display_name = posted_mode.mode_slug
            merged_mode.min_price = posted_mode.min_price
            merged_mode.currency = posted_mode.currency
            merged_mode.sku = posted_mode.sku
            merged_mode.bulk_sku = posted_mode.bulk_sku
            merged_mode.expiration_datetime = posted_mode.expiration_datetime
            merged_mode.save()

            merged_modes.add(merged_mode)
            merged_mode_keys.add(merged_mode.mode_slug)

        # Masters degrees are not sold through the eCommerce site.
        # So, Masters course modes are not included in PUT calls to this API,
        # and their omission which would normally cause them to be deleted.
        # We don't want that to happen, but for the time being,
        # we cannot include in Masters modes in the PUT calls from eCommerce.
        # So, here's hack to handle Masters course modes, along with any other
        # modes that end up in that boat.
        MODES_TO_NOT_DELETE = {
            CourseMode.MASTERS,
        }

        modes_to_delete = set(existing_modes.keys()) - merged_mode_keys
        modes_to_delete -= MODES_TO_NOT_DELETE
        self._deleted_modes = [
            existing_modes[mode] for mode in modes_to_delete
        ]
        self.modes = list(merged_modes)
Beispiel #16
0
    def groups(self):
        """
        Return the groups (based on CourseModes) for the course associated with this
        EnrollmentTrackUserPartition instance. Note that only groups based on selectable
        CourseModes are returned (which means that Credit will never be returned).

        If a course is using the Verified Track Cohorting pilot feature, this method
        returns an empty array regardless of registered CourseModes.
        """
        course_key = CourseKey.from_string(self.parameters["course_id"])

        if is_course_using_cohort_instead(course_key):
            return []

        return [
            Group(ENROLLMENT_GROUP_IDS[mode.slug]["id"], six.text_type(mode.name))
            for mode in CourseMode.modes_for_course(course_key, include_expired=True)
        ]
Beispiel #17
0
def serialize_upgrade_info(user, course_overview, enrollment):
    """
    Return verified mode upgrade information, or None.

    This is used in a few API views to provide consistent upgrade info to frontends.
    """
    if not can_show_verified_upgrade(user, enrollment):
        return None

    mode = CourseMode.verified_mode_for_course(course=course_overview)
    return {
        'access_expiration_date': get_user_course_expiration_date(user, course_overview),
        'currency': mode.currency.upper(),
        'currency_symbol': get_currency_symbol(mode.currency.upper()),
        'price': mode.min_price,
        'sku': mode.sku,
        'upgrade_url': verified_upgrade_deadline_link(user, course_overview),
    }
Beispiel #18
0
def certificate_status(generated_certificate):
    """
    This returns a dictionary with a key for status, and other information.

    If the status is "downloadable", the dictionary also contains
    "download_url".

    If the student has been graded, the dictionary also contains their
    grade for the course with the key "grade".
    """
    # Import here instead of top of file since this module gets imported before
    # the course_modes app is loaded, resulting in a Django deprecation warning.
    from common.djangoapps.course_modes.models import CourseMode  # pylint: disable=redefined-outer-name, reimported

    if generated_certificate:
        cert_status = {
            'status': generated_certificate.status,
            'mode': generated_certificate.mode,
            'uuid': generated_certificate.verify_uuid,
        }
        if generated_certificate.grade:
            cert_status['grade'] = generated_certificate.grade

        if generated_certificate.mode == 'audit':
            course_mode_slugs = [
                mode.slug for mode in CourseMode.modes_for_course(
                    generated_certificate.course_id)
            ]
            # Short term fix to make sure old audit users with certs still see their certs
            # only do this if there if no honor mode
            if 'honor' not in course_mode_slugs:
                cert_status['status'] = CertificateStatuses.auditing
                return cert_status

        if generated_certificate.status == CertificateStatuses.downloadable:
            cert_status['download_url'] = generated_certificate.download_url

        return cert_status
    else:
        return {
            'status': CertificateStatuses.unavailable,
            'mode': GeneratedCertificate.MODES.honor,
            'uuid': None
        }
Beispiel #19
0
def get_celebrations_dict(user, enrollment, course, browser_timezone):
    """
    Returns a dict of celebrations that should be performed.
    """
    if not enrollment:
        return {
            'first_section': False,
            'streak_length_to_celebrate': None,
            'streak_discount_enabled': False,
        }

    streak_length_to_celebrate = UserCelebration.perform_streak_updates(
        user, course.id, browser_timezone)
    celebrations = {
        'first_section':
        CourseEnrollmentCelebration.should_celebrate_first_section(enrollment),
        'streak_length_to_celebrate':
        streak_length_to_celebrate,
        'streak_discount_enabled':
        False,
    }

    if streak_length_to_celebrate:
        # We only want to offer the streak discount
        # if the course has not ended, is upgradeable and the user is not an enterprise learner

        if can_show_streak_discount_coupon(user, course):
            # Send course streak coupon event
            course_key = str(course.id)
            modes_dict = CourseMode.modes_for_course_dict(
                course_id=course_key, include_expired=False)
            verified_mode = modes_dict.get('verified', None)
            if verified_mode:
                celebrations['streak_discount_enabled'] = True
                segment.track(
                    user_id=user.id,
                    event_name='edx.bi.course.streak_discount_enabled',
                    properties={
                        'course_id': str(course_key),
                        'sku': verified_mode.sku,
                    })

    return celebrations
Beispiel #20
0
    def test_create_with_non_existent_course(self):
        """ Verify the API does not allow data to be created for courses that do not exist. """

        permissions = Permission.objects.filter(
            name__in=('Can add course mode', 'Can change course mode'))
        for permission in permissions:
            self.user.user_permissions.add(permission)

        expected_modes = [
            CourseMode(mode_slug=CourseMode.DEFAULT_MODE_SLUG,
                       min_price=150,
                       currency=u'USD',
                       sku=u'ABC123',
                       bulk_sku=u'BULK-ABC123')
        ]

        course_key = 'non/existing/key'

        course_dict = {
            u'id':
            six.text_type(course_key),
            u'name':
            six.text_type('Non Existing Course'),
            u'verification_deadline':
            None,
            u'modes':
            [self._serialize_course_mode(mode) for mode in expected_modes]
        }

        path = reverse('commerce_api:v1:courses:retrieve_update',
                       args=[six.text_type(course_key)])

        response = self.client.put(path,
                                   json.dumps(course_dict),
                                   content_type=JSON_CONTENT_TYPE)
        self.assertEqual(response.status_code, 400)

        expected_dict = {
            'id': [u'Course {} does not exist.'.format(course_key)]
        }
        self.assertDictEqual(expected_dict,
                             json.loads(response.content.decode('utf-8')))
Beispiel #21
0
 def test_invalid_mode_expiration(self, mode_slug, exp_dt_name):
     exp_dt = self.DATES[exp_dt_name]
     is_error_expected = CourseMode.is_professional_slug(
         mode_slug) and exp_dt is not None
     try:
         self.create_mode(mode_slug=mode_slug,
                          mode_name=mode_slug.title(),
                          expiration_datetime=exp_dt,
                          min_price=10)
         self.assertFalse(is_error_expected,
                          "Expected a ValidationError to be thrown.")
     except ValidationError as exc:
         self.assertTrue(is_error_expected,
                         "Did not expect a ValidationError to be thrown.")
         self.assertEqual(
             exc.messages,
             [
                 u"Professional education modes are not allowed to have expiration_datetime set."
             ],
         )
Beispiel #22
0
def _calculate_upgrade_deadline(course_id, content_availability_date):  # lint-amnesty, pylint: disable=missing-function-docstring
    upgrade_deadline = None

    delta = _get_upgrade_deadline_delta_setting(course_id)
    if delta is not None:
        upgrade_deadline = content_availability_date + datetime.timedelta(days=delta)
        if upgrade_deadline is not None:
            # The content availability-based deadline should never occur
            # after the verified mode's expiration date, if one is set.
            try:
                verified_mode = CourseMode.verified_mode_for_course(course_id)
            except CourseMode.DoesNotExist:
                pass
            else:
                if verified_mode:
                    course_mode_upgrade_deadline = verified_mode.expiration_datetime
                    if course_mode_upgrade_deadline is not None:
                        upgrade_deadline = min(upgrade_deadline, course_mode_upgrade_deadline)

    return upgrade_deadline
Beispiel #23
0
    def test_all_modes_for_courses(self):
        now_dt = now()
        future = now_dt + timedelta(days=1)
        past = now_dt - timedelta(days=1)

        # Unexpired, no expiration date
        CourseModeFactory.create(
            course_id=self.course_key,
            mode_display_name="Honor No Expiration",
            mode_slug="honor_no_expiration",
            expiration_datetime=None
        )

        # Unexpired, expiration date in future
        CourseModeFactory.create(
            course_id=self.course_key,
            mode_display_name="Honor Not Expired",
            mode_slug="honor_not_expired",
            expiration_datetime=future
        )

        # Expired
        CourseModeFactory.create(
            course_id=self.course_key,
            mode_display_name="Verified Expired",
            mode_slug="verified_expired",
            expiration_datetime=past
        )

        # We should get all of these back when querying for *all* course modes,
        # including ones that have expired.
        other_course_key = CourseLocator(org="not", course="a", run="course")
        all_modes = CourseMode.all_modes_for_courses([self.course_key, other_course_key])
        assert len(all_modes[self.course_key]) == 3
        assert all_modes[self.course_key][0].name == 'Honor No Expiration'
        assert all_modes[self.course_key][1].name == 'Honor Not Expired'
        assert all_modes[self.course_key][2].name == 'Verified Expired'

        # Check that we get a default mode for when no course mode is available
        assert len(all_modes[other_course_key]) == 1
        assert all_modes[other_course_key][0] == CourseMode.DEFAULT_MODE
Beispiel #24
0
    def _display_steps(self, always_show_payment, already_verified, already_paid, course_mode):
        """Determine which steps to display to the user.

        Includes all steps by default, but removes steps
        if the user has already completed them.

        Arguments:

            always_show_payment (bool): If True, display the payment steps
                even if the user has already paid.

            already_verified (bool): Whether the user has submitted
                a verification request recently.

            already_paid (bool): Whether the user is enrolled in a paid
                course mode.

        Returns:
            list

        """
        display_steps = self.ALL_STEPS
        remove_steps = set()

        if already_verified or not CourseMode.is_verified_mode(course_mode):
            remove_steps |= set(self.VERIFICATION_STEPS)

        if already_paid and not always_show_payment:
            remove_steps |= set(self.PAYMENT_STEPS)
        else:
            # The "make payment" step doubles as an intro step,
            # so if we're showing the payment step, hide the intro step.
            remove_steps |= set([self.INTRO_STEP])
        return [
            {
                'name': step,
                'title': six.text_type(self.STEP_TITLES[step]),
            }
            for step in display_steps
            if step not in remove_steps
        ]
Beispiel #25
0
def _enrollment_mode_display(enrollment_mode, course_id):
    """Checking enrollment mode and status and returns the display mode
     Args:
        enrollment_mode (str): enrollment mode.
        verification_status (str) : verification status of student

    Returns:
        display_mode (str) : display mode for certs
    """
    course_mode_slugs = [mode.slug for mode in CourseMode.modes_for_course(course_id)]

    if enrollment_mode == CourseMode.VERIFIED:
        display_mode = DISPLAY_VERIFIED
        if DISPLAY_HONOR in course_mode_slugs:
            display_mode = DISPLAY_HONOR
    elif enrollment_mode in [CourseMode.PROFESSIONAL, CourseMode.NO_ID_PROFESSIONAL_MODE]:
        display_mode = DISPLAY_PROFESSIONAL
    else:
        display_mode = enrollment_mode

    return display_mode
def get_user_course_duration(user, course):
    """
    Return a timedelta measuring the duration of the course for a particular user.

    Business Logic:
      - Course access duration is bounded by the min and max duration.
      - If course fields are missing, default course access duration to MIN_DURATION.
    """
    if not CourseDurationLimitConfig.enabled_for_enrollment(user, course):
        return None

    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    if enrollment is None or enrollment.mode != CourseMode.AUDIT:
        return None

    verified_mode = CourseMode.verified_mode_for_course(course=course,
                                                        include_expired=True)
    if not verified_mode:
        return None

    return get_expected_duration(course.id)
Beispiel #27
0
def set_entitlement_policy(entitlement, site):
    """
    Assign the appropriate CourseEntitlementPolicy to the given CourseEntitlement based on its mode and site.

    Arguments:
        entitlement: Course Entitlement object
        site: string representation of a Site object

    Notes:
        Site-specific, mode-agnostic policies take precedence over mode-specific, site-agnostic policies.
        If no appropriate CourseEntitlementPolicy is found, the default CourseEntitlementPolicy is assigned.
    """
    policy_mode = entitlement.mode
    if CourseMode.is_professional_slug(policy_mode):
        policy_mode = CourseMode.PROFESSIONAL
    filter_query = (Q(site=site) | Q(site__isnull=True)) & (
        Q(mode=policy_mode) | Q(mode__isnull=True))
    policy = CourseEntitlementPolicy.objects.filter(filter_query).order_by(
        '-site', '-mode').first()
    entitlement.policy = policy if policy else None
    entitlement.save()
Beispiel #28
0
    def test_verified_mode_creation(self, mode_slug, mode_display_name,
                                    min_price, suggested_prices, currency):
        parameters = {}
        parameters['mode_slug'] = mode_slug
        parameters['mode_display_name'] = mode_display_name
        parameters['min_price'] = min_price
        parameters['suggested_prices'] = suggested_prices
        parameters['currency'] = currency

        url = reverse('create_mode', args=[str(self.course.id)])
        response = self.client.get(url, parameters)

        assert response.status_code == 200

        expected_mode = [
            Mode(mode_slug, mode_display_name, min_price, suggested_prices,
                 currency, None, None, None, None)
        ]
        course_mode = CourseMode.modes_for_course(self.course.id)

        assert course_mode == expected_mode
Beispiel #29
0
    def test_update_overwrite(self):
        """
        Verify that data submitted via PUT overwrites/deletes modes that are
        not included in the body of the request, EXCEPT the Masters mode,
        which it leaves alone.
        """
        existing_mode = self.course_mode
        existing_masters_mode = CourseMode.objects.create(
            course_id=self.course.id,
            mode_slug=u'masters',
            min_price=10000,
            currency=u'USD',
            sku=u'DEF456',
            bulk_sku=u'BULK-DEF456'
        )
        new_mode = CourseMode(
            course_id=self.course.id,
            mode_slug=u'credit',
            min_price=500,
            currency=u'USD',
            sku=u'ABC123',
            bulk_sku=u'BULK-ABC123'
        )

        path = reverse('commerce_api:v1:courses:retrieve_update', args=[six.text_type(self.course.id)])
        data = json.dumps(self._serialize_course(self.course, [new_mode]))
        response = self.client.put(path, data, content_type=JSON_CONTENT_TYPE)
        self.assertEqual(response.status_code, 200)

        # Check modes list in response, disregarding its order.
        expected_dict = self._serialize_course(self.course, [new_mode])
        expected_items = expected_dict['modes']
        actual_items = json.loads(response.content.decode('utf-8'))['modes']
        self.assertCountEqual(actual_items, expected_items)

        # The existing non-Masters CourseMode should have been removed.
        self.assertFalse(CourseMode.objects.filter(id=existing_mode.id).exists())

        # The existing Masters course mode should remain.
        self.assertTrue(CourseMode.objects.filter(id=existing_masters_mode.id).exists())
Beispiel #30
0
 def description(self):
     """
     Returns a description for what experience changes a learner encounters when the course end date passes.
     Note that this currently contains 4 scenarios:
         1. End date is in the future and learner is enrolled in a certificate earning mode
         2. End date is in the future and learner is not enrolled at all or not enrolled
             in a certificate earning mode
         3. End date is in the past
         4. End date does not exist (and now neither does the description)
     """
     if self.date and self.current_time <= self.date:
         mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_id)
         if is_active and CourseMode.is_eligible_for_certificate(mode):
             return _('After this date, the course will be archived, which means you can review the '
                      'course content but can no longer participate in graded assignments or work towards earning '
                      'a certificate.')
         else:
             return _('After the course ends, the course content will be archived and no longer active.')
     elif self.date:
         return _('This course is archived, which means you can review course content but it is no longer active.')
     else:
         return ''