def test_update_verification_deadline_left_alone(self):
        """
        When the course's verification deadline is set and an update request doesn't
        include it, we should take no action on it.
        """
        verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc)
        response, __ = self._get_update_response_and_expected_data(None, verification_deadline)
        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)

        verified_mode = CourseMode(
            mode_slug=u'verified',
            min_price=200,
            currency=u'USD',
            sku=u'ABC123',
            bulk_sku=u'BULK-ABC123',
            expiration_datetime=None
        )
        updated_data = self._serialize_course(self.course, [verified_mode], None)
        # don't include the verification_deadline key in the PUT request
        updated_data.pop('verification_deadline', None)

        response = self.client.put(self.path, json.dumps(updated_data), content_type=JSON_CONTENT_TYPE)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)
Exemple #2
0
    def test_deadline(self):
        """ Verify deadline is set to course end date by signal when changed. """
        deadline = datetime.now(tz=UTC) - timedelta(days=7)
        VerificationDeadline.set_deadline(self.course.id, deadline)

        _listen_for_course_publish('store', self.course.id)
        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), self.course.end)
Exemple #3
0
    def test_deadline(self):
        """ Verify deadline is set to course end date by signal when changed. """
        deadline = now() - timedelta(days=7)
        VerificationDeadline.set_deadline(self.course.id, deadline)

        _listen_for_course_publish('store', self.course.id)
        assert VerificationDeadline.deadline_for_course(
            self.course.id) == self.course.end
    def test_deadline(self):
        """ Verify deadline is set to course end date by signal when changed. """
        deadline = datetime.now(tz=UTC) - timedelta(days=7)
        VerificationDeadline.set_deadline(self.course.id, deadline)

        _listen_for_course_publish('store', self.course.id)
        self.assertEqual(
            VerificationDeadline.deadline_for_course(self.course.id),
            self.course.end)
    def test_update_remove_verification_deadline(self):
        """
        Verify that verification deadlines can be removed through the API.
        """
        verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc)
        response, __ = self._get_update_response_and_expected_data(None, verification_deadline)
        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)

        verified_mode = CourseMode(
            mode_slug=u"verified", min_price=200, currency=u"USD", sku=u"ABC123", expiration_datetime=None
        )
        updated_data = self._serialize_course(self.course, [verified_mode], None)
        updated_data["verification_deadline"] = None

        response = self.client.put(self.path, json.dumps(updated_data), content_type=JSON_CONTENT_TYPE)

        self.assertEqual(response.status_code, 200)
        self.assertIsNone(VerificationDeadline.deadline_for_course(self.course.id))
Exemple #6
0
    def test_update(self):
        """ Verify the view supports updating a course. """
        # Sanity check: Ensure no verification deadline is set
        self.assertIsNone(VerificationDeadline.deadline_for_course(self.course.id))

        # Generate the expected data
        verification_deadline = datetime(year=2020, month=12, day=31, tzinfo=pytz.utc)
        expiration_datetime = datetime.now(pytz.utc)
        response, expected = self._get_update_response_and_expected_data(expiration_datetime, verification_deadline)

        # Sanity check: The API should return HTTP status 200 for updates
        self.assertEqual(response.status_code, 200)

        # Verify the course and modes are returned as JSON
        actual = json.loads(response.content)
        self.assertEqual(actual, expected)

        # Verify the verification deadline is updated
        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)
Exemple #7
0
    def test_deadline_explicit(self):
        """ Verify deadline is unchanged by signal when explicitly set. """
        deadline = now() - timedelta(days=7)
        VerificationDeadline.set_deadline(self.course.id, deadline, is_explicit=True)

        _listen_for_course_publish('store', self.course.id)

        actual_deadline = VerificationDeadline.deadline_for_course(self.course.id)
        self.assertNotEqual(actual_deadline, self.course.end)
        self.assertEqual(actual_deadline, deadline)
Exemple #8
0
    def test_update_verification_deadline_without_expiring_modes(self):
        """ Verify verification deadline can be set if no course modes expire.

         This accounts for the verified professional mode, which requires verification but should never expire.
        """
        verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc)
        response, __ = self._get_update_response_and_expected_data(None, verification_deadline)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)
    def test_update(self):
        """ Verify the view supports updating a course. """
        # Sanity check: Ensure no verification deadline is set
        self.assertIsNone(VerificationDeadline.deadline_for_course(self.course.id))

        # Generate the expected data
        verification_deadline = datetime(year=2020, month=12, day=31, tzinfo=pytz.utc)
        expiration_datetime = datetime.now(pytz.utc)
        response, expected = self._get_update_response_and_expected_data(expiration_datetime, verification_deadline)

        # Sanity check: The API should return HTTP status 200 for updates
        self.assertEqual(response.status_code, 200)

        # Verify the course and modes are returned as JSON
        actual = json.loads(response.content)
        self.assertEqual(actual, expected)

        # Verify the verification deadline is updated
        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)
    def test_update_verification_deadline_without_expiring_modes(self):
        """ Verify verification deadline can be set if no course modes expire.

         This accounts for the verified professional mode, which requires verification but should never expire.
        """
        verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc)
        response, __ = self._get_update_response_and_expected_data(None, verification_deadline)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)
Exemple #11
0
    def test_deadline_explicit(self):
        """ Verify deadline is unchanged by signal when explicitly set. """
        deadline = datetime.now(tz=UTC) - timedelta(days=7)
        VerificationDeadline.set_deadline(self.course.id, deadline, is_explicit=True)

        _listen_for_course_publish('store', self.course.id)

        actual_deadline = VerificationDeadline.deadline_for_course(self.course.id)
        self.assertNotEqual(actual_deadline, self.course.end)
        self.assertEqual(actual_deadline, deadline)
    def _serialize_course(cls, course, modes=None, verification_deadline=None):
        """ Serializes a course to a Python dict. """
        modes = modes or []
        verification_deadline = verification_deadline or VerificationDeadline.deadline_for_course(course.id)

        return {
            u'id': unicode(course.id),
            u'name': unicode(course.display_name),
            u'verification_deadline': cls._serialize_datetime(verification_deadline),
            u'modes': [cls._serialize_course_mode(mode) for mode in modes]
        }
Exemple #13
0
    def test_update(self):
        """ Verify the view supports updating a course. """
        # Sanity check: Ensure no verification deadline is set
        assert VerificationDeadline.deadline_for_course(self.course.id) is None

        # Generate the expected data
        now = datetime.now(pytz.utc)
        verification_deadline = now + timedelta(days=1)
        expiration_datetime = now
        response, expected = self._get_update_response_and_expected_data(expiration_datetime, verification_deadline)

        # Sanity check: The API should return HTTP status 200 for updates
        assert response.status_code == 200

        # Verify the course and modes are returned as JSON
        actual = json.loads(response.content.decode('utf-8'))
        assert actual == expected

        # Verify the verification deadline is updated
        assert VerificationDeadline.deadline_for_course(self.course.id) == verification_deadline
Exemple #14
0
    def _serialize_course(cls, course, modes=None, verification_deadline=None):
        """ Serializes a course to a Python dict. """
        modes = modes or []
        verification_deadline = verification_deadline or VerificationDeadline.deadline_for_course(course.id)

        return {
            u'id': six.text_type(course.id),
            u'name': six.text_type(course.display_name),
            u'verification_deadline': cls._serialize_datetime(verification_deadline),
            u'modes': [cls._serialize_course_mode(mode) for mode in modes]
        }
    def test_disable_verification_deadline(self):
        # Configure a verification deadline for the course
        VerificationDeadline.set_deadline(self.course.id, self.VERIFICATION_DEADLINE)

        # Create the course mode Django admin form
        form = self._admin_form("verified", upgrade_deadline=self.UPGRADE_DEADLINE)

        # Use the form to disable the verification deadline
        self._set_form_verification_deadline(form, None)
        form.save()

        # Check that the deadline was disabled
        self.assertIs(VerificationDeadline.deadline_for_course(self.course.id), None)
Exemple #16
0
    def test_disable_verification_deadline(self):
        # Configure a verification deadline for the course
        VerificationDeadline.set_deadline(self.course.id, self.VERIFICATION_DEADLINE)

        # Create the course mode Django admin form
        form = self._admin_form("verified", upgrade_deadline=self.UPGRADE_DEADLINE)

        # Use the form to disable the verification deadline
        self._set_form_verification_deadline(form, None)
        form.save()

        # Check that the deadline was disabled
        self.assertIs(VerificationDeadline.deadline_for_course(self.course.id), None)
Exemple #17
0
    def test_update_remove_verification_deadline(self):
        """
        Verify that verification deadlines can be removed through the API.
        """
        verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc)
        response, __ = self._get_update_response_and_expected_data(None, verification_deadline)
        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)

        verified_mode = CourseMode(
            mode_slug=u'verified',
            min_price=200,
            currency=u'USD',
            sku=u'ABC123',
            bulk_sku=u'BULK-ABC123',
            expiration_datetime=None
        )
        updated_data = self._serialize_course(self.course, [verified_mode], None)
        updated_data['verification_deadline'] = None

        response = self.client.put(self.path, json.dumps(updated_data), content_type=JSON_CONTENT_TYPE)

        self.assertEqual(response.status_code, 200)
        self.assertIsNone(VerificationDeadline.deadline_for_course(self.course.id))
Exemple #18
0
    def get(cls, course_id):
        """ Retrieve a single course. """
        try:
            course_id = CourseKey.from_string(str(course_id))
        except InvalidKeyError:
            log.debug('[%s] is not a valid course key.', course_id)
            raise ValueError  # lint-amnesty, pylint: disable=raise-missing-from

        course_modes = CourseMode.objects.filter(course_id=course_id)

        if course_modes:
            verification_deadline = VerificationDeadline.deadline_for_course(course_id)
            return cls(course_id, list(course_modes), verification_deadline=verification_deadline)

        return None
Exemple #19
0
    def get(cls, course_id):
        """ Retrieve a single course. """
        try:
            course_id = CourseKey.from_string(unicode(course_id))
        except InvalidKeyError:
            log.debug('[%s] is not a valid course key.', course_id)
            raise ValueError

        course_modes = CourseMode.objects.filter(course_id=course_id)

        if course_modes:
            verification_deadline = VerificationDeadline.deadline_for_course(course_id)
            return cls(course_id, list(course_modes), verification_deadline=verification_deadline)

        return None
    def get(cls, course_id):
        """ Retrieve a single course. """
        try:
            course_id = CourseKey.from_string(six.text_type(course_id))
        except InvalidKeyError:
            log.debug(u'[%s] is not a valid course key.', course_id)
            raise ValueError

        course_modes = CourseMode.objects.filter(course_id=course_id)

        if course_modes:
            verification_deadline = VerificationDeadline.deadline_for_course(course_id)
            return cls(course_id, list(course_modes), verification_deadline=verification_deadline)

        return None
Exemple #21
0
    def test_set_verification_deadline(self, course_mode):
        # Configure a verification deadline for the course
        VerificationDeadline.set_deadline(self.course.id, self.VERIFICATION_DEADLINE)

        # Create the course mode Django admin form
        form = self._admin_form(course_mode)

        # Update the verification deadline form data
        # We need to set the date and time fields separately, since they're
        # displayed as separate widgets in the form.
        new_deadline = (self.VERIFICATION_DEADLINE + timedelta(days=1)).replace(microsecond=0)
        self._set_form_verification_deadline(form, new_deadline)
        form.save()

        # Check that the deadline was updated
        updated_deadline = VerificationDeadline.deadline_for_course(self.course.id)
        self.assertEqual(updated_deadline, new_deadline)
    def test_set_verification_deadline(self, course_mode):
        # Configure a verification deadline for the course
        VerificationDeadline.set_deadline(self.course.id, self.VERIFICATION_DEADLINE)

        # Create the course mode Django admin form
        form = self._admin_form(course_mode)

        # Update the verification deadline form data
        # We need to set the date and time fields separately, since they're
        # displayed as separate widgets in the form.
        new_deadline = (self.VERIFICATION_DEADLINE + timedelta(days=1)).replace(microsecond=0)
        self._set_form_verification_deadline(form, new_deadline)
        form.save()

        # Check that the deadline was updated
        updated_deadline = VerificationDeadline.deadline_for_course(self.course.id)
        self.assertEqual(updated_deadline, new_deadline)
Exemple #23
0
    def include_verified_mode_info(enrollment_data, course_key):
        """
        Add information about the verified mode for the given
        `course_key`, if that course has a verified mode.

        Args:
          enrollment_data (dict): Dictionary representing a single enrollment.
          course_key (CourseKey): The course which this enrollment belongs to.

        Returns:
          None
        """
        course_modes = enrollment_data['course_modes']
        for mode in course_modes:
            if mode['slug'] == CourseMode.VERIFIED:
                enrollment_data['verified_price'] = mode['min_price']
                enrollment_data['verified_upgrade_deadline'] = mode['expiration_datetime']
                enrollment_data['verification_deadline'] = VerificationDeadline.deadline_for_course(course_key)
Exemple #24
0
    def include_verified_mode_info(enrollment_data, course_key):
        """
        Add information about the verified mode for the given
        `course_key`, if that course has a verified mode.

        Args:
          enrollment_data (dict): Dictionary representing a single enrollment.
          course_key (CourseKey): The course which this enrollment belongs to.

        Returns:
          None
        """
        course_modes = enrollment_data['course_modes']
        for mode in course_modes:
            if mode['slug'] == CourseMode.VERIFIED:
                enrollment_data['verified_price'] = mode['min_price']
                enrollment_data['verified_upgrade_deadline'] = mode['expiration_datetime']
                enrollment_data['verification_deadline'] = VerificationDeadline.deadline_for_course(course_key)
 def date(self):
     return VerificationDeadline.deadline_for_course(self.course_id)
Exemple #26
0
    def get(self,
            request,
            course_id,
            always_show_payment=False,
            current_step=None,
            message=FIRST_TIME_VERIFY_MSG):
        """
        Render the payment and verification flow.

        Arguments:
            request (HttpRequest): The request object.
            course_id (unicode): The ID of the course the user is trying
                to enroll in.

        Keyword Arguments:
            always_show_payment (bool): If True, show the payment steps
                even if the user has already paid.  This is useful
                for users returning to the flow after paying.
            current_step (string): The current step in the flow.
            message (string): The messaging to display.

        Returns:
            HttpResponse

        Raises:
            Http404: The course does not exist or does not
                have a verified mode.

        """
        # Parse the course key
        # The URL regex should guarantee that the key format is valid.
        course_key = CourseKey.from_string(course_id)
        course = modulestore().get_course(course_key)

        # Verify that the course exists
        if course is None:
            log.warn(u"Could not find course with ID %s.", course_id)
            raise Http404

        # Check whether the user has access to this course
        # based on country access rules.
        redirect_url = embargo_api.redirect_if_blocked(
            course_key,
            user=request.user,
            ip_address=get_ip(request),
            url=request.path)
        if redirect_url:
            return redirect(redirect_url)

        # If the verification deadline has passed
        # then show the user a message that he/she can't verify.
        #
        # We're making the assumptions (enforced in Django admin) that:
        #
        # 1) Only verified modes have verification deadlines.
        #
        # 2) If set, verification deadlines are always AFTER upgrade deadlines, because why would you
        #   let someone upgrade into a verified track if they can't complete verification?
        #
        verification_deadline = VerificationDeadline.deadline_for_course(
            course.id)
        response = self._response_if_deadline_passed(
            course, self.VERIFICATION_DEADLINE, verification_deadline)
        if response is not None:
            log.info(u"Verification deadline for '%s' has passed.", course.id)
            return response

        # Retrieve the relevant course mode for the payment/verification flow.
        #
        # WARNING: this is technical debt!  A much better way to do this would be to
        # separate out the payment flow and use the product SKU to figure out what
        # the user is trying to purchase.
        #
        # Nonetheless, for the time being we continue to make the really ugly assumption
        # that at some point there was a paid course mode we can query for the price.
        relevant_course_mode = self._get_paid_mode(course_key)

        # If we can find a relevant course mode, then log that we're entering the flow
        # Otherwise, this course does not support payment/verification, so respond with a 404.
        if relevant_course_mode is not None:
            if CourseMode.is_verified_mode(relevant_course_mode):
                log.info(
                    u"Entering payment and verification flow for user '%s', course '%s', with current step '%s'.",
                    request.user.id, course_id, current_step)
            else:
                log.info(
                    u"Entering payment flow for user '%s', course '%s', with current step '%s'",
                    request.user.id, course_id, current_step)
        else:
            # Otherwise, there has never been a verified/paid mode,
            # so return a page not found response.
            log.warn(
                u"No paid/verified course mode found for course '%s' for verification/payment flow request",
                course_id)
            raise Http404

        # If the user is trying to *pay* and the upgrade deadline has passed,
        # then they shouldn't be able to enter the flow.
        #
        # NOTE: This should match the availability dates used by the E-Commerce service
        # to determine whether a user can purchase a product.  The idea is that if the service
        # won't fulfill the order, we shouldn't even let the user get into the payment flow.
        #
        user_is_trying_to_pay = message in [
            self.FIRST_TIME_VERIFY_MSG, self.UPGRADE_MSG
        ]
        if user_is_trying_to_pay:
            upgrade_deadline = relevant_course_mode.expiration_datetime
            response = self._response_if_deadline_passed(
                course, self.UPGRADE_DEADLINE, upgrade_deadline)
            if response is not None:
                log.info(u"Upgrade deadline for '%s' has passed.", course.id)
                return response

        # Check whether the user has verified, paid, and enrolled.
        # A user is considered "paid" if he or she has an enrollment
        # with a paid course mode (such as "verified").
        # For this reason, every paid user is enrolled, but not
        # every enrolled user is paid.
        # If the course mode is not verified(i.e only paid) then already_verified is always True
        already_verified = (self._check_already_verified(
            request.user) if CourseMode.is_verified_mode(relevant_course_mode)
                            else True)
        already_paid, is_enrolled = self._check_enrollment(
            request.user, course_key)

        # Redirect the user to a more appropriate page if the
        # messaging won't make sense based on the user's
        # enrollment / payment / verification status.
        sku_to_use = relevant_course_mode.sku
        purchase_workflow = request.GET.get('purchase_workflow', 'single')
        if purchase_workflow == 'bulk' and relevant_course_mode.bulk_sku:
            sku_to_use = relevant_course_mode.bulk_sku
        redirect_response = self._redirect_if_necessary(
            message, already_verified, already_paid, is_enrolled, course_key,
            user_is_trying_to_pay, request.user, sku_to_use)
        if redirect_response is not None:
            return redirect_response

        display_steps = self._display_steps(always_show_payment,
                                            already_verified, already_paid,
                                            relevant_course_mode)

        # Override the actual value if account activation has been disabled
        # Also see the reference to this parameter in context dictionary further down
        user_is_active = self._get_user_active_status(request.user)
        requirements = self._requirements(display_steps, user_is_active)

        if current_step is None:
            current_step = display_steps[0]['name']

        # Allow the caller to skip the first page
        # This is useful if we want the user to be able to
        # use the "back" button to return to the previous step.
        # This parameter should only work for known skip-able steps
        if request.GET.get(
                'skip-first-step') and current_step in self.SKIP_STEPS:
            display_step_names = [step['name'] for step in display_steps]
            current_step_idx = display_step_names.index(current_step)
            if (current_step_idx + 1) < len(display_steps):
                current_step = display_steps[current_step_idx + 1]['name']

        courseware_url = ""
        if not course.start or course.start < datetime.datetime.today(
        ).replace(tzinfo=UTC):
            courseware_url = reverse('course_root',
                                     kwargs={'course_id': unicode(course_key)})

        full_name = (request.user.profile.name
                     if request.user.profile.name else "")

        # If the user set a contribution amount on another page,
        # use that amount to pre-fill the price selection form.
        contribution_amount = request.session.get('donation_for_course',
                                                  {}).get(
                                                      unicode(course_key), '')

        # Remember whether the user is upgrading
        # so we can fire an analytics event upon payment.
        request.session['attempting_upgrade'] = (message == self.UPGRADE_MSG)

        # Determine the photo verification status
        verification_good_until = self._verification_valid_until(request.user)

        # get available payment processors
        if relevant_course_mode.sku:
            # transaction will be conducted via ecommerce service
            processors = ecommerce_api_client(
                request.user).payment.processors.get()
        else:
            # transaction will be conducted using legacy shopping cart
            processors = [settings.CC_PROCESSOR_NAME]

        # Render the top-level page
        context = {
            'contribution_amount':
            contribution_amount,
            'course':
            course,
            'course_key':
            unicode(course_key),
            'checkpoint_location':
            request.GET.get('checkpoint'),
            'course_mode':
            relevant_course_mode,
            'courseware_url':
            courseware_url,
            'current_step':
            current_step,
            'disable_courseware_js':
            True,
            'display_steps':
            display_steps,
            'is_active':
            json.dumps(user_is_active),
            'user_email':
            request.user.email,
            'message_key':
            message,
            'platform_name':
            configuration_helpers.get_value('PLATFORM_NAME',
                                            settings.PLATFORM_NAME),
            'processors':
            processors,
            'requirements':
            requirements,
            'user_full_name':
            full_name,
            'verification_deadline':
            verification_deadline or "",
            'already_verified':
            already_verified,
            'verification_good_until':
            verification_good_until,
            'capture_sound':
            staticfiles_storage.url("audio/camera_capture.wav"),
            'nav_hidden':
            True,
            'is_ab_testing':
            'begin-flow' in request.path,
        }

        return render_to_response("verify_student/pay_and_verify.html",
                                  context)
Exemple #27
0
    def test_no_deadline(self):
        """ Verify the signal sets deadline to course end when no deadline exists."""
        _listen_for_course_publish('store', self.course.id)

        assert VerificationDeadline.deadline_for_course(
            self.course.id) == self.course.end
Exemple #28
0
    def get(
        self, request, course_id,
        always_show_payment=False,
        current_step=None,
        message=FIRST_TIME_VERIFY_MSG
    ):
        """
        Render the payment and verification flow.

        Arguments:
            request (HttpRequest): The request object.
            course_id (unicode): The ID of the course the user is trying
                to enroll in.

        Keyword Arguments:
            always_show_payment (bool): If True, show the payment steps
                even if the user has already paid.  This is useful
                for users returning to the flow after paying.
            current_step (string): The current step in the flow.
            message (string): The messaging to display.

        Returns:
            HttpResponse

        Raises:
            Http404: The course does not exist or does not
                have a verified mode.

        """
        # Parse the course key
        # The URL regex should guarantee that the key format is valid.
        course_key = CourseKey.from_string(course_id)
        course = modulestore().get_course(course_key)

        # Verify that the course exists
        if course is None:
            log.warn(u"Could not find course with ID %s.", course_id)
            raise Http404

        # Check whether the user has access to this course
        # based on country access rules.
        redirect_url = embargo_api.redirect_if_blocked(
            course_key,
            user=request.user,
            ip_address=get_ip(request),
            url=request.path
        )
        if redirect_url:
            return redirect(redirect_url)

        # If the verification deadline has passed
        # then show the user a message that he/she can't verify.
        #
        # We're making the assumptions (enforced in Django admin) that:
        #
        # 1) Only verified modes have verification deadlines.
        #
        # 2) If set, verification deadlines are always AFTER upgrade deadlines, because why would you
        #   let someone upgrade into a verified track if they can't complete verification?
        #
        verification_deadline = VerificationDeadline.deadline_for_course(course.id)
        response = self._response_if_deadline_passed(course, self.VERIFICATION_DEADLINE, verification_deadline)
        if response is not None:
            log.info(u"Verification deadline for '%s' has passed.", course.id)
            return response

        # Retrieve the relevant course mode for the payment/verification flow.
        #
        # WARNING: this is technical debt!  A much better way to do this would be to
        # separate out the payment flow and use the product SKU to figure out what
        # the user is trying to purchase.
        #
        # Nonetheless, for the time being we continue to make the really ugly assumption
        # that at some point there was a paid course mode we can query for the price.
        relevant_course_mode = self._get_paid_mode(course_key)

        # If we can find a relevant course mode, then log that we're entering the flow
        # Otherwise, this course does not support payment/verification, so respond with a 404.
        if relevant_course_mode is not None:
            if CourseMode.is_verified_mode(relevant_course_mode):
                log.info(
                    u"Entering payment and verification flow for user '%s', course '%s', with current step '%s'.",
                    request.user.id, course_id, current_step
                )
            else:
                log.info(
                    u"Entering payment flow for user '%s', course '%s', with current step '%s'",
                    request.user.id, course_id, current_step
                )
        else:
            # Otherwise, there has never been a verified/paid mode,
            # so return a page not found response.
            log.warn(
                u"No paid/verified course mode found for course '%s' for verification/payment flow request",
                course_id
            )
            raise Http404

        # If the user is trying to *pay* and the upgrade deadline has passed,
        # then they shouldn't be able to enter the flow.
        #
        # NOTE: This should match the availability dates used by the E-Commerce service
        # to determine whether a user can purchase a product.  The idea is that if the service
        # won't fulfill the order, we shouldn't even let the user get into the payment flow.
        #
        user_is_trying_to_pay = message in [self.FIRST_TIME_VERIFY_MSG, self.UPGRADE_MSG]
        if user_is_trying_to_pay:
            upgrade_deadline = relevant_course_mode.expiration_datetime
            response = self._response_if_deadline_passed(course, self.UPGRADE_DEADLINE, upgrade_deadline)
            if response is not None:
                log.info(u"Upgrade deadline for '%s' has passed.", course.id)
                return response

        # Check whether the user has verified, paid, and enrolled.
        # A user is considered "paid" if he or she has an enrollment
        # with a paid course mode (such as "verified").
        # For this reason, every paid user is enrolled, but not
        # every enrolled user is paid.
        # If the course mode is not verified(i.e only paid) then already_verified is always True
        already_verified = (
            self._check_already_verified(request.user)
            if CourseMode.is_verified_mode(relevant_course_mode)
            else True
        )
        already_paid, is_enrolled = self._check_enrollment(request.user, course_key)

        # Redirect the user to a more appropriate page if the
        # messaging won't make sense based on the user's
        # enrollment / payment / verification status.
        sku_to_use = relevant_course_mode.sku
        purchase_workflow = request.GET.get('purchase_workflow', 'single')
        if purchase_workflow == 'bulk' and relevant_course_mode.bulk_sku:
            sku_to_use = relevant_course_mode.bulk_sku
        redirect_response = self._redirect_if_necessary(
            message,
            already_verified,
            already_paid,
            is_enrolled,
            course_key,
            user_is_trying_to_pay,
            request.user,
            sku_to_use
        )
        if redirect_response is not None:
            return redirect_response

        display_steps = self._display_steps(
            always_show_payment,
            already_verified,
            already_paid,
            relevant_course_mode
        )

        # Override the actual value if account activation has been disabled
        # Also see the reference to this parameter in context dictionary further down
        user_is_active = self._get_user_active_status(request.user)
        requirements = self._requirements(display_steps, user_is_active)

        if current_step is None:
            current_step = display_steps[0]['name']

        # Allow the caller to skip the first page
        # This is useful if we want the user to be able to
        # use the "back" button to return to the previous step.
        # This parameter should only work for known skip-able steps
        if request.GET.get('skip-first-step') and current_step in self.SKIP_STEPS:
            display_step_names = [step['name'] for step in display_steps]
            current_step_idx = display_step_names.index(current_step)
            if (current_step_idx + 1) < len(display_steps):
                current_step = display_steps[current_step_idx + 1]['name']

        courseware_url = ""
        if not course.start or course.start < datetime.datetime.today().replace(tzinfo=UTC):
            courseware_url = reverse(
                'course_root',
                kwargs={'course_id': unicode(course_key)}
            )

        full_name = (
            request.user.profile.name
            if request.user.profile.name
            else ""
        )

        # If the user set a contribution amount on another page,
        # use that amount to pre-fill the price selection form.
        contribution_amount = request.session.get(
            'donation_for_course', {}
        ).get(unicode(course_key), '')

        # Remember whether the user is upgrading
        # so we can fire an analytics event upon payment.
        request.session['attempting_upgrade'] = (message == self.UPGRADE_MSG)

        # Determine the photo verification status
        verification_good_until = self._verification_valid_until(request.user)

        # get available payment processors
        if relevant_course_mode.sku:
            # transaction will be conducted via ecommerce service
            processors = ecommerce_api_client(request.user).payment.processors.get()
        else:
            # transaction will be conducted using legacy shopping cart
            processors = [settings.CC_PROCESSOR_NAME]

        # Render the top-level page
        context = {
            'contribution_amount': contribution_amount,
            'course': course,
            'course_key': unicode(course_key),
            'checkpoint_location': request.GET.get('checkpoint'),
            'course_mode': relevant_course_mode,
            'courseware_url': courseware_url,
            'current_step': current_step,
            'disable_courseware_js': True,
            'display_steps': display_steps,
            'is_active': json.dumps(user_is_active),
            'user_email': request.user.email,
            'message_key': message,
            'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
            'processors': processors,
            'requirements': requirements,
            'user_full_name': full_name,
            'verification_deadline': verification_deadline or "",
            'already_verified': already_verified,
            'verification_good_until': verification_good_until,
            'capture_sound': staticfiles_storage.url("audio/camera_capture.wav"),
            'nav_hidden': True,
            'is_ab_testing': 'begin-flow' in request.path,
        }

        return render_to_response("verify_student/pay_and_verify.html", context)
Exemple #29
0
 def date(self):  # lint-amnesty, pylint: disable=invalid-overridden-method
     return VerificationDeadline.deadline_for_course(self.course_id)
Exemple #30
0
    def test_no_deadline(self):
        """ Verify the signal sets deadline to course end when no deadline exists."""
        _listen_for_course_publish('store', self.course.id)

        self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), self.course.end)
 def date(self):
     return VerificationDeadline.deadline_for_course(self.course.id)