def calculate_run_price(course_run, user): """ Calculate the price of a course given the coupons and financial aid available to the user. Args: course_run (CourseRun): A course run user (django.contrib.auth.models.User): A user Returns: (decimal.Decimal, Coupon): The adjusted of the course, and the coupon used if any """ program = course_run.course.program enrollment = get_object_or_404(ProgramEnrollment, program=program, user=user) price = get_formatted_course_price(enrollment)['price'] coupons = [coupon for coupon in pick_coupons(user) if coupon.program == program] if not coupons: # There is no coupon for this program return price, None coupon = coupons[0] if course_run.edx_course_key not in coupon.course_keys: # coupon does not apply to this particular course run return price, None price = calculate_coupon_price(coupon, price, course_run.edx_course_key) return price, coupon
def get(self, request, *args, **kwargs): """ GET handler """ user = request.user program_enrollment = get_object_or_404( ProgramEnrollment, user=user, program__id=self.kwargs["program_id"], program__live=True) serializer = FormattedCoursePriceSerializer( get_formatted_course_price(program_enrollment)) return Response(data=serializer.data)
def test_calculate_run_price_coupon(self): """ If there is a coupon calculate_run_price should use calculate_coupon_price to get the discounted price """ course_run, user = create_purchasable_course_run() coupon = CouponFactory.create(content_object=course_run.course) UserCoupon.objects.create(coupon=coupon, user=user) discounted_price = 5 with patch('ecommerce.api.calculate_coupon_price', autospec=True) as _calculate_coupon_price: _calculate_coupon_price.return_value = discounted_price assert calculate_run_price(course_run, user) == (discounted_price, coupon) program_enrollment = course_run.course.program.programenrollment_set.first() fa_price = get_formatted_course_price(program_enrollment)['price'] _calculate_coupon_price.assert_called_with(coupon, fa_price, course_run.edx_course_key)
def test_get_all_course_prices(self): """ Test that the course_price_list route will return a list of formatted course prices which should only include entries from the program the user is enrolled in. """ resp = self.make_http_request(self.client.get, self.course_price_url, status.HTTP_200_OK) expected = sorted([ get_formatted_course_price(enrollment) for enrollment in ProgramEnrollment.objects.filter(user=self.profile.user) ], key=lambda x: x['program_id']) actual = sorted([ decimalize_price(fcp) for fcp in resp.data ], key=lambda x: x['program_id']) assert actual == expected
def test_calculate_run_price_coupon(self): """ If there is a coupon calculate_run_price should use calculate_coupon_price to get the discounted price """ course_run, user = create_purchasable_course_run() coupon = CouponFactory.create(content_object=course_run.course) UserCoupon.objects.create(coupon=coupon, user=user) discounted_price = 5 with patch('ecommerce.api.calculate_coupon_price', autospec=True) as _calculate_coupon_price: _calculate_coupon_price.return_value = discounted_price assert calculate_run_price(course_run, user) == (discounted_price, coupon) program_enrollment = course_run.course.program.programenrollment_set.first() fa_price = get_formatted_course_price(program_enrollment)['price'] _calculate_coupon_price.assert_called_with(coupon, fa_price, course_run.edx_course_key)
def test_staff_can_get_other_user(self): """ A staff user should be able to get the data for a learner enrolled in their program """ self.client.force_login(self.staff_user_profile.user) resp = self.make_http_request(self.client.get, self.course_price_url, status.HTTP_200_OK) expected = sorted([ get_formatted_course_price(enrollment) for enrollment in ProgramEnrollment.objects.filter(user=self.profile.user) ], key=lambda x: x['program_id']) actual = sorted([ decimalize_price(fcp) for fcp in resp.data ], key=lambda x: x['program_id']) assert actual == expected
def test_calculate_run_price_no_coupons(self): """ If there are no coupons for this program the price should be what get_formatted_course_price returned """ course_run, user = create_purchasable_course_run() # This coupon is for a different program coupon = CouponFactory.create() UserCoupon.objects.create(coupon=coupon, user=user) discounted_price = 5 program_enrollment = course_run.course.program.programenrollment_set.first() fa_price = get_formatted_course_price(program_enrollment)['price'] with patch('ecommerce.api.calculate_coupon_price', autospec=True) as _calculate_coupon_price: _calculate_coupon_price.return_value = discounted_price assert calculate_run_price(course_run, user) == (fa_price, None) assert _calculate_coupon_price.called is False
def test_calculate_run_price_no_coupons(self): """ If there are no coupons for this program the price should be what get_formatted_course_price returned """ course_run, user = create_purchasable_course_run() # This coupon is for a different program coupon = CouponFactory.create() UserCoupon.objects.create(coupon=coupon, user=user) discounted_price = 5 program_enrollment = course_run.course.program.programenrollment_set.first() fa_price = get_formatted_course_price(program_enrollment)['price'] with patch('ecommerce.api.calculate_coupon_price', autospec=True) as _calculate_coupon_price: _calculate_coupon_price.return_value = discounted_price assert calculate_run_price(course_run, user) == (fa_price, None) assert _calculate_coupon_price.called is False
def test_calculate_run_price_other_run(self): """ If the coupon is for another course in this program it should not be returned here """ course_run, user = create_purchasable_course_run() other_course = CourseRunFactory.create(course__program=course_run.course.program).course coupon = CouponFactory.create(content_object=other_course) UserCoupon.objects.create(coupon=coupon, user=user) discounted_price = 5 program_enrollment = course_run.course.program.programenrollment_set.first() fa_price = get_formatted_course_price(program_enrollment)['price'] with patch('ecommerce.api.calculate_coupon_price', autospec=True) as _calculate_coupon_price: _calculate_coupon_price.return_value = discounted_price assert calculate_run_price(course_run, user) == (fa_price, None) assert _calculate_coupon_price.called is False
def test_calculate_run_price_other_run(self): """ If the coupon is for another course in this program it should not be returned here """ course_run, user = create_purchasable_course_run() other_course = CourseRunFactory.create(course__program=course_run.course.program).course coupon = CouponFactory.create(content_object=other_course) UserCoupon.objects.create(coupon=coupon, user=user) discounted_price = 5 program_enrollment = course_run.course.program.programenrollment_set.first() fa_price = get_formatted_course_price(program_enrollment)['price'] with patch('ecommerce.api.calculate_coupon_price', autospec=True) as _calculate_coupon_price: _calculate_coupon_price.return_value = discounted_price assert calculate_run_price(course_run, user) == (fa_price, None) assert _calculate_coupon_price.called is False
def test_generate_financial_aid_email_approved(self): """ Tests generate_financial_aid_email() with status APPROVED """ self.financial_aid.status = FinancialAidStatus.APPROVED self.financial_aid.save() email_dict = generate_financial_aid_email(self.financial_aid) assert email_dict["subject"] == FINANCIAL_AID_APPROVAL_SUBJECT.format( program_name=self.financial_aid.tier_program.program.title) assert email_dict["body"] == FINANCIAL_AID_EMAIL_BODY.format( first_name=self.financial_aid.user.profile.first_name, message=FINANCIAL_AID_APPROVAL_MESSAGE.format( program_name=self.financial_aid.tier_program.program.title, price=get_formatted_course_price( self.program_enrollment)["price"]), program_name=self.financial_aid.tier_program.program.title)
def test_get_course_price_for_learner_with_no_financial_aid_request(self): """ Tests get_course_price_for_learner() who has no financial aid request """ enrollment = ProgramEnrollment.objects.get(program=self.program, user=self.profile.user) # Enrolled and has no financial aid course_price = self.program.price expected_response = { "program_id": enrollment.program.id, "price": course_price, "financial_aid_availability": True, "has_financial_aid_request": False } self.assertDictEqual(get_formatted_course_price(enrollment), expected_response)
def test_get_course_price_for_learner_in_no_financial_aid_program(self): """ Tests get_course_price_for_learner() for a program without financial aid """ enrollment = ProgramEnrollment.objects.get(program=self.program, user=self.profile.user) self.program.financial_aid_availability = False self.program.save() course_price = self.program.price expected_response = { "program_id": enrollment.program.id, "price": course_price, "financial_aid_availability": False, "has_financial_aid_request": False } self.assertDictEqual(get_formatted_course_price(enrollment), expected_response)
def get(self, request, username, *args, **kwargs): """ GET handler """ user = get_object_or_404(User, social_auth__uid=username, social_auth__provider=EdxOrgOAuth2.name) program_enrollments = (ProgramEnrollment.objects.select_related( 'user', 'program').filter(user=user, program__live=True).all()) formatted_course_prices = [ get_formatted_course_price(program_enrollment) for program_enrollment in program_enrollments ] serializer = FormattedCoursePriceSerializer(formatted_course_prices, many=True) return Response(data=serializer.data)
def test_get_course_price_for_learner_with_no_financial_aid_request(self): """ Tests get_course_price_for_learner() who has no financial aid request """ enrollment = ProgramEnrollment.objects.get(program=self.program, user=self.profile.user) # Enrolled and has no financial aid course_price = self.program.price expected_response = { "program_id": enrollment.program.id, "price": course_price, "financial_aid_availability": True, "has_financial_aid_request": False } self.assertDictEqual( get_formatted_course_price(enrollment), expected_response )
def test_get_course_price_for_learner_in_no_financial_aid_program(self): """ Tests get_course_price_for_learner() for a program without financial aid """ enrollment = ProgramEnrollment.objects.get(program=self.program, user=self.profile.user) self.program.financial_aid_availability = False self.program.save() course_price = self.program.price expected_response = { "program_id": enrollment.program.id, "price": course_price, "financial_aid_availability": False, "has_financial_aid_request": False } self.assertDictEqual( get_formatted_course_price(enrollment), expected_response )
def test_generate_financial_aid_email_approved(self): """ Tests generate_financial_aid_email() with status APPROVED """ self.financial_aid.status = FinancialAidStatus.APPROVED self.financial_aid.save() email_dict = generate_financial_aid_email(self.financial_aid) assert email_dict["subject"] == FINANCIAL_AID_APPROVAL_SUBJECT.format( program_name=self.financial_aid.tier_program.program.title ) assert email_dict["body"] == FINANCIAL_AID_EMAIL_BODY.format( first_name=self.financial_aid.user.profile.first_name, message=FINANCIAL_AID_APPROVAL_MESSAGE.format( program_name=self.financial_aid.tier_program.program.title, price=get_formatted_course_price(self.program_enrollment)["price"] ), program_name=self.financial_aid.tier_program.program.title )
def test_get_course_price_for_learner_with_financial_aid(self, status): """ Tests get_course_price_for_learner() who has approved financial aid """ enrollment = ProgramEnrollment.objects.get(program=self.program, user=self.profile.user) financial_aid = FinancialAidFactory.create( user=self.profile.user, tier_program=self.tier_programs['25k'], status=status, ) course_price = self.program.price expected_response = { "program_id": enrollment.program.id, "price": course_price - financial_aid.tier_program.discount_amount, "financial_aid_availability": True, "has_financial_aid_request": True } self.assertDictEqual(get_formatted_course_price(enrollment), expected_response)
def test_get_course_price_for_learner_with_financial_aid_in_reset(self): """ Tests get_course_price_for_learner() who has financial aid request in status `reset` """ enrollment = ProgramEnrollment.objects.get(program=self.program, user=self.profile.user) FinancialAidFactory.create( user=self.profile.user, tier_program=self.tier_programs['25k'], status=FinancialAidStatus.RESET, ) # Enrolled and has no financial aid course_price = self.program.price expected_response = { "program_id": enrollment.program.id, "price": course_price, "financial_aid_availability": True, "has_financial_aid_request": False } self.assertDictEqual(get_formatted_course_price(enrollment), expected_response)
def test_get_course_price_for_learner_with_financial_aid(self, status): """ Tests get_course_price_for_learner() who has approved financial aid """ enrollment = ProgramEnrollment.objects.get(program=self.program, user=self.profile.user) financial_aid = FinancialAidFactory.create( user=self.profile.user, tier_program=self.tier_programs['25k'], status=status, ) course_price = self.program.price expected_response = { "program_id": enrollment.program.id, "price": course_price - financial_aid.tier_program.discount_amount, "financial_aid_availability": True, "has_financial_aid_request": True } self.assertDictEqual( get_formatted_course_price(enrollment), expected_response )
def test_get_course_price_for_learner_with_financial_aid_in_reset(self): """ Tests get_course_price_for_learner() who has financial aid request in status `reset` """ enrollment = ProgramEnrollment.objects.get(program=self.program, user=self.profile.user) FinancialAidFactory.create( user=self.profile.user, tier_program=self.tier_programs['25k'], status=FinancialAidStatus.RESET, ) # Enrolled and has no financial aid course_price = self.program.price expected_response = { "program_id": enrollment.program.id, "price": course_price, "financial_aid_availability": True, "has_financial_aid_request": False } self.assertDictEqual( get_formatted_course_price(enrollment), expected_response )
def generate_financial_aid_email(financial_aid): """ Generates the email subject and body for a FinancialAid status update. Accepted statuses are FinancialAidStatus.APPROVED and FinancialAidStatus.PENDING_MANUAL_APPROVAL (documents have been received). Args: financial_aid (FinancialAid): The FinancialAid object in question Returns: dict: {"subject": (str), "body": (str)} """ if financial_aid.status == FinancialAidStatus.APPROVED: program_enrollment = ProgramEnrollment.objects.get( user=financial_aid.user, program=financial_aid.tier_program.program ) message = FINANCIAL_AID_APPROVAL_MESSAGE.format( program_name=financial_aid.tier_program.program.title, price=get_formatted_course_price(program_enrollment)["price"] ) subject = FINANCIAL_AID_APPROVAL_SUBJECT.format(program_name=financial_aid.tier_program.program.title) elif financial_aid.status == FinancialAidStatus.PENDING_MANUAL_APPROVAL: message = FINANCIAL_AID_DOCUMENTS_RECEIVED_MESSAGE subject = FINANCIAL_AID_DOCUMENTS_RECEIVED_SUBJECT.format( program_name=financial_aid.tier_program.program.title ) elif financial_aid.status == FinancialAidStatus.RESET: message = FINANCIAL_AID_DOCUMENTS_RESET_MESSAGE subject = FINANCIAL_AID_RESET_SUBJECT.format( program_name=financial_aid.tier_program.program.title ) else: # django.core.exceptions.ValidationError raise ValidationError("Invalid status on FinancialAid for generate_financial_aid_email()") body = FINANCIAL_AID_EMAIL_BODY.format( first_name=financial_aid.user.profile.first_name, message=message, program_name=financial_aid.tier_program.program.title ) return {"subject": subject, "body": body}
def generate_financial_aid_email(financial_aid): """ Generates the email subject and body for a FinancialAid status update. Accepted statuses are FinancialAidStatus.APPROVED and FinancialAidStatus.PENDING_MANUAL_APPROVAL (documents have been received). Args: financial_aid (FinancialAid): The FinancialAid object in question Returns: dict: {"subject": (str), "body": (str)} """ if financial_aid.status == FinancialAidStatus.APPROVED: program_enrollment = ProgramEnrollment.objects.get( user=financial_aid.user, program=financial_aid.tier_program.program) message = FINANCIAL_AID_APPROVAL_MESSAGE.format( program_name=financial_aid.tier_program.program.title, price=get_formatted_course_price(program_enrollment)["price"]) subject = FINANCIAL_AID_APPROVAL_SUBJECT.format( program_name=financial_aid.tier_program.program.title) elif financial_aid.status == FinancialAidStatus.PENDING_MANUAL_APPROVAL: message = FINANCIAL_AID_DOCUMENTS_RECEIVED_MESSAGE subject = FINANCIAL_AID_DOCUMENTS_RECEIVED_SUBJECT.format( program_name=financial_aid.tier_program.program.title) elif financial_aid.status == FinancialAidStatus.RESET: message = FINANCIAL_AID_DOCUMENTS_RESET_MESSAGE subject = FINANCIAL_AID_RESET_SUBJECT.format( program_name=financial_aid.tier_program.program.title) else: # django.core.exceptions.ValidationError raise ValidationError( "Invalid status on FinancialAid for generate_financial_aid_email()" ) body = FINANCIAL_AID_EMAIL_BODY.format( first_name=financial_aid.user.profile.first_name, message=message, program_name=financial_aid.tier_program.program.title) return {"subject": subject, "body": body}