示例#1
0
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)

        entitlement = serializer.instance
        user = entitlement.user

        # find all course_runs within the course
        course_runs = get_course_runs_for_course(entitlement.course_uuid)

        # check if the user has enrollments for any of the course_runs
        user_run_enrollments = [
            CourseEnrollment.get_enrollment(user, CourseKey.from_string(course_run.get('key')))
            for course_run
            in course_runs
            if CourseEnrollment.get_enrollment(user, CourseKey.from_string(course_run.get('key')))
        ]

        # filter to just enrollments that can be upgraded.
        upgradeable_enrollments = [
            enrollment
            for enrollment
            in user_run_enrollments
            if enrollment.is_active and enrollment.upgrade_deadline and enrollment.upgrade_deadline > timezone.now()
        ]

        # if there is only one upgradeable enrollment, convert it from audit to the entitlement.mode
        # if there is any ambiguity about which enrollment to upgrade
        # (i.e. multiple upgradeable enrollments or no available upgradeable enrollment), dont enroll
        if len(upgradeable_enrollments) == 1:
            enrollment = upgradeable_enrollments[0]
            log.info(
                'Upgrading enrollment [%s] from %s to %s while adding entitlement for user [%s] for course [%s]',
                enrollment,
                enrollment.mode,
                serializer.data.get('mode'),
                user.username,
                serializer.data.get('course_uuid')
            )
            enrollment.update_enrollment(mode=entitlement.mode)
            entitlement.set_enrollment(enrollment)
        else:
            log.info(
                'No enrollment upgraded while adding entitlement for user [%s] for course [%s] ',
                user.username,
                serializer.data.get('course_uuid')
            )

        headers = self.get_success_headers(serializer.data)
        # Note, the entitlement is re-serialized before getting added to the Response,
        # so that the 'modified' date reflects changes that occur when upgrading enrollment.
        return Response(
            CourseEntitlementSerializer(entitlement).data,
            status=status.HTTP_201_CREATED, headers=headers
        )
    def test_course_home_for_global_staff(self):
        """
        Tests that staff user can access the course home without being enrolled
        in the course.
        """
        course = self.course
        self.user.is_staff = True
        self.user.save()

        self.override_waffle_switch(True)
        CourseEnrollment.get_enrollment(self.user, course.id).delete()
        response = self.visit_course_home(course, start_count=1, resume_count=0)
        content = pq(response.content)
        self.assertTrue(content('.action-resume-course').attr('href').endswith('/course/' + course.url_name))
示例#3
0
def sync_cohort_with_mode(course_id, user_id, verified_cohort_name):
    """
    If the learner's mode does not match their assigned cohort, move the learner into the correct cohort.
    It is assumed that this task is only initiated for courses that are using the
    Automatic Verified Track Cohorting MVP feature. It is also assumed that before
    initiating this task, verification has been done to ensure that the course is
    cohorted and has an appropriately named "verified" cohort.
    """
    course_key = CourseKey.from_string(course_id)
    user = User.objects.get(id=user_id)
    enrollment = CourseEnrollment.get_enrollment(user, course_key)
    # Note that this will enroll the user in the default cohort on initial enrollment.
    # That's good because it will force creation of the default cohort if necessary.
    current_cohort = get_cohort(user, course_key)
    verified_cohort = get_cohort_by_name(course_key, verified_cohort_name)

    if enrollment.mode == CourseMode.VERIFIED and (current_cohort.id != verified_cohort.id):
        LOGGER.info(
            "MOVING_TO_VERIFIED: Moving user '%s' to the verified cohort for course '%s'", user.username, course_id
        )
        add_user_to_cohort(verified_cohort, user.username)
    elif enrollment.mode != CourseMode.VERIFIED and current_cohort.id == verified_cohort.id:
        LOGGER.info(
            "MOVING_TO_DEFAULT: Moving user '%s' to the default cohort for course '%s'", user.username, course_id
        )
        default_cohort = get_cohort_by_name(course_key, DEFAULT_COHORT_NAME)
        add_user_to_cohort(default_cohort, user.username)
    else:
        LOGGER.info(
            "NO_ACTION_NECESSARY: No action necessary for user '%s' in course '%s' and enrollment mode '%s'",
            user.username, course_id, enrollment.mode
        )
示例#4
0
def course_run_refund_status(request, course_id):
    """
    Get Refundable status for a course.

    Arguments:
        request: The request object.
        course_id (str): The unique identifier for the course.

    Returns:
        Json response.

    """

    try:
        course_key = CourseKey.from_string(course_id)
        course_enrollment = CourseEnrollment.get_enrollment(request.user, course_key)

    except InvalidKeyError:
        logging.exception("The course key used to get refund status caused InvalidKeyError during look up.")

        return JsonResponse({'course_refundable_status': ''}, status=406)

    refundable_status = course_enrollment.refundable()
    logging.info("Course refund status for course {0} is {1}".format(course_id, refundable_status))

    return JsonResponse({'course_refundable_status': refundable_status}, status=200)
示例#5
0
    def handle(self, *args, **options):
        csv_path = options['csv_path']
        with open(csv_path) as csvfile:
            reader = unicodecsv.DictReader(csvfile)
            for row in reader:
                username = row['username']
                email = row['email']
                course_key = row['course_id']
                try:
                    user = User.objects.get(Q(username=username) | Q(email=email))
                except ObjectDoesNotExist:
                    user = None
                    msg = 'User with username {} or email {} does not exist'.format(username, email)
                    logger.warning(msg)

                try:
                    course_id = CourseKey.from_string(course_key)
                except InvalidKeyError:
                    course_id = None
                    msg = 'Invalid course id {course_id}, skipping un-enrollement for {username}, {email}'.format(**row)
                    logger.warning(msg)

                if user and course_id:
                    enrollment = CourseEnrollment.get_enrollment(user, course_id)
                    if not enrollment:
                        msg = 'Enrollment for the user {} in course {} does not exist!'.format(username, course_key)
                        logger.info(msg)
                    else:
                        try:
                            CourseEnrollment.unenroll(user, course_id, skip_refund=True)
                        except Exception as err:
                            msg = 'Error un-enrolling User {} from course {}: '.format(username, course_key, err)
                            logger.error(msg, exc_info=True)
示例#6
0
 def register_alerts(self, request, course):
     """
     Registers an alert if the course has not started yet.
     """
     is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id)
     if not course.start or not is_enrolled:
         return
     days_until_start = (course.start - self.current_time).days
     if course.start > self.current_time:
         if days_until_start > 0:
             CourseHomeMessages.register_info_message(
                 request,
                 Text(_(
                     "Don't forget to add a calendar reminder!"
                 )),
                 title=Text(_("Course starts in {time_remaining_string} on {course_start_date}.")).format(
                     time_remaining_string=self.time_remaining_string,
                     course_start_date=self.long_date_html,
                 )
             )
         else:
             CourseHomeMessages.register_info_message(
                 request,
                 Text(_("Course starts in {time_remaining_string} at {course_start_time}.")).format(
                     time_remaining_string=self.time_remaining_string,
                     course_start_time=self.short_time_html,
                 )
             )
示例#7
0
 def register_alerts(self, request, course):
     """
     Registers an alert if the end date is approaching.
     """
     is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id)
     if not course.start or self.current_time < course.start or not is_enrolled:
         return
     days_until_end = (course.end - self.current_time).days
     if course.end > self.current_time and days_until_end <= settings.COURSE_MESSAGE_ALERT_DURATION_IN_DAYS:
         if days_until_end > 0:
             CourseHomeMessages.register_info_message(
                 request,
                 Text(self.description),
                 title=Text(_('This course is ending in {time_remaining_string} on {course_end_date}.')).format(
                     time_remaining_string=self.time_remaining_string,
                     course_end_date=self.long_date_html,
                 )
             )
         else:
             CourseHomeMessages.register_info_message(
                 request,
                 Text(self.description),
                 title=Text(_('This course is ending in {time_remaining_string} at {course_end_time}.')).format(
                     time_remaining_string=self.time_remaining_string,
                     course_end_time=self.short_time_html,
                 )
             )
示例#8
0
def check_and_get_upgrade_link_and_date(user, enrollment=None, course=None):
    """
    For an authenticated user, return a link to allow them to upgrade
    in the specified course.
    """
    if enrollment is None and course is None:
        raise ValueError("Must specify either an enrollment or a course")

    if enrollment:
        if course is None:
            course = enrollment.course
        elif enrollment.course_id != course.id:
            raise ValueError("{} refers to a different course than {} which was supplied".format(
                enrollment, course
            ))

        if enrollment.user_id != user.id:
            raise ValueError("{} refers to a different user than {} which was supplied".format(
                enrollment, user
            ))

    if enrollment is None:
        enrollment = CourseEnrollment.get_enrollment(user, course.id)

    if user.is_authenticated and verified_upgrade_link_is_valid(enrollment):
        return (
            verified_upgrade_deadline_link(user, course),
            enrollment.upgrade_deadline
        )

    return (None, None)
示例#9
0
    def get_upgradeable_enrollments_for_entitlement(self, entitlement):
        """
        Retrieve all the CourseEnrollments that are upgradeable for a given CourseEntitlement

        Arguments:
            entitlement: CourseEntitlement that we are requesting the CourseEnrollments for.

        Returns:
            list: List of upgradeable CourseEnrollments
        """
        # find all course_runs within the course
        course_runs = get_course_runs_for_course(entitlement.course_uuid)

        # check if the user has enrollments for any of the course_runs
        upgradeable_enrollments = []
        for course_run in course_runs:
            course_run_id = CourseKey.from_string(course_run.get('key'))
            enrollment = CourseEnrollment.get_enrollment(entitlement.user, course_run_id)

            if (enrollment and
                    enrollment.is_active and
                    is_course_run_entitlement_fulfillable(course_run_id, entitlement)):
                upgradeable_enrollments.append(enrollment)

        return upgradeable_enrollments
示例#10
0
def add_or_update_enrollment_attr(user_id, course_id, attributes):
    """Set enrollment attributes for the enrollment of given user in the
    course provided.

    Args:
        course_id (str): The Course to set enrollment attributes for.
        user_id (str): The User to set enrollment attributes for.
        attributes (list): Attributes to be set.

    Example:
        >>>add_or_update_enrollment_attr(
            "Bob",
            "course-v1-edX-DemoX-1T2015",
            [
                {
                    "namespace": "credit",
                    "name": "provider_id",
                    "value": "hogwarts",
                },
            ]
        )
    """
    course_key = CourseKey.from_string(course_id)
    user = _get_user(user_id)
    enrollment = CourseEnrollment.get_enrollment(user, course_key)
    if not _invalid_attribute(attributes) and enrollment is not None:
        CourseEnrollmentAttribute.add_enrollment_attr(enrollment, attributes)
示例#11
0
    def get_credit_state(self, user_id, course_key_or_id, return_course_info=False):
        """
        Return all information about the user's credit state inside of a given
        course.

        ARGS:
            - user_id: The PK of the User in question
            - course_key: The course ID (as string or CourseKey)

        RETURNS:
            NONE (user not found or is not enrolled or is not credit course)
            - or -
            {
                'enrollment_mode': the mode that the user is enrolled in the course
                'profile_fullname': the name that the student registered under, used for verification
                'is_credit_course': if the course has been marked as a credit bearing course
                'credit_requirement_status': the user's status in fulfilling those requirements
                'course_name': optional display name of the course
                'course_end_date': optional end date of the course
            }
        """

        # This seems to need to be here otherwise we get
        # circular references when starting up the app
        from openedx.core.djangoapps.credit.api.eligibility import (
            is_credit_course,
            get_credit_requirement_status,
        )

        # since we have to do name matching during various
        # verifications, User must have a UserProfile
        try:
            user = User.objects.select_related('profile').get(id=user_id)
        except ObjectDoesNotExist:
            # bad user_id
            return None

        course_key = _get_course_key(course_key_or_id)

        enrollment = CourseEnrollment.get_enrollment(user, course_key)
        if not enrollment or not enrollment.is_active:
            # not enrolled
            return None

        result = {
            'enrollment_mode': enrollment.mode,
            'profile_fullname': user.profile.name,
            'student_email': user.email,
            'is_credit_course': is_credit_course(course_key),
            'credit_requirement_status': get_credit_requirement_status(course_key, user.username)
        }

        if return_course_info:
            course = modulestore().get_course(course_key, depth=0)
            result.update({
                'course_name': course.display_name,
                'course_end_date': course.end,
            })
        return result
示例#12
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
        """
        Return whether Content Type Gating is enabled for this enrollment.

        Content Type Gating is enabled for an enrollment if it is enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now())
        else:
            # TODO: clean up as part of REV-100
            experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(user)
            is_in_holdback = False
            no_masquerade = get_course_masquerade(user, course_key) is None
            student_masquerade = is_masquerading_as_specific_student(user, course_key)
            if user and (no_masquerade or student_masquerade):
                try:
                    holdback_value = ExperimentData.objects.get(
                        user=user,
                        experiment_id=EXPERIMENT_ID,
                        key=experiment_data_holdback_key,
                    ).value
                    is_in_holdback = holdback_value == 'True'
                except ExperimentData.DoesNotExist:
                    pass
            if is_in_holdback:
                return False
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)
    def get_enrollment_info(self, user, course_id):
        """
        Returns the User Enrollment information.
        """
        course = get_course_by_id(course_id, depth=0)
        is_course_staff = bool(has_access(user, 'staff', course))
        manual_enrollment_reason = 'N/A'

        # check the user enrollment role
        if user.is_staff:
            platform_name = configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
            enrollment_role = _('{platform_name} Staff').format(platform_name=platform_name)
        elif is_course_staff:
            enrollment_role = _('Course Staff')
        else:
            enrollment_role = _('Student')

        course_enrollment = CourseEnrollment.get_enrollment(user=user, course_key=course_id)

        if is_course_staff:
            enrollment_source = _('Staff')
        else:
            # get the registration_code_redemption object if exists
            registration_code_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment(
                course_enrollment)
            # get the paid_course registration item if exists
            paid_course_reg_item = PaidCourseRegistration.get_course_item_for_user_enrollment(
                user=user,
                course_id=course_id,
                course_enrollment=course_enrollment
            )

            # from where the user get here
            if registration_code_redemption is not None:
                enrollment_source = _('Used Registration Code')
            elif paid_course_reg_item is not None:
                enrollment_source = _('Credit Card - Individual')
            else:
                manual_enrollment = ManualEnrollmentAudit.get_manual_enrollment(course_enrollment)
                if manual_enrollment is not None:
                    enrollment_source = _(
                        'manually enrolled by username: {username}'
                    ).format(username=manual_enrollment.enrolled_by.username)

                    manual_enrollment_reason = manual_enrollment.reason
                else:
                    enrollment_source = _('Manually Enrolled')

        enrollment_date = course_enrollment.created.strftime("%B %d, %Y")
        currently_enrolled = course_enrollment.is_active

        course_enrollment_data = collections.OrderedDict()
        course_enrollment_data['Enrollment Date'] = enrollment_date
        course_enrollment_data['Currently Enrolled'] = currently_enrolled
        course_enrollment_data['Enrollment Source'] = enrollment_source
        course_enrollment_data['Manual (Un)Enrollment Reason'] = manual_enrollment_reason
        course_enrollment_data['Enrollment Role'] = enrollment_role
        return course_enrollment_data
示例#14
0
    def post(self, request, *args, **kwargs):
        """
        Attempt to enroll the user.
        """
        user = request.user
        valid, course_key, error = self._is_data_valid(request)
        if not valid:
            return DetailResponse(error, status=HTTP_406_NOT_ACCEPTABLE)

        embargo_response = embargo_api.get_embargo_response(request, course_key, user)

        if embargo_response:
            return embargo_response

        # Don't do anything if an enrollment already exists
        course_id = unicode(course_key)
        enrollment = CourseEnrollment.get_enrollment(user, course_key)
        if enrollment and enrollment.is_active:
            msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username)
            return DetailResponse(msg, status=HTTP_409_CONFLICT)

        # Check to see if enrollment for this course is closed.
        course = courses.get_course(course_key)
        if CourseEnrollment.is_enrollment_closed(user, course):
            msg = Messages.ENROLLMENT_CLOSED.format(course_id=course_id)
            log.info(u'Unable to enroll user %s in closed course %s.', user.id, course_id)
            return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE)

        # If there is no audit or honor course mode, this most likely
        # a Prof-Ed course. Return an error so that the JS redirects
        # to track selection.
        honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR)
        audit_mode = CourseMode.mode_for_course(course_key, CourseMode.AUDIT)

        # Accept either honor or audit as an enrollment mode to
        # maintain backwards compatibility with existing courses
        default_enrollment_mode = audit_mode or honor_mode
        if default_enrollment_mode:
            msg = Messages.ENROLL_DIRECTLY.format(
                username=user.username,
                course_id=course_id
            )
            if not default_enrollment_mode.sku:
                # If there are no course modes with SKUs, return a different message.
                msg = Messages.NO_SKU_ENROLLED.format(
                    enrollment_mode=default_enrollment_mode.slug,
                    course_id=course_id,
                    username=user.username
                )
            log.info(msg)
            self._enroll(course_key, user, default_enrollment_mode.slug)
            self._handle_marketing_opt_in(request, course_key, user)
            return DetailResponse(msg)
        else:
            msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format(course_id=course_id)
            return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE)
    def test_lookup_valid_redeemed_registration_code(self):
        """
        test to lookup for the valid and redeemed registration code
        and then mark that registration code as un_redeemed
        which will unenroll the user and delete the redemption
        entry from the database.
        """
        student = UserFactory()
        self.client.login(username=student.username, password='******')
        cart = Order.get_cart_for_user(student)
        cart.order_type = 'business'
        cart.save()
        CourseRegCodeItem.add_to_order(cart, self.course.id, 2)
        cart.purchase()

        reg_code = CourseRegistrationCode.objects.filter(order=cart)[0]

        enrollment = CourseEnrollment.enroll(student, self.course.id)

        RegistrationCodeRedemption.objects.create(
            registration_code=reg_code,
            redeemed_by=student,
            course_enrollment=enrollment
        )
        self.client.login(username=self.instructor.username, password='******')
        data = {
            'registration_code': reg_code.code
        }
        response = self.client.get(self.lookup_code_url, data)
        self.assertEqual(response.status_code, 200)
        json_dict = json.loads(response.content)
        self.assertTrue(json_dict['is_registration_code_valid'])
        self.assertTrue(json_dict['is_registration_code_redeemed'])

        # now mark the registration code as unredeemed
        # this will unenroll the user and removed the redemption entry from
        # the database.

        data = {
            'registration_code': reg_code.code,
            'action_type': 'unredeem_registration_code'
        }
        response = self.client.post(self.registration_code_detail_url, data)
        self.assertEqual(response.status_code, 200)

        json_dict = json.loads(response.content)
        message = _('This enrollment code has been marked as unused.')
        self.assertEqual(message, json_dict['message'])

        redemption = RegistrationCodeRedemption.get_registration_code_redemption(reg_code.code, self.course.id)
        self.assertIsNone(redemption)

        # now the student course enrollment should be false.
        enrollment = CourseEnrollment.get_enrollment(student, self.course.id)
        self.assertEqual(enrollment.is_active, False)
示例#16
0
    def test_look_up_valid_registration_code(self):
        """
        test lookup for the valid registration code
        and that registration code has been redeemed by user
        and then mark the registration code as in_valid
        when marking as invalidate, it also lookup for
        registration redemption entry and also delete
        that redemption entry and un_enroll the student
        who used that registration code for their enrollment.
        """
        for i in range(2):
            CourseRegistrationCode.objects.create(
                code="reg_code{}".format(i),
                course_id=unicode(self.course.id),
                created_by=self.instructor,
                invoice=self.sale_invoice,
                invoice_item=self.invoice_item,
                mode_slug="honor",
            )

        reg_code = CourseRegistrationCode.objects.all()[0]
        student = UserFactory()
        enrollment = CourseEnrollment.enroll(student, self.course.id)

        RegistrationCodeRedemption.objects.create(
            registration_code=reg_code, redeemed_by=student, course_enrollment=enrollment
        )

        data = {"registration_code": reg_code.code}
        response = self.client.get(self.lookup_code_url, data)
        self.assertEqual(response.status_code, 200)
        json_dict = json.loads(response.content)
        self.assertTrue(json_dict["is_registration_code_valid"])
        self.assertTrue(json_dict["is_registration_code_redeemed"])

        # now mark that registration code as invalid
        data = {"registration_code": reg_code.code, "action_type": "invalidate_registration_code"}
        response = self.client.post(self.registration_code_detail_url, data)
        self.assertEqual(response.status_code, 200)

        json_dict = json.loads(response.content)
        message = _("This enrollment code has been canceled. It can no longer be used.")
        self.assertEqual(message, json_dict["message"])

        # now check that the registration code should be marked as invalid in the db.
        reg_code = CourseRegistrationCode.objects.get(code=reg_code.code)
        self.assertEqual(reg_code.is_valid, False)

        redemption = RegistrationCodeRedemption.get_registration_code_redemption(reg_code.code, self.course.id)
        self.assertIsNone(redemption)

        # now the student course enrollment should be false.
        enrollment = CourseEnrollment.get_enrollment(student, self.course.id)
        self.assertEqual(enrollment.is_active, False)
示例#17
0
def sync_cohort_with_mode(self, course_id, user_id, verified_cohort_name, default_cohort_name):
    """
    If the learner's mode does not match their assigned cohort, move the learner into the correct cohort.
    It is assumed that this task is only initiated for courses that are using the
    Automatic Verified Track Cohorting MVP feature. It is also assumed that before
    initiating this task, verification has been done to ensure that the course is
    cohorted and has an appropriately named "verified" cohort.
    """
    course_key = CourseKey.from_string(course_id)
    user = User.objects.get(id=user_id)
    try:
        enrollment = CourseEnrollment.get_enrollment(user, course_key)
        # Note that this will enroll the user in the default cohort on initial enrollment.
        # That's good because it will force creation of the default cohort if necessary.
        try:
            current_cohort = get_cohort(user, course_key)
        except IntegrityError as integrity_error:
            # It is quite common that get_cohort will throw an IntegrityError. This happens
            # when 2 celery workers are both handling enrollment change events for the same user
            # (for example, if the enrollment mode goes from None -> Audit -> Honor); if the user
            # was not previously in a cohort, calling get_cohort will result in a cohort assignment.
            LOGGER.info(
                "HANDLING_INTEGRITY_ERROR: IntegrityError encountered for course '%s' and user '%s': %s",
                course_id, user.id, unicode(integrity_error)
            )
            current_cohort = get_cohort(user, course_key)

        verified_cohort = get_cohort_by_name(course_key, verified_cohort_name)

        if enrollment.mode == CourseMode.VERIFIED and (current_cohort.id != verified_cohort.id):
            LOGGER.info(
                "MOVING_TO_VERIFIED: Moving user '%s' to the verified cohort '%s' for course '%s'",
                user.id, verified_cohort.name, course_id
            )
            add_user_to_cohort(verified_cohort, user.username)
        elif enrollment.mode != CourseMode.VERIFIED and current_cohort.id == verified_cohort.id:
            default_cohort = get_cohort_by_name(course_key, default_cohort_name)
            LOGGER.info(
                "MOVING_TO_DEFAULT: Moving user '%s' to the default cohort '%s' for course '%s'",
                user.id, default_cohort.name, course_id
            )
            add_user_to_cohort(default_cohort, user.username)
        else:
            LOGGER.info(
                "NO_ACTION_NECESSARY: No action necessary for user '%s' in course '%s' and enrollment mode '%s'. "
                "The user is already in cohort '%s'.",
                user.id, course_id, enrollment.mode, current_cohort.name
            )
    except Exception as exc:
        LOGGER.warning(
            "SYNC_COHORT_WITH_MODE_RETRY: Exception encountered for course '%s' and user '%s': %s",
            course_id, user.id, unicode(exc)
        )
        raise self.retry(exc=exc)
示例#18
0
 def upgrade_url(self, user, course_key):
     """
     Returns the URL for the user to upgrade, or None if not applicable.
     """
     enrollment = CourseEnrollment.get_enrollment(user, course_key)
     verified_mode = enrollment.verified_mode if enrollment else None
     if verified_mode:
         if self.is_enabled(user):
             return self.get_checkout_page_url(verified_mode.sku)
         else:
             return reverse('verify_student_upgrade_and_verify', args=(course_key,))
     return None
示例#19
0
文件: utils.py 项目: edx/edx-platform
def check_and_get_upgrade_link_and_date(user, enrollment=None, course=None):
    """
    For an authenticated user, return a link to allow them to upgrade
    in the specified course.

    Returns the upgrade link and upgrade deadline for a user in a given course given
    that the user is within the window to upgrade defined by our dynamic pacing feature;
    otherwise, returns None for both the link and date.
    """
    if enrollment is None and course is None:
        logger.warn(u'Must specify either an enrollment or a course')
        return (None, None)

    if enrollment:
        if course is None:
            course = enrollment.course
        elif enrollment.course_id != course.id:
            logger.warn(u'{} refers to a different course than {} which was supplied. Enrollment course id={}, '
                        u'repr={!r}, deprecated={}. Course id={}, repr={!r}, deprecated={}.'
                        .format(enrollment,
                                course,
                                enrollment.course_id,
                                enrollment.course_id,
                                enrollment.course_id.deprecated,
                                course.id,
                                course.id,
                                course.id.deprecated
                                )
                        )
            return (None, None)

        if enrollment.user_id != user.id:
            logger.warn(u'{} refers to a different user than {} which was supplied. Enrollment user id={}, repr={!r}. '
                        u'User id={}, repr={!r}.'.format(enrollment,
                                                         user,
                                                         enrollment.user_id,
                                                         enrollment.user_id,
                                                         user.id,
                                                         user.id,
                                                         )
                        )
            return (None, None)

    if enrollment is None:
        enrollment = CourseEnrollment.get_enrollment(user, course.id)

    if user.is_authenticated and verified_upgrade_link_is_valid(enrollment):
        return (
            verified_upgrade_deadline_link(user, course),
            enrollment.upgrade_deadline
        )

    return (None, None)
    def test_bulk_enrollment(self):
        """ Test all users are enrolled using the command."""
        lines = (str(enrollment.course.id) + "," + str(enrollment.user.username) + ",verified\n"
                 for enrollment in self.enrollments)

        with NamedTemporaryFile() as csv:
            csv = self._write_test_csv(csv, lines=lines)
            call_command("bulk_change_enrollment_csv", "--csv_file_path={}".format(csv.name))

        for enrollment in self.enrollments:
            new_enrollment = CourseEnrollment.get_enrollment(user=enrollment.user, course_key=enrollment.course)
            self.assertEqual(new_enrollment.is_active, True)
            self.assertEqual(new_enrollment.mode, CourseMode.VERIFIED)
    def get_enrollment_info(self, user, course_id):
        """
        Returns the User Enrollment information.
        """
        course = get_course_by_id(course_id, depth=0)
        is_course_staff = has_access(user, 'staff', course)

        # check the user enrollment role
        if user.is_staff:
            enrollment_role = _('Edx Staff')
        elif is_course_staff:
            enrollment_role = _('Course Staff')
        else:
            enrollment_role = _('Student')

        course_enrollment = CourseEnrollment.get_enrollment(user=user, course_key=course_id)

        if is_course_staff:
            enrollment_source = _('Staff')
        else:
            # get the registration_code_redemption object if exists
            registration_code_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment(
                course_enrollment)
            # get the paid_course registration item if exists
            paid_course_reg_item = PaidCourseRegistration.get_course_item_for_user_enrollment(
                user=user,
                course_id=course_id,
                course_enrollment=course_enrollment
            )

            # from where the user get here
            if registration_code_redemption is not None:
                enrollment_source = _('Used Registration Code')
            elif paid_course_reg_item is not None:
                enrollment_source = _('Credit Card - Individual')
            else:
                enrollment_source = _('Manually Enrolled')

        enrollment_date = course_enrollment.created.strftime("%B %d, %Y")
        currently_enrolled = course_enrollment.is_active

        course_enrollment_data = collections.OrderedDict()
        course_enrollment_data['Enrollment Date'] = enrollment_date
        course_enrollment_data['Currently Enrolled'] = currently_enrolled
        course_enrollment_data['Enrollment Source'] = enrollment_source
        course_enrollment_data['Enrollment Role'] = enrollment_role
        return course_enrollment_data
示例#22
0
def get_user_course_expiration_date(user, course):
    """
    Return expiration date for given user course pair.
    Return None if the course does not expire.

    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.
    """
    access_duration = MIN_DURATION

    verified_mode = CourseMode.verified_mode_for_course(course=course, include_expired=True)

    if not verified_mode:
        return None

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

    try:
        # Content availability date is equivalent to max(enrollment date, course start date)
        # for most people. Using the schedule date will provide flexibility to deal with
        # more complex business rules in the future.
        content_availability_date = enrollment.schedule.start
        # We have anecdotally observed a case where the schedule.start was
        # equal to the course start, but should have been equal to the enrollment start
        # https://openedx.atlassian.net/browse/PROD-58
        # This section is meant to address that case
        if enrollment.created and course.start:
            if (content_availability_date.date() == course.start.date() and
               course.start < enrollment.created < timezone.now()):
                content_availability_date = enrollment.created
    except CourseEnrollment.schedule.RelatedObjectDoesNotExist:
        content_availability_date = max(enrollment.created, course.start)

    # The user course expiration date is the content availability date
    # plus the weeks_to_complete field from course-discovery.
    discovery_course_details = get_course_run_details(course.id, ['weeks_to_complete'])
    expected_weeks = discovery_course_details.get('weeks_to_complete')
    if expected_weeks:
        access_duration = timedelta(weeks=expected_weeks)

    # Course access duration is bounded by the min and max duration.
    access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

    return content_availability_date + access_duration
示例#23
0
    def get_verification_context(request, course):
        enrollment = CourseEnrollment.get_enrollment(request.user, course.id)
        show_course_sock = verified_upgrade_link_is_valid(enrollment) and get_language() == 'en'
        if show_course_sock:
            upgrade_url = verified_upgrade_deadline_link(request.user, course=course)
            course_price = get_cosmetic_verified_display_price(course)
        else:
            upgrade_url = ''
            course_price = ''

        context = {
            'show_course_sock': show_course_sock,
            'course_price': course_price,
            'course_id': course.id,
            'upgrade_url': upgrade_url,
        }

        return context
示例#24
0
文件: tasks.py 项目: edx/edx-platform
def sync_cohort_with_mode(self, course_id, user_id, verified_cohort_name, default_cohort_name):
    """
    If the learner's mode does not match their assigned cohort, move the learner into the correct cohort.
    It is assumed that this task is only initiated for courses that are using the
    Automatic Verified Track Cohorting MVP feature. It is also assumed that before
    initiating this task, verification has been done to ensure that the course is
    cohorted and has an appropriately named "verified" cohort.
    """
    course_key = CourseKey.from_string(course_id)
    user = User.objects.get(id=user_id)
    try:
        enrollment = CourseEnrollment.get_enrollment(user, course_key)
        # Note that this will enroll the user in the default cohort on initial enrollment.
        # That's good because it will force creation of the default cohort if necessary.
        current_cohort = get_cohort(user, course_key)

        verified_cohort = get_cohort_by_name(course_key, verified_cohort_name)

        acceptable_modes = {CourseMode.VERIFIED, CourseMode.CREDIT_MODE}
        if enrollment.mode in acceptable_modes and (current_cohort.id != verified_cohort.id):
            LOGGER.info(
                u"MOVING_TO_VERIFIED: Moving user '%s' to the verified cohort '%s' for course '%s'",
                user.id, verified_cohort.name, course_id
            )
            add_user_to_cohort(verified_cohort, user.username)
        elif enrollment.mode not in acceptable_modes and current_cohort.id == verified_cohort.id:
            default_cohort = get_cohort_by_name(course_key, default_cohort_name)
            LOGGER.info(
                u"MOVING_TO_DEFAULT: Moving user '%s' to the default cohort '%s' for course '%s'",
                user.id, default_cohort.name, course_id
            )
            add_user_to_cohort(default_cohort, user.username)
        else:
            LOGGER.info(
                u"NO_ACTION_NECESSARY: No action necessary for user '%s' in course '%s' and enrollment mode '%s'. "
                u"The user is already in cohort '%s'.",
                user.id, course_id, enrollment.mode, current_cohort.name
            )
    except Exception as exc:
        LOGGER.warning(
            u"SYNC_COHORT_WITH_MODE_RETRY: Exception encountered for course '%s' and user '%s': %s",
            course_id, user.id, six.text_type(exc)
        )
        raise self.retry(exc=exc)
示例#25
0
def _get_zendesk_custom_field_context(request):
    """
    Construct a dictionary of data that can be stored in Zendesk custom fields.
    """
    context = {}

    course_id = request.POST.get("course_id")
    if not course_id:
        return context

    context["course_id"] = course_id
    if not request.user.is_authenticated():
        return context

    enrollment = CourseEnrollment.get_enrollment(request.user, CourseKey.from_string(course_id))
    if enrollment and enrollment.is_active:
        context["enrollment_mode"] = enrollment.mode

    return context
示例#26
0
    def manual_enrollment_data(enrollment_data, course_key):
        """
        Returns serialized information about the manual enrollment
        belonging to this enrollment, if it exists.

        Args:
          enrollment_data (dict): Representation of a single course enrollment.
          course_key (CourseKey): The course for this enrollment.

        Returns:
          None: If no manual enrollment change has been made.
          dict: Serialization of the latest manual enrollment change.
        """
        user = User.objects.get(username=enrollment_data['user'])
        enrollment = CourseEnrollment.get_enrollment(user, course_key)
        manual_enrollment_audit = ManualEnrollmentAudit.get_manual_enrollment(enrollment)
        if manual_enrollment_audit is None:
            return {}
        return ManualEnrollmentSerializer(instance=manual_enrollment_audit).data
示例#27
0
    def manual_enrollment_data(enrollment_data, course_key):
        """
        Returns serialized information about the manual enrollment
        belonging to this enrollment, if it exists.

        Args:
          enrollment_data (dict): Representation of a single course enrollment.
          course_key (CourseKey): The course for this enrollment.

        Returns:
          None: If no manual enrollment change has been made.
          dict: Serialization of the latest manual enrollment change.
        """
        user = User.objects.get(username=enrollment_data['user'])
        enrollment = CourseEnrollment.get_enrollment(user, course_key)
        manual_enrollment_audit = ManualEnrollmentAudit.get_manual_enrollment(enrollment)
        if manual_enrollment_audit is None:
            return {}
        return ManualEnrollmentSerializer(instance=manual_enrollment_audit).data
    def get_course_enrollment(self, course_key, user):
        """
        If student is not enrolled in course enroll the student in free mode
        """

        course_enrollment = CourseEnrollment.get_enrollment(user, course_key)
        #  If student is not enrolled in course enroll the student in free mode
        if not course_enrollment:
            # try to create a enroll user in default course enrollment mode in case of
            # professional it will break because of no default course mode.
            try:
                course_enrollment = CourseEnrollment.get_or_create_enrollment(
                    user=user, course_key=course_key)

            except Exception:  # pylint: disable=broad-except
                # In case if no free mode is available.
                course_enrollment = None

        return course_enrollment
示例#29
0
def sync_cohort_with_mode(self, course_id, user_id, verified_cohort_name,
                          default_cohort_name):
    """
    If the learner's mode does not match their assigned cohort, move the learner into the correct cohort.
    It is assumed that this task is only initiated for courses that are using the
    Automatic Verified Track Cohorting MVP feature. It is also assumed that before
    initiating this task, verification has been done to ensure that the course is
    cohorted and has an appropriately named "verified" cohort.
    """
    course_key = CourseKey.from_string(course_id)
    user = User.objects.get(id=user_id)
    try:
        enrollment = CourseEnrollment.get_enrollment(user, course_key)
        # Note that this will enroll the user in the default cohort on initial enrollment.
        # That's good because it will force creation of the default cohort if necessary.
        current_cohort = get_cohort(user, course_key)

        verified_cohort = get_cohort_by_name(course_key, verified_cohort_name)

        acceptable_modes = {CourseMode.VERIFIED, CourseMode.CREDIT_MODE}
        if enrollment.mode in acceptable_modes and (current_cohort.id !=
                                                    verified_cohort.id):
            LOGGER.info(
                "MOVING_TO_VERIFIED: Moving user '%s' to the verified cohort '%s' for course '%s'",
                user.id, verified_cohort.name, course_id)
            add_user_to_cohort(verified_cohort, user.username)
        elif enrollment.mode not in acceptable_modes and current_cohort.id == verified_cohort.id:
            default_cohort = get_cohort_by_name(course_key,
                                                default_cohort_name)
            LOGGER.info(
                "MOVING_TO_DEFAULT: Moving user '%s' to the default cohort '%s' for course '%s'",
                user.id, default_cohort.name, course_id)
            add_user_to_cohort(default_cohort, user.username)
        else:
            LOGGER.info(
                "NO_ACTION_NECESSARY: No action necessary for user '%s' in course '%s' and enrollment mode '%s'. "
                "The user is already in cohort '%s'.", user.id, course_id,
                enrollment.mode, current_cohort.name)
    except Exception as exc:
        LOGGER.warning(
            "SYNC_COHORT_WITH_MODE_RETRY: Exception encountered for course '%s' and user '%s': %s",
            course_id, user.id, unicode(exc))
        raise self.retry(exc=exc)
示例#30
0
def user_organization_protection_status(user, course_key):
    """
    Returns the organization protection status of the user related to this course
    If the user is in the Masters track of the course, we return the protected status.
    If the user is a staff of the course, we return the protection_exempt status
    else, we return the unprotected status
    """
    if has_course_staff_privileges(user, course_key):
        return OrganizationProtectionStatus.protection_exempt
    enrollment = CourseEnrollment.get_enrollment(user, course_key)
    if enrollment and enrollment.is_active:
        if enrollment.mode in ORGANIZATION_PROTECTED_MODES:
            return OrganizationProtectionStatus.protected
        else:
            return OrganizationProtectionStatus.unprotected
    else:
        raise ValueError(
            'Cannot check the org_protection status on a student [%s] not enrolled in course [%s]',
            user.id, course_key)
示例#31
0
 def register_alerts(self, request, course):
     """
     Registers an alert close to the certificate delivery date.
     """
     is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id)
     if not is_enrolled or not self.is_enabled or course.end > self.current_time:
         return
     if self.date > self.current_time:
         CourseHomeMessages.register_info_message(
             request,
             Text(
                 _('If you have earned a certificate, you will be able to access it {time_remaining_string}'
                   ' from now. You will also be able to view your certificates on your {dashboard_link}.'
                   )).format(
                       time_remaining_string=self.time_remaining_string,
                       dashboard_link=HTML('<a href="{dashboard_url}</a>').
                       format(dashboard_url=reverse('dashboard'))),
             title=Text(
                 _('We are working on generating course certificates.')))
示例#32
0
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.
    """

    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)
示例#33
0
    def get_verification_context(request, course):
        enrollment = CourseEnrollment.get_enrollment(request.user, course.id)
        show_course_sock = verified_upgrade_link_is_valid(enrollment)
        if show_course_sock:
            upgrade_url = verified_upgrade_deadline_link(request.user,
                                                         course=course)
            course_price, _ = format_strikeout_price(request.user, course)
        else:
            upgrade_url = ''
            course_price = ''

        context = {
            'show_course_sock': show_course_sock,
            'course_price': course_price,
            'course_id': course.id,
            'upgrade_url': upgrade_url,
        }

        return context
示例#34
0
def get_expiration_banner_text(user, course, language='en'):
    """
    Get text for banner that messages user course expiration date
    for different tests that depend on it.
    """
    expiration_date = now() + timedelta(weeks=4)
    upgrade_link = verified_upgrade_deadline_link(user=user, course=course)
    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    upgrade_deadline = enrollment.upgrade_deadline
    if upgrade_deadline is None or now() < upgrade_deadline:
        upgrade_deadline = enrollment.course_upgrade_deadline

    date_string = u'<span class="localized-datetime" data-format="shortDate" \
        data-datetime="{formatted_date}" data-language="{language}">{formatted_date_localized}</span>'
    formatted_expiration_date = date_string.format(
        language=language,
        formatted_date=expiration_date.strftime("%Y-%m-%d"),
        formatted_date_localized=strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR)
    )
    if upgrade_deadline:
        formatted_upgrade_deadline = date_string.format(
            language=language,
            formatted_date=upgrade_deadline.strftime("%Y-%m-%d"),
            formatted_date_localized=strftime_localized(upgrade_deadline, EXPIRATION_DATE_FORMAT_STR)
        )

        bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\
                     You lose all access to this course, including your progress, on {expiration_date}.\
                     <br>Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists\
                     on the site. <a href="{upgrade_link}">Upgrade now<span class="sr-only"> to retain access past\
                     {expiration_date}</span></a>'.format(
            expiration_date=formatted_expiration_date,
            upgrade_link=upgrade_link,
            upgrade_deadline=formatted_upgrade_deadline
        )
    else:
        bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\
                     You lose all access to this course, including your progress, on {expiration_date}.\
                     '.format(
            expiration_date=formatted_expiration_date
        )
    return bannerText
示例#35
0
def get_expiration_banner_text(user, course, language='en'):
    """
    Get text for banner that messages user course expiration date
    for different tests that depend on it.
    """
    expiration_date = now() + timedelta(weeks=4)
    upgrade_link = verified_upgrade_deadline_link(user=user, course=course)
    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    upgrade_deadline = enrollment.upgrade_deadline
    if upgrade_deadline is None or now() < upgrade_deadline:
        upgrade_deadline = enrollment.course_upgrade_deadline

    date_string = u'<span class="localized-datetime" data-format="shortDate" \
        data-datetime="{formatted_date}" data-language="{language}">{formatted_date_localized}</span>'
    formatted_expiration_date = date_string.format(
        language=language,
        formatted_date=expiration_date.strftime("%Y-%m-%d"),
        formatted_date_localized=strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR)
    )
    if upgrade_deadline:
        formatted_upgrade_deadline = date_string.format(
            language=language,
            formatted_date=upgrade_deadline.strftime("%Y-%m-%d"),
            formatted_date_localized=strftime_localized(upgrade_deadline, EXPIRATION_DATE_FORMAT_STR)
        )

        bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\
                     You lose all access to this course, including your progress, on {expiration_date}.\
                     <br>Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists\
                     on the site. <a href="{upgrade_link}">Upgrade now<span class="sr-only"> to retain access past\
                     {expiration_date}</span></a>'.format(
            expiration_date=formatted_expiration_date,
            upgrade_link=upgrade_link,
            upgrade_deadline=formatted_upgrade_deadline
        )
    else:
        bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\
                     You lose all access to this course, including your progress, on {expiration_date}.\
                     '.format(
            expiration_date=formatted_expiration_date
        )
    return bannerText
示例#36
0
def get_expiration_banner_text(user, course):
    """
    Get text for banner that messages user course expiration date
    for different tests that depend on it.
    """
    expiration_date = now() + timedelta(weeks=4)
    upgrade_link = verified_upgrade_deadline_link(user=user, course=course)
    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    upgrade_deadline = enrollment.upgrade_deadline
    if upgrade_deadline is None or now() < upgrade_deadline:
        upgrade_deadline = enrollment.course_upgrade_deadline

    language = get_language()
    language_is_es = language and language.split('-')[0].lower() == 'es'
    if language_is_es:
        formatted_expiration_date = strftime_localized(
            expiration_date, '%-d de %b. de %Y').lower()
    else:
        formatted_expiration_date = strftime_localized(expiration_date,
                                                       '%b. %-d, %Y')

    if upgrade_deadline:
        if language_is_es:
            formatted_upgrade_deadline = strftime_localized(
                upgrade_deadline, '%-d de %b. de %Y').lower()
        else:
            formatted_upgrade_deadline = strftime_localized(
                upgrade_deadline, '%b. %-d, %Y')

        bannerText = '<strong>Audit Access Expires {expiration_date}</strong><br>\
                     You lose all access to this course, including your progress, on {expiration_date}.\
                     <br>Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists\
                     on the site. <a href="{upgrade_link}">Upgrade now<span class="sr-only"> to retain access past\
                     {expiration_date}</span></a>'.format(
            expiration_date=formatted_expiration_date,
            upgrade_link=upgrade_link,
            upgrade_deadline=formatted_upgrade_deadline)
    else:
        bannerText = '<strong>Audit Access Expires {expiration_date}</strong><br>\
                     You lose all access to this course, including your progress, on {expiration_date}.\
                     '.format(expiration_date=formatted_expiration_date)
    return bannerText
示例#37
0
 def __init__(self, course_key, request, username=''):
     self.overview = course_detail(
         request,
         username or request.user.username,
         course_key,
     )
     self.original_user_is_staff = has_access(request.user, 'staff',
                                              self.overview).has_access
     self.course_key = course_key
     self.course_masquerade, self.effective_user = setup_masquerade(
         request,
         course_key,
         staff_access=self.original_user_is_staff,
     )
     self.is_staff = has_access(self.effective_user, 'staff',
                                self.overview).has_access
     self.enrollment_object = CourseEnrollment.get_enrollment(
         self.effective_user,
         self.course_key,
         select_related=['celebration'])
示例#38
0
    def get_verification_context(request, course):
        enrollment = CourseEnrollment.get_enrollment(request.user, course.id)
        show_course_sock = verified_upgrade_link_is_valid(
            enrollment) and get_language() == 'en'
        if show_course_sock:
            upgrade_url = verified_upgrade_deadline_link(request.user,
                                                         course=course)
            course_price = get_cosmetic_verified_display_price(course)
        else:
            upgrade_url = ''
            course_price = ''

        context = {
            'show_course_sock': show_course_sock,
            'course_price': course_price,
            'course_id': course.id,
            'upgrade_url': upgrade_url,
        }

        return context
示例#39
0
def _get_zendesk_custom_field_context(request):
    """
    Construct a dictionary of data that can be stored in Zendesk custom fields.
    """
    context = {}

    course_id = request.POST.get("course_id")
    if not course_id:
        return context

    context["course_id"] = course_id
    if not request.user.is_authenticated():
        return context

    enrollment = CourseEnrollment.get_enrollment(
        request.user, CourseKey.from_string(course_id))
    if enrollment and enrollment.is_active:
        context["enrollment_mode"] = enrollment.mode

    return context
示例#40
0
    def handle(self, *args, **options):
        csv_path = options['csv_path']
        with open(csv_path) as csvfile:
            reader = unicodecsv.DictReader(csvfile)
            for row in reader:
                username = row['username']
                email = row['email']
                course_key = row['course_id']
                try:
                    user = User.objects.get(
                        Q(username=username) | Q(email=email))
                except ObjectDoesNotExist:
                    user = None
                    msg = 'User with username {} or email {} does not exist'.format(
                        username, email)
                    logger.warning(msg)

                try:
                    course_id = CourseKey.from_string(course_key)
                except InvalidKeyError:
                    course_id = None
                    msg = 'Invalid course id {course_id}, skipping un-enrollement for {username}, {email}'.format(
                        **row)
                    logger.warning(msg)

                if user and course_id:
                    enrollment = CourseEnrollment.get_enrollment(
                        user, course_id)
                    if not enrollment:
                        msg = 'Enrollment for the user {} in course {} does not exist!'.format(
                            username, course_key)
                        logger.info(msg)
                    else:
                        try:
                            CourseEnrollment.unenroll(user,
                                                      course_id,
                                                      skip_refund=True)
                        except Exception as err:
                            msg = 'Error un-enrolling User {} from course {}: '.format(
                                username, course_key, err)
                            logger.error(msg, exc_info=True)
示例#41
0
def get_user_course_expiration_date(user, course):
    """
    Return expiration date for given user course pair.
    Return None if the course does not expire.

    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.
    """
    access_duration = get_user_course_duration(user, course)
    if access_duration is None:
        return None

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

    try:
        # Content availability date is equivalent to max(enrollment date, course start date)
        # for most people. Using the schedule date will provide flexibility to deal with
        # more complex business rules in the future.
        content_availability_date = enrollment.schedule.start_date
        # We have anecdotally observed a case where the schedule.start_date was
        # equal to the course start, but should have been equal to the enrollment start
        # https://openedx.atlassian.net/browse/PROD-58
        # This section is meant to address that case
        if enrollment.created and course.start:
            if (content_availability_date.date() == course.start.date()
                    and course.start < enrollment.created < timezone.now()):
                content_availability_date = enrollment.created
            # If course teams change the course start date, set the content_availability_date
            # to max of enrollment or course start date
            elif (content_availability_date.date() < course.start.date()
                  and content_availability_date.date() <
                  enrollment.created.date()):
                content_availability_date = max(enrollment.created,
                                                course.start)
    except CourseEnrollment.schedule.RelatedObjectDoesNotExist:
        content_availability_date = max(enrollment.created, course.start)

    return content_availability_date + access_duration
示例#42
0
    def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None):
        """
        Return whether Course Duration Limits are enabled for this enrollment.

        Course Duration Limits are enabled for an enrollment if they are enabled for
        the course being enrolled in (either specifically, or via a containing context,
        such as the org, site, or globally), and if the configuration is specified to be
        ``enabled_as_of`` before the enrollment was created.

        Only one of enrollment and (user, course_key) may be specified at a time.

        Arguments:
            enrollment: The enrollment being queried.
            user: The user being queried.
            course_key: The CourseKey of the course being queried.
        """
        if CONTENT_TYPE_GATING_FLAG.is_enabled():
            return True

        if enrollment is not None and (user is not None or course_key is not None):
            raise ValueError('Specify enrollment or user/course_key, but not both')

        if enrollment is None and (user is None or course_key is None):
            raise ValueError('Both user and course_key must be specified if no enrollment is provided')

        if enrollment is None and user is None and course_key is None:
            raise ValueError('At least one of enrollment or user and course_key must be specified')

        if course_key is None:
            course_key = enrollment.course_id

        if enrollment is None:
            enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # enrollment might be None if the user isn't enrolled. In that case,
        # return enablement as if the user enrolled today
        if enrollment is None:
            return cls.enabled_for_course(course_key=course_key, target_date=datetime.utcnow().date())
        else:
            current_config = cls.current(course_key=enrollment.course_id)
            return current_config.enabled_as_of_date(target_date=enrollment.created.date())
示例#43
0
    def is_enabled(cls, request, course_key):
        """
        Show this link for active courses where financial assistance is available, unless upgrade deadline has passed
        """
        now = datetime.datetime.now(pytz.UTC)
        feature_flags = None
        try:
            course_overview = CourseOverview.objects.get(id=course_key)
        except CourseOverview.DoesNotExist:
            course_overview = None

        # hide link if there's no ENABLE_FINANCIAL_ASSISTANCE_FORM setting (ex: Edge) or if it's False
        subset_name = 'FEATURES'
        feature_flags = getattr(settings, subset_name)
        if feature_flags is None or not feature_flags.get(
                'ENABLE_FINANCIAL_ASSISTANCE_FORM'):
            return False

        # hide link for archived courses
        if course_overview is not None and course_overview.end_date is not None and now > course_overview.end_date:
            return False

        # hide link if not logged in or user not enrolled in the course
        if not request.user or not CourseEnrollment.is_enrolled(
                request.user, course_key):
            return False

        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)

        # hide if we're no longer in an upsell mode (already upgraded)
        if enrollment.mode not in CourseMode.UPSELL_TO_VERIFIED_MODES:
            return False

        # hide if there's no course_upgrade_deadline, or one with a value in the past
        if enrollment.course_upgrade_deadline:
            if now > enrollment.course_upgrade_deadline:
                return False
        else:
            return False

        return bool(course_overview.eligible_for_financial_aid)
示例#44
0
def get_user_course_expiration_date(user, course):
    """
    Return expiration date for given user course pair.
    Return None if the course does not expire.

    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.
    """
    access_duration = MIN_DURATION

    verified_mode = CourseMode.verified_mode_for_course(course=course,
                                                        include_expired=True)

    if not verified_mode:
        return None

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

    try:
        # Content availability date is equivalent to max(enrollment date, course start date)
        # for most people. Using the schedule date will provide flexibility to deal with
        # more complex business rules in the future.
        content_availability_date = enrollment.schedule.start
    except CourseEnrollment.schedule.RelatedObjectDoesNotExist:
        content_availability_date = max(enrollment.created, course.start)

    # The user course expiration date is the content availability date
    # plus the weeks_to_complete field from course-discovery.
    discovery_course_details = get_course_run_details(course.id,
                                                      ['weeks_to_complete'])
    expected_weeks = discovery_course_details.get('weeks_to_complete')
    if expected_weeks:
        access_duration = timedelta(weeks=expected_weeks)

    # Course access duration is bounded by the min and max duration.
    access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

    return content_availability_date + access_duration
示例#45
0
def postpay_callback(request):
    """
    Receives the POST-back from processor.
    Mainly this calls the processor-specific code to check if the payment was accepted, and to record the order
    if it was, and to generate an error page.
    If successful this function should have the side effect of changing the "cart" into a full "order" in the DB.
    The cart can then render a success page which links to receipt pages.
    If unsuccessful the order will be left untouched and HTML messages giving more detailed error info will be
    returned.
    """
    params = request.POST.dict()
    result = process_postpay_callback(params)

    if result["success"]:
        # See if this payment occurred as part of the verification flow process
        # If so, send the user back into the flow so they have the option
        # to continue with verification.

        # Only orders where order_items.count() == 1 might be attempting to upgrade
        attempting_upgrade = request.session.get("attempting_upgrade", False)
        if attempting_upgrade:
            if result["order"].has_items(CertificateItem):
                course_id = result["order"].orderitem_set.all().select_subclasses("certificateitem")[0].course_id
                if course_id:
                    course_enrollment = CourseEnrollment.get_enrollment(request.user, course_id)
                    if course_enrollment:
                        course_enrollment.emit_event(EVENT_NAME_USER_UPGRADED)

            request.session["attempting_upgrade"] = False

        verify_flow_redirect = _get_verify_flow_redirect(result["order"])
        if verify_flow_redirect is not None:
            return verify_flow_redirect

        # Otherwise, send the user to the receipt page
        return HttpResponseRedirect(reverse("shoppingcart.views.show_receipt", args=[result["order"].id]))
    else:
        request.session["attempting_upgrade"] = False
        return render_to_response(
            "shoppingcart/error.html", {"order": result["order"], "error_html": result["error_html"]}
        )
示例#46
0
 def __init__(self, course_key, request, username=''):
     self.overview = course_detail(
         request,
         username or request.user.username,
         course_key,
     )
     self.effective_user = self.overview.effective_user
     # We need to memoize `is_staff` _before_ we configure masquerade.
     self.is_staff = has_access(self.effective_user, 'staff',
                                self.overview).has_access
     self.course_key = course_key
     self.enrollment_object = CourseEnrollment.get_enrollment(
         self.effective_user,
         self.course_key,
         select_related=['celebration'])
     course_masquerade, _user = setup_masquerade(
         request,
         course_key,
         staff_access=self.is_staff,
     )
     self.course_masquerade = course_masquerade
示例#47
0
def postpay_callback(request):
    """
    Receives the POST-back from processor.
    Mainly this calls the processor-specific code to check if the payment was accepted, and to record the order
    if it was, and to generate an error page.
    If successful this function should have the side effect of changing the "cart" into a full "order" in the DB.
    The cart can then render a success page which links to receipt pages.
    If unsuccessful the order will be left untouched and HTML messages giving more detailed error info will be
    returned.
    """
    params = request.POST.dict()
    result = process_postpay_callback(params)

    if result['success']:
        # See if this payment occurred as part of the verification flow process
        # If so, send the user back into the flow so they have the option
        # to continue with verification.

        # Only orders where order_items.count() == 1 might be attempting to upgrade
        attempting_upgrade = request.session.get('attempting_upgrade', False)
        if attempting_upgrade:
            if result['order'].has_items(CertificateItem):
                course_id = result['order'].orderitem_set.all().select_subclasses("certificateitem")[0].course_id
                if course_id:
                    course_enrollment = CourseEnrollment.get_enrollment(request.user, course_id)
                    if course_enrollment:
                        course_enrollment.emit_event(EVENT_NAME_USER_UPGRADED)

            request.session['attempting_upgrade'] = False

        verify_flow_redirect = _get_verify_flow_redirect(result['order'])
        if verify_flow_redirect is not None:
            return verify_flow_redirect

        # Otherwise, send the user to the receipt page
        return HttpResponseRedirect(reverse('shoppingcart.views.show_receipt', args=[result['order'].id]))
    else:
        request.session['attempting_upgrade'] = False
        return render_to_response('shoppingcart/error.html', {'order': result['order'],
                                                              'error_html': result['error_html']})
示例#48
0
    def _enroll_entitlement(self, entitlement, course_run_key, user):
        """
        Internal method to handle the details of enrolling a User in a Course Run.

        Returns a response object is there is an error or exception, None otherwise
        """
        try:
            enrollment = CourseEnrollment.enroll(
                user=user,
                course_key=course_run_key,
                mode=entitlement.mode,
                check_access=True
            )
        except AlreadyEnrolledError:
            enrollment = CourseEnrollment.get_enrollment(user, course_run_key)
            if enrollment.mode == entitlement.mode:
                entitlement.set_enrollment(enrollment)
            elif enrollment.mode not in [mode.slug for mode in CourseMode.paid_modes_for_course(course_run_key)]:
                enrollment.update_enrollment(mode=entitlement.mode)
                entitlement.set_enrollment(enrollment)
            # Else the User is already enrolled in another paid Mode and we should
            # not do anything else related to Entitlements.
        except CourseEnrollmentException:
            message = (
                'Course Entitlement Enroll for {username} failed for course: {course_id}, '
                'mode: {mode}, and entitlement: {entitlement}'
            ).format(
                username=user.username,
                course_id=course_run_key,
                mode=entitlement.mode,
                entitlement=entitlement.uuid
            )
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={'message': message}
            )

        entitlement.set_enrollment(enrollment)
        return None
示例#49
0
def get_expiration_banner_text(user, course):
    """
    Get text for banner that messages user course expiration date
    for different tests that depend on it.
    """
    expiration_date = (now() + timedelta(weeks=4)).strftime('%b. %-d, %Y')
    upgrade_link = verified_upgrade_deadline_link(user=user, course=course)
    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    upgrade_deadline = enrollment.upgrade_deadline
    if upgrade_deadline is None:
        return
    if now() < upgrade_deadline:
        upgrade_deadline = enrollment.course_upgrade_deadline
    bannerText = '<strong>Audit Access Expires {expiration_date}</strong><br>\
                 You lose all access to this course, including your progress, on {expiration_date}.<br>\
                 Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists on the site.\
                  <a href="{upgrade_link}">Upgrade now<span class="sr-only"> to retain access past {expiration_date}\
                 </span></a>'.format(
        expiration_date=expiration_date,
        upgrade_link=upgrade_link,
        upgrade_deadline=upgrade_deadline.strftime('%b. %-d, %Y'))
    return bannerText
示例#50
0
    def _enroll_entitlement(self, entitlement, course_run_key, user):
        """
        Internal method to handle the details of enrolling a User in a Course Run.

        Returns a response object is there is an error or exception, None otherwise
        """
        try:
            enrollment = CourseEnrollment.enroll(
                user=user,
                course_key=course_run_key,
                mode=entitlement.mode,
                check_access=True
            )
        except AlreadyEnrolledError:
            enrollment = CourseEnrollment.get_enrollment(user, course_run_key)
            if enrollment.mode == entitlement.mode:
                entitlement.set_enrollment(enrollment)
            elif enrollment.mode not in [mode.slug for mode in CourseMode.paid_modes_for_course(course_run_key)]:
                enrollment.update_enrollment(mode=entitlement.mode)
                entitlement.set_enrollment(enrollment)
            # Else the User is already enrolled in another paid Mode and we should
            # not do anything else related to Entitlements.
        except CourseEnrollmentException:
            message = (
                'Course Entitlement Enroll for {username} failed for course: {course_id}, '
                'mode: {mode}, and entitlement: {entitlement}'
            ).format(
                username=user.username,
                course_id=course_run_key,
                mode=entitlement.mode,
                entitlement=entitlement.uuid
            )
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={'message': message}
            )

        entitlement.set_enrollment(enrollment)
        return None
示例#51
0
    def _is_enrollment_inside_date_bounds(self, experiment_values, user, course_key):
        """ Returns True if the user's enrollment (if any) is valid for the configured experiment date range """
        from student.models import CourseEnrollment

        enrollment_start = experiment_values.get('enrollment_start')
        enrollment_end = experiment_values.get('enrollment_end')
        if not enrollment_start and not enrollment_end:
            return True  # early exit just to avoid any further lookups

        now = datetime.datetime.now(pytz.utc)
        enrollment = CourseEnrollment.get_enrollment(user, course_key)

        # If the user isn't enrolled, act like they would enroll right now (this keeps the pre-enroll and post-enroll
        # experiences the same, if they decide to enroll right now)
        enrollment_creation_date = enrollment.created if enrollment else now

        # Enrollment must be after any enrollment_start date, if specified
        if enrollment_start:
            try:
                start_date = dateutil.parser.parse(enrollment_start).replace(tzinfo=pytz.UTC)
            except ValueError:
                log.exception('Could not parse enrollment start date for experiment %d', self.experiment_id)
                return False
            if enrollment_creation_date < start_date:
                return False

        # Enrollment must be before any enrollment_end date, if specified
        if enrollment_end:
            try:
                end_date = dateutil.parser.parse(enrollment_end).replace(tzinfo=pytz.UTC)
            except ValueError:
                log.exception('Could not parse enrollment end date for experiment %d', self.experiment_id)
                return False
            if enrollment_creation_date >= end_date:
                return False

        # All good! Either because the key was not set or because the enrollment was valid
        return True
示例#52
0
def check_and_get_upgrade_link_and_date(user, enrollment=None, course=None):
    """
    For an authenticated user, return a link to allow them to upgrade
    in the specified course.

    Returns the upgrade link and upgrade deadline for a user in a given course given
    that the user is within the window to upgrade defined by our dynamic pacing feature;
    otherwise, returns None for both the link and date.
    """
    if enrollment is None and course is None:
        logger.warn(u'Must specify either an enrollment or a course')
        return (None, None)

    if enrollment:
        if course is None:
            course = enrollment.course
        elif enrollment.course_id != course.id:
            logger.warn(u'{} refers to a different course than {} which was supplied'.format(
                enrollment, course
            ))
            return (None, None)

        if enrollment.user_id != user.id:
            logger.warn(u'{} refers to a different user than {} which was supplied'.format(
                enrollment, user
            ))
            return (None, None)

    if enrollment is None:
        enrollment = CourseEnrollment.get_enrollment(user, course.id)

    if user.is_authenticated and verified_upgrade_link_is_valid(enrollment):
        return (
            verified_upgrade_deadline_link(user, course),
            enrollment.upgrade_deadline
        )

    return (None, None)
示例#53
0
def get_user_course_expiration_date(user, course):
    """
    Return expiration date for given user course pair.
    Return None if the course does not expire.

    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.
    """
    access_duration = get_user_course_duration(user, course)
    if access_duration is None:
        return None

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

    # We reset schedule.start in order to change a user's computed deadlines.
    # But their expiration date shouldn't change when we adjust their schedule (they don't
    # get additional time), so we need to based the expiration date on a fixed start date.
    content_availability_date = max(enrollment.created, course.start)

    return content_availability_date + access_duration
示例#54
0
def get_enrollment_attributes(user_id, course_id):
    """Retrieve enrollment attributes for given user for provided course.

    Args:
        user_id: The User to get enrollment attributes for
        course_id (str): The Course to get enrollment attributes for.

    Example:
        >>>get_enrollment_attributes("Bob", "course-v1-edX-DemoX-1T2015")
        [
            {
                "namespace": "credit",
                "name": "provider_id",
                "value": "hogwarts",
            },
        ]

    Returns: list
    """
    course_key = CourseKey.from_string(course_id)
    user = _get_user(user_id)
    enrollment = CourseEnrollment.get_enrollment(user, course_key)
    return CourseEnrollmentAttribute.get_enrollment_attributes(enrollment)
示例#55
0
def get_enrollment_attributes(user_id, course_id):
    """Retrieve enrollment attributes for given user for provided course.

    Args:
        user_id: The User to get enrollment attributes for
        course_id (str): The Course to get enrollment attributes for.

    Example:
        >>>get_enrollment_attributes("Bob", "course-v1-edX-DemoX-1T2015")
        [
            {
                "namespace": "credit",
                "name": "provider_id",
                "value": "hogwarts",
            },
        ]

    Returns: list
    """
    course_key = CourseKey.from_string(course_id)
    user = _get_user(user_id)
    enrollment = CourseEnrollment.get_enrollment(user, course_key)
    return CourseEnrollmentAttribute.get_enrollment_attributes(enrollment)
示例#56
0
def calculate_course_home(course_id, user):
    """
    Calculate course home.
    """

    course_key = CourseKey.from_string(course_id)
    user_is_enrolled = CourseEnrollment.is_enrolled(user, course_key)

    if not user_is_enrolled:
        return None

    course = get_course_by_id(course_key)
    custom_course_settings = course.other_course_settings
    course_entry_points = custom_course_settings.get("course_entry_points", [])
    enrollment = CourseEnrollment.get_enrollment(user, course_key)
    custom_entry_point = _calculate_entry_point(course, course_id, course_key, course_entry_points, user, enrollment)

    if custom_entry_point:
        return custom_entry_point

    if is_external_course(course_id):
        return custom_course_settings.get("external_course_target")

    return None
示例#57
0
    def is_enabled(cls, request, course_key):
        """
        Show this tool to all learners who are eligible to upgrade.
        """
        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
        if enrollment is None:
            return False

        if enrollment.dynamic_upgrade_deadline is None:
            return False

        if not enrollment.is_active:
            return False

        if enrollment.mode not in CourseMode.UPSELL_TO_VERIFIED_MODES:
            return False

        if enrollment.course_upgrade_deadline is None:
            return False

        if datetime.datetime.now(pytz.UTC) >= enrollment.course_upgrade_deadline:
            return False

        return True
 def register_alerts(self, request, course):
     """
     Registers an alert close to the certificate delivery date.
     """
     is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id)
     if not is_enrolled or not self.is_enabled or course.end > self.current_time:
         return
     if self.date > self.current_time:
         CourseHomeMessages.register_info_message(
             request,
             Text(_(
                 u'If you have earned a certificate, you will be able to access it {time_remaining_string}'
                 u' from now. You will also be able to view your certificates on your {learner_profile_link}.'
             )).format(
                 time_remaining_string=self.time_remaining_string,
                 learner_profile_link=HTML(
                     u'<a href="{learner_profile_url}">{learner_profile_name}</a>'
                 ).format(
                     learner_profile_url=reverse('learner_profile', kwargs={'username': request.user.username}),
                     learner_profile_name=_('Learner Profile'),
                 ),
             ),
             title=Text(_('We are working on generating course certificates.'))
         )
def _get_zendesk_custom_field_context(request, **kwargs):
    """
    Construct a dictionary of data that can be stored in Zendesk custom fields.
    """
    context = {}

    course_id = request.POST.get("course_id")
    if not course_id:
        return context

    context["course_id"] = course_id
    if not request.user.is_authenticated():
        return context

    enrollment = CourseEnrollment.get_enrollment(request.user, CourseKey.from_string(course_id))
    if enrollment and enrollment.is_active:
        context["enrollment_mode"] = enrollment.mode

    enterprise_learner_data = kwargs.get('learner_data', None)
    if enterprise_learner_data:
        enterprise_customer_name = enterprise_learner_data[0]['enterprise_customer']['name']
        context["enterprise_customer_name"] = enterprise_customer_name

    return context
示例#60
0
    def post(self, request, course_key_string, *args, **kwargs):
        """
        Handle a POST request.
        """
        course_key = CourseKey.from_string(course_key_string)

        # Check if we're masquerading as someone else. If so, we should just ignore this request.
        _, user = setup_masquerade(
            request,
            course_key,
            staff_access=has_access(request.user, 'staff', course_key),
            reset_masquerade_data=True,
        )
        if user != request.user:
            return Response(status=202)  # "Accepted"

        data = dict(request.data)
        first_section = data.pop('first_section', None)
        if data:
            return Response(
                status=400)  # there were parameters we didn't recognize

        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
        if not enrollment:
            return Response(status=404)

        defaults = {}
        if first_section is not None:
            defaults['celebrate_first_section'] = first_section

        if defaults:
            _, created = CourseEnrollmentCelebration.objects.update_or_create(
                enrollment=enrollment, defaults=defaults)
            return Response(status=201 if created else 200)
        else:
            return Response(status=200)  # just silently allow it