def send_email(self, order): """ Sends an email with enrollment code order information. """ # Note (multi-courses): Change from a course_name to a list of course names. product = order.lines.first().product course = Course.objects.get(id=product.attr.course_key) send_notification(order.user, 'ORDER_WITH_CSV', context={ 'contact_url': get_lms_url('/contact'), 'course_name': course.name, 'download_csv_link': get_ecommerce_url( reverse('coupons:enrollment_code_csv', args=[order.number])), 'enrollment_code_title': product.title, 'order_number': order.number, 'partner_name': order.site.siteconfiguration.partner.name, 'lms_url': get_lms_url(), 'receipt_page_url': get_lms_url('{}?orderNum={}'.format( settings.RECEIPT_PAGE_PATH, order.number)), }, site=order.site)
def get_lms_footer(): """ Retrieve LMS footer via branding API. Returns: str: HTML representation of the footer. """ try: footer_api_url = get_lms_url('api/branding/v1/footer') response = requests.get( footer_api_url, data={'language': 'en'} ) if response.status_code == 200: return response.text else: logger.error( 'Unable to retrieve footer from %s. Branding API returned status code %d.', footer_api_url, response.status_code ) return None except requests.exceptions.ConnectionError: logger.exception('Connection error occurred while retrieving footer from %s.', get_lms_url()) return None except requests.Timeout: logger.exception('Connection timed out while retrieving footer from %s.', get_lms_url()) return None
def setUp(self): super(CheckoutPageTest, self).setUp() user = self.create_user(is_superuser=False) self.create_access_token(user) self.client.login(username=user.username, password=self.password) self.provider = 'ASU' self.price = 100 self.credit_hours = 2 self.eligibility_url = get_lms_url('/api/credit/v1/eligibility/') self.provider_url = get_lms_url('/api/credit/v1/providers/') self.course = CourseFactory( thumbnail_url='http://www.edx.org/course.jpg', partner=self.partner) self.provider_data = [{ 'enable_integration': False, 'description': 'Arizona State University', 'url': 'https://credit.example.com/', 'status_url': 'https://credit.example.com/status', 'thumbnail_url': 'http://edX/DemoX/asset/images_course_image.jpg', 'fulfillment_instructions': 'Sample fulfilment requirement.', 'display_name': 'Arizona State University', 'id': 'ASU' }] self.eligibilities = [{ 'deadline': '2016-10-28T09:56:44Z', 'course_key': 'edx/cs01/2015' }]
def setUp(self): super(CheckoutPageTest, self).setUp() user = self.create_user(is_superuser=False) self.create_access_token(user) self.client.login(username=user.username, password=self.password) self.provider = 'ASU' self.price = 100 self.credit_hours = 2 self.eligibility_url = get_lms_url('/api/credit/v1/eligibility/') self.provider_url = get_lms_url('/api/credit/v1/providers/') self.course = CourseFactory(thumbnail_url='http://www.edx.org/course.jpg') self.provider_data = [ { 'enable_integration': False, 'description': 'Arizona State University', 'url': 'https://credit.example.com/', 'status_url': 'https://credit.example.com/status', 'thumbnail_url': 'http://edX/DemoX/asset/images_course_image.jpg', 'fulfillment_instructions': 'Sample fulfilment requirement.', 'display_name': 'Arizona State University', 'id': 'ASU' } ] self.eligibilities = [ { 'deadline': '2016-10-28T09:56:44Z', 'course_key': 'edx/cs01/2015' } ]
def send_course_purchase_email(sender, order=None, **kwargs): # pylint: disable=unused-argument """Send course purchase notification email when a course is purchased.""" if waffle.switch_is_active('ENABLE_NOTIFICATIONS'): # We do not currently support email sending for orders with more than one item. if len(order.lines.all()) == ORDER_LINE_COUNT: product = order.lines.first().product provider_id = getattr(product.attr, 'credit_provider', None) if not provider_id: stripped_title = product.title.replace("Seat in ", "", 1) stripped_title = stripped_title.replace( "with professional certificate", "") stripped_title = stripped_title.replace( "with verified certificate", "") send_notification( order.user, 'CREDIT_RECEIPT', { 'course_title': stripped_title, 'receipt_page_url': get_lms_url('{}?orderNum={}'.format( settings.RECEIPT_PAGE_PATH, order.number)), 'credit_hours': str(order.total_excl_tax), 'credit_provider': 'Credit provider', }, threadlocals.get_current_request().site) logger.error( 'Failed to send credit receipt notification. Credit seat product [%s] has no provider.', product.id) return elif product.get_product_class().name == 'Seat': provider_data = get_provider_data(provider_id) if provider_data: send_notification( order.user, 'CREDIT_RECEIPT', { 'course_title': product.title, 'receipt_page_url': get_lms_url('{}?orderNum={}'.format( settings.RECEIPT_PAGE_PATH, order.number)), 'credit_hours': product.attr.credit_hours, 'credit_provider': provider_data['display_name'], }, threadlocals.get_current_request().site) else: logger.info( 'Currently support receipt emails for order with one item.')
def test_post_checkout_callback(self): """ When the post_checkout signal is emitted, the receiver should attempt to fulfill the newly-placed order and send receipt email. """ httpretty.register_uri(httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'), body='{"display_name": "Hogwarts"}', content_type="application/json") toggle_switch('ENABLE_NOTIFICATIONS', True) course = Course.objects.create(id='edX/DemoX/Demo_Course', name='Demo Course') seat = course.create_or_update_seat('credit', False, 50, self.partner, 'ASU', None, 2) basket = BasketFactory() basket.add_product(seat, 1) order = factories.create_order(number=1, basket=basket, user=self.user) with mock.patch( 'threadlocals.threadlocals.get_current_request') as mock_gcr: mock_gcr.return_value = self.request send_course_purchase_email(None, order=order) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].from_email, self.site_configuration.from_email) self.assertEqual(mail.outbox[0].subject, 'Order Receipt') self.assertEqual( mail.outbox[0].body, '\nPayment confirmation for: {course_title}' '\n\nDear {full_name},' '\n\nThank you for purchasing {credit_hours} credit hours from {credit_provider} for {course_title}. ' 'A charge will appear on your credit or debit card statement with a company name of "{platform_name}".' '\n\nTo receive your course credit, you must also request credit at the {credit_provider} website. ' 'For a link to request credit from {credit_provider}, or to see the status of your credit request, ' 'go to your {platform_name} dashboard.' '\n\nTo explore other credit-eligible courses, visit the {platform_name} website. ' 'We add new courses frequently!' '\n\nTo view your payment information, visit the following website.' '\n{receipt_url}' '\n\nThank you. We hope you enjoyed your course!' '\nThe {platform_name} team' '\n\nYou received this message because you purchased credit hours for {course_title}, ' 'an {platform_name} course.\n'.format( course_title=order.lines.first().product.title, full_name=self.user.get_full_name(), credit_hours=2, credit_provider='Hogwarts', platform_name=get_current_request().site.name, receipt_url=get_lms_url('{}?orderNum={}'.format( settings.RECEIPT_PAGE_PATH, order.number))))
def test_post_checkout_callback(self): """ When the post_checkout signal is emitted, the receiver should attempt to fulfill the newly-placed order and send receipt email. """ httpretty.register_uri( httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'), body='{"display_name": "Hogwarts"}', content_type="application/json" ) toggle_switch('ENABLE_NOTIFICATIONS', True) course = Course.objects.create(id='edX/DemoX/Demo_Course', name='Demo Course') seat = course.create_or_update_seat('credit', False, 50, self.partner, 'ASU', None, 2) basket = BasketFactory() basket.add_product(seat, 1) order = factories.create_order(number=1, basket=basket, user=self.user) with mock.patch('threadlocals.threadlocals.get_current_request') as mock_gcr: mock_gcr.return_value = self.request send_course_purchase_email(None, order=order) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].from_email, self.site_configuration.from_email) self.assertEqual(mail.outbox[0].subject, 'Order Receipt') self.assertEqual( mail.outbox[0].body, '\nPayment confirmation for: {course_title}' '\n\nDear {full_name},' '\n\nThank you for purchasing {credit_hours} credit hours from {credit_provider} for {course_title}. ' 'A charge will appear on your credit or debit card statement with a company name of "{platform_name}".' '\n\nTo receive your course credit, you must also request credit at the {credit_provider} website. ' 'For a link to request credit from {credit_provider}, or to see the status of your credit request, ' 'go to your {platform_name} dashboard.' '\n\nTo explore other credit-eligible courses, visit the {platform_name} website. ' 'We add new courses frequently!' '\n\nTo view your payment information, visit the following website.' '\n{receipt_url}' '\n\nThank you. We hope you enjoyed your course!' '\nThe {platform_name} team' '\n\nYou received this message because you purchased credit hours for {course_title}, ' 'an {platform_name} course.\n'.format( course_title=order.lines.first().product.title, full_name=self.user.get_full_name(), credit_hours=2, credit_provider='Hogwarts', platform_name=get_current_request().site.name, receipt_url=get_lms_url('{}?orderNum={}'.format(settings.RECEIPT_PAGE_PATH, order.number)) ) )
def test_basket_redirect_enrollment_code(self): """ Verify the view redirects to LMS when an enrollment code is provided. """ self.create_and_test_coupon() httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200) self.assert_redemption_page_redirects(get_lms_url())
def mock_creditcourse_endpoint(self, course_id, status, body=None): url = get_lms_url('/api/credit/v1/courses/{}/'.format(course_id)) responses.add(responses.PUT, url, status=status, json=body, content_type=JSON)
def test_multiple_vouchers(self): """ Verify a redirect to LMS happens when a basket with already existing vouchers is used. """ self.create_and_test_coupon() basket = Basket.get_basket(self.user, self.site) basket.vouchers.add(Voucher.objects.get(code=COUPON_CODE)) httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200) self.assert_redemption_page_redirects(get_lms_url())
def get_credit_providers(self): """ Retrieve all credit providers from LMS. Results will be sorted alphabetically by display name. """ key = 'credit_providers' credit_providers_cache_response = TieredCache.get_cached_response(key) if credit_providers_cache_response.is_found: return credit_providers_cache_response.value try: credit_api = EdxRestApiClient( get_lms_url('/api/credit/v1/'), oauth_access_token=self.request.user.access_token) credit_providers = credit_api.providers.get() credit_providers.sort( key=lambda provider: provider['display_name']) # Update the cache TieredCache.set_all_tiers(key, credit_providers, settings.CREDIT_PROVIDER_CACHE_TIMEOUT) except (SlumberBaseException, Timeout): logger.exception('Failed to retrieve credit providers!') credit_providers = [] return credit_providers
def credit_api_client(self): """ Returns an instance of the Credit API client. """ return EdxRestApiClient( get_lms_url('api/credit/v1/'), oauth_access_token=self.request.user.access_token )
def core(request): return { 'lms_base_url': get_lms_url(), 'lms_dashboard_url': get_lms_dashboard_url(), 'platform_name': request.site.name, 'support_url': settings.SUPPORT_URL, }
def get_provider_data(provider_id): """Get the provider information for provider id provider. Args: provider_id(str): Identifier for the provider Returns: dict """ provider_info_url = get_lms_url('api/credit/v1/providers/{}'.format(provider_id)) timeout = settings.PROVIDER_DATA_PROCESSING_TIMEOUT headers = { 'Content-Type': 'application/json', 'X-Edx-Api-Key': settings.EDX_API_KEY } try: response = requests.get(provider_info_url, headers=headers, timeout=timeout) if response.status_code == 200: return response.json() else: logger.error( 'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s', provider_id, response.status_code, response.text) return None except requests.exceptions.ConnectionError: logger.exception('Connection error occurred during getting data for %s provider', provider_id) return None except requests.Timeout: logger.exception('Failed to retrieve data for %s provider, connection timeout', provider_id) return None
def mock_course_api_response(self, course=None): """ Helper function to register an API endpoint for the course information. """ course_info = { 'short_description': 'Test description', 'media': { 'course_image': { 'uri': '/asset-v1:test+test+test+type@asset+block@images_course_image.jpg' }, 'image': { 'raw': 'path/to/the/course/image' } }, 'start': '2013-02-05T05:00:00Z', 'name': course.name if course else 'Test course', 'org': 'test' } course_info_json = json.dumps(course_info) course_id = course.id if course else 'course-v1:test+test+test' course_url = get_lms_url( 'api/courses/v1/courses/{}/'.format(course_id)) httpretty.register_uri(httpretty.GET, course_url, body=course_info_json, content_type=CONTENT_TYPE)
def get(self, request): """ Looks up the passed code and adds the matching product to a basket, then applies the voucher and if the basket total is FREE places the order and enrolls the user in the course. """ template_name = 'coupons/offer.html' code = request.GET.get('code', None) if not code: return render(request, template_name, {'error': _('Code not provided')}) try: voucher, product = get_voucher_from_code(code=code) except Voucher.DoesNotExist: msg = 'No voucher found with code {code}'.format(code=code) return render(request, template_name, {'error': _(msg)}) except exceptions.ProductNotFoundError: return render( request, template_name, { 'error': _('The voucher is not applicable to your current basket.') }) valid_voucher, msg = voucher_is_valid(voucher, product, request) if not valid_voucher: return render(request, template_name, {'error': msg}) basket = prepare_basket(request, product, voucher) if basket.total_excl_tax == AC.FREE: self.place_free_order(basket) else: return HttpResponseRedirect(reverse('basket:summary')) return HttpResponseRedirect(get_lms_url(''))
def test_get_offers_for_single_course_voucher(self): """ Verify that the course offers data is returned for a single course voucher. """ course, seat = self.create_course_and_seat() new_range = RangeFactory(products=[seat, ]) voucher, __ = prepare_voucher(_range=new_range, benefit_value=10) voucher, products = get_voucher_and_products_from_code(voucher.code) benefit = voucher.offers.first().benefit request = self.prepare_offers_listing_request(voucher.code) self.mock_course_api_response(course=course) offers = VoucherViewSet().get_offers(products=products, request=request, voucher=voucher) first_offer = offers[0] self.assertEqual(len(offers), 1) self.assertDictEqual(first_offer, { 'benefit': { 'type': benefit.type, 'value': benefit.value }, 'contains_verified': True, 'course_start_date': '2013-02-05T05:00:00Z', 'id': course.id, 'image_url': get_lms_url('/asset-v1:test+test+test+type@asset+block@images_course_image.jpg'), 'organization': CourseKey.from_string(course.id).org, 'seat_type': course.type, 'stockrecords': serializers.StockRecordSerializer(seat.stockrecords.first()).data, 'title': course.name, 'voucher_end_date': voucher.end_datetime })
def _add_to_context_data(self, context): formset = context.get('formset', []) lines = context.get('line_list', []) site_configuration = self.request.site.siteconfiguration context_updates, lines_data = self.process_basket_lines(lines) context.update(context_updates) context.update(self.process_totals(context)) context.update({ 'analytics_data': prepare_analytics_data( self.request.user, site_configuration.segment_key, ), 'enable_client_side_checkout': False, 'sdn_check': site_configuration.enable_sdn_check }) payment_processors = site_configuration.get_payment_processors() if ( site_configuration.client_side_payment_processor and waffle.flag_is_active(self.request, CLIENT_SIDE_CHECKOUT_FLAG_NAME) ): payment_processors_data = self._get_payment_processors_data(payment_processors) context.update(payment_processors_data) context.update({ 'formset_lines_data': list(zip(formset, lines_data)), 'homepage_url': get_lms_url(''), 'min_seat_quantity': 1, 'max_seat_quantity': 100, 'payment_processors': payment_processors, 'lms_url_root': site_configuration.lms_url_root, }) return context
def _add_dynamic_discount_to_request(self, basket): # TODO: Remove as a part of REVMI-124 as this is a hacky solution # The problem is that orders are being created after payment processing, and the discount is not # saved in the database, so it needs to be calculated again in order to save the correct info to the # order. REVMI-124 will create the order before payment processing, when we have the discount context. if waffle.flag_is_active(self.request, DYNAMIC_DISCOUNT_FLAG) and basket.lines.count() == 1: # pragma: no cover pylint: disable=line-too-long discount_lms_url = get_lms_url('/api/discounts/') lms_discount_client = EdxRestApiClient( discount_lms_url, jwt=self.request.site.siteconfiguration.access_token) ck = basket.lines.first().product.course_id user_id = basket.owner.lms_user_id try: response = lms_discount_client.user(user_id).course(ck).get() self.request.POST = self.request.POST.copy() self.request.POST['discount_jwt'] = response.get('jwt') logger.info( """Received discount jwt from LMS with url: [%s], user_id: [%s], course_id: [%s], and basket_id: [%s] returned [%s]""", discount_lms_url, str(user_id), ck, basket.id, response) except (SlumberHttpBaseException, requests.exceptions.Timeout) as error: logger.warning( """Failed to receive discount jwt from LMS with url: [%s], user_id: [%s], course_id: [%s], and basket_id: [%s] returned [%s]""", discount_lms_url, str(user_id), ck, basket.id, vars(error.response) if hasattr(error, 'response') else '')
def is_eligible_for_credit(self, course_key): """ Check if a user is eligible for a credit course. Calls the LMS eligibility API endpoint and sends the username and course key query parameters and returns eligibility details for the user and course combination. Args: course_key (string): The course key for which the eligibility is checked for. Returns: A list that contains eligibility information, or empty if user is not eligible. Raises: ConnectionError, SlumberBaseException and Timeout for failures in establishing a connection with the LMS eligibility API endpoint. """ query_strings = { 'username': self.username, 'course_key': course_key } try: api = EdxRestApiClient( get_lms_url('api/credit/v1/'), oauth_access_token=self.access_token ) response = api.eligibility().get(**query_strings) except (ConnectionError, SlumberBaseException, Timeout): # pragma: no cover log.exception( 'Failed to retrieve eligibility details for [%s] in course [%s]', self.username, course_key ) raise return response
def core(request): return { 'lms_base_url': get_lms_url(), 'lms_dashboard_url': get_lms_dashboard_url(), 'platform_name': request.site.name, 'support_url': request.site.siteconfiguration.payment_support_url, }
def send_course_purchase_email(sender, order=None, **kwargs): # pylint: disable=unused-argument """Send course purchase notification email when a course is purchased.""" if waffle.switch_is_active('ENABLE_NOTIFICATIONS'): # We do not currently support email sending for orders with more than one item. if len(order.lines.all()) == ORDER_LINE_COUNT: product = order.lines.first().product provider_id = getattr(product.attr, 'credit_provider', None) if not provider_id: logger.error( 'Failed to send credit receipt notification. Credit seat product [%s] has not provider.', product.id ) return elif product.get_product_class().name == 'Seat': provider_data = get_provider_data(provider_id) if provider_data: send_notification( order.user, 'CREDIT_RECEIPT', { 'course_title': product.title, 'receipt_page_url': get_lms_url( '{}?orderNum={}'.format(settings.RECEIPT_PAGE_PATH, order.number) ), 'credit_hours': product.attr.credit_hours, 'credit_provider': provider_data['display_name'], }, threadlocals.get_current_request().site ) else: logger.info('Currently support receipt emails for order with one item.')
def get(self, request): """ Looks up the passed code and adds the matching product to a basket, then applies the voucher and if the basket total is FREE places the order and enrolls the user in the course. """ template_name = 'coupons/offer.html' code = request.GET.get('code', None) if not code: return render(request, template_name, {'error': _('Code not provided')}) try: voucher, product = get_voucher_from_code(code=code) except Voucher.DoesNotExist: msg = 'No voucher found with code {code}'.format(code=code) return render(request, template_name, {'error': _(msg)}) except exceptions.ProductNotFoundError: return render(request, template_name, {'error': _('The voucher is not applicable to your current basket.')}) valid_voucher, msg = voucher_is_valid(voucher, product, request) if not valid_voucher: return render(request, template_name, {'error': msg}) basket = prepare_basket(request, product, voucher) if basket.total_excl_tax == AC.FREE: self.place_free_order(basket) else: return HttpResponseRedirect(reverse('basket:summary')) return HttpResponseRedirect(get_lms_url(''))
def get_context_data(self, **kwargs): context = super(PaymentFailedView, self).get_context_data(**kwargs) context.update({ 'dashboard_url': get_lms_url(), 'payment_support_email': self.request.site.siteconfiguration.payment_support_email }) return context
def send_order_completion_email(sender, order=None, **kwargs): # pylint: disable=unused-argument """Send course purchase notification email when a course is purchased.""" if waffle.switch_is_active('ENABLE_NOTIFICATIONS'): # We do not currently support email sending for orders with more than one item. if len(order.lines.all()) == ORDER_LINE_COUNT: product = order.lines.first().product if product.get_product_class().name == 'Seat': send_notification( order.user, 'COURSE_PURCHASED', { 'course_title': product.title, 'full_name': order.user.get_full_name(), 'lms_dashboard': get_lms_url('dashboard'), 'lms_courses': get_lms_url('courses') }, order.site) else: logger.info( 'Currently support receipt emails for order with one item.')
def test_invalid_user(self): """ Verify an unauthorized request is redirected to the LMS dashboard. """ order = OrderFactory() order.user = self.create_user() response = self.client.get(reverse(self.path, args=[order.number])) self.assertEqual(response.status_code, 302) self.assertEqual(response['location'], get_lms_url('dashboard'))
def test_invalid_user(self): """ Verify an unauthorized request is redirected to the LMS dashboard. """ order = OrderFactory() order.user = self.create_user() response = self.client.get(reverse(self.path, args=[order.number])) self.assertEqual(response.status_code, 302) redirect_location = get_lms_url('dashboard') self.assertEqual(response['location'], redirect_location)
def mock_footer_api_response(self): """ Helper function to register an API endpoint for the footer information. """ footer_url = get_lms_url('api/branding/v1/footer') footer_content = { 'footer': 'edX Footer' } content_json = json.dumps(footer_content) httpretty.register_uri(httpretty.GET, footer_url, body=content_json, content_type='application/json')
def mock_credit_api_error(self): """ Mock an error response when calling the Credit API providers endpoint. """ def callback(request, uri, headers): # pylint: disable=unused-argument return 500, headers, 'Failure!' url = get_lms_url('/api/credit/v1/providers/') httpretty.register_uri(httpretty.GET, url, body=callback, content_type='application/json')
def mock_credit_api_error(self): """ Mock an error response when calling the Credit API providers endpoint. """ url = get_lms_url('/api/credit/v1/providers/') responses.add(responses.GET, url, body='Failure!', content_type='application/json', status=500)
def test_payment_error_context(self): response = self.client.get(reverse('payment_error')) self.assertDictContainsSubset( { 'dashboard_url': get_lms_url(), 'payment_support_email': self.site.siteconfiguration.payment_support_email }, response.context)
def get_context_data(self, **kwargs): context = super(BasketSummaryView, self).get_context_data(**kwargs) formset = context.get('formset', []) lines = context.get('line_list', []) site_configuration = self.request.site.siteconfiguration failed_enterprise_consent_code = self.request.GET.get(CONSENT_FAILED_PARAM) if failed_enterprise_consent_code: messages.error( self.request, _("Could not apply the code '{code}'; it requires data sharing consent.").format( code=failed_enterprise_consent_code ) ) context_updates, lines_data = self._process_basket_lines(lines) context.update(context_updates) user = self.request.user context.update({ 'analytics_data': prepare_analytics_data( user, site_configuration.segment_key, ), 'enable_client_side_checkout': False, 'sdn_check': site_configuration.enable_sdn_check }) payment_processors = site_configuration.get_payment_processors() if site_configuration.client_side_payment_processor \ and waffle.flag_is_active(self.request, CLIENT_SIDE_CHECKOUT_FLAG_NAME): payment_processors_data = self._get_payment_processors_data(payment_processors) context.update(payment_processors_data) # Total benefit displayed in price summary. # Currently only one voucher per basket is supported. try: applied_voucher = self.request.basket.vouchers.first() total_benefit = ( format_benefit_value(applied_voucher.best_offer.benefit) if applied_voucher else None ) except ValueError: total_benefit = None num_of_items = self.request.basket.num_items context.update({ 'formset_lines_data': zip(formset, lines_data), 'free_basket': context['order_total'].incl_tax == 0, 'homepage_url': get_lms_url(''), 'min_seat_quantity': 1, 'max_seat_quantity': 100, 'payment_processors': payment_processors, 'total_benefit': total_benefit, 'enable_alipay_wechatpay': settings.ENABLE_ALIPAY_WECHATPAY, 'line_price': (self.request.basket.total_incl_tax_excl_discounts / num_of_items) if num_of_items > 0 else 0 }) return context
def test_get_provider_data_unavailable_request(self): """ Check if None return on the bad request """ httpretty.register_uri(httpretty.GET, get_lms_url('api/credit/v1/providers/ABC'), status=400) provider_data = get_provider_data('ABC') self.assertEqual(provider_data, None)
def test_core(self): request = get_current_request() self.assertDictEqual( core(request), { 'lms_base_url': get_lms_url(), 'lms_dashboard_url': get_lms_dashboard_url(), 'platform_name': request.site.name, 'support_url': SUPPORT_URL })
def test_get_provider_data_unavailable_request(self): """ Check if None return on the bad request """ httpretty.register_uri( httpretty.GET, get_lms_url('api/credit/v1/providers/ABC'), status=400 ) provider_data = get_provider_data('ABC') self.assertEqual(provider_data, None)
def get_course_info_from_lms(course_key): """ Get course information from LMS via the course api and cache """ api = EdxRestApiClient(get_lms_url('api/courses/v1/')) cache_key = 'courses_api_detail_{}'.format(course_key) cache_hash = hashlib.md5(cache_key).hexdigest() course = cache.get(cache_hash) if not course: # pragma: no cover course = api.courses(course_key).get() cache.set(cache_hash, course, settings.COURSES_API_CACHE_TIMEOUT) return course
def setUp(self): super(CouponRedeemViewTests, self).setUp() self.user = self.create_user() self.client.login(username=self.user.username, password=self.password) self.course = CourseFactory() self.seat = self.course.create_or_update_seat('verified', True, 50, self.partner) self.catalog = Catalog.objects.create(partner=self.partner) self.catalog.stock_records.add(StockRecord.objects.get(product=self.seat)) self.student_dashboard_url = get_lms_url(self.site.siteconfiguration.student_dashboard_url)
def get_course_info_from_lms(course_key): """ Get course information from LMS via the course api and cache """ api = EdxRestApiClient(get_lms_url('api/courses/v1/')) cache_key = 'courses_api_detail_{}'.format(course_key) cache_hash = hashlib.md5(cache_key.encode('utf-8')).hexdigest() course = cache.get(cache_hash) if not course: # pragma: no cover course = api.courses(course_key).get() cache.set(cache_hash, course, settings.COURSES_API_CACHE_TIMEOUT) return course
def _publish_creditcourse(self, course_id, access_token): """Creates or updates a CreditCourse object on the LMS.""" api = EdxRestApiClient(get_lms_url('api/credit/v1/'), oauth_access_token=access_token, timeout=self.timeout) data = {'course_key': course_id, 'enabled': True} api.courses(course_id).put(data)
def mock_creditcourse_endpoint(self, course_id, status, body=None): self.assertTrue(httpretty.is_enabled(), 'httpretty must be enabled to mock Credit API calls.') url = get_lms_url('/api/credit/v1/courses/{}/'.format(course_id)) httpretty.register_uri(httpretty.PUT, url, status=status, body=json.dumps(body), content_type=JSON)
def get_offers(self, request, voucher): """ Get the course offers associated with the voucher. Arguments: request (HttpRequest): Request data. voucher (Voucher): Oscar Voucher for which the offers are returned. Returns: dict: Dictionary containing a link to the next page of Course Discovery results and a List of course offers where each offer is represented as a dictionary. """ benefit = voucher.offers.first().benefit catalog_query = benefit.range.catalog_query catalog_id = benefit.range.course_catalog next_page = None offers = [] if catalog_id: catalog = fetch_course_catalog(request.site, catalog_id) catalog_query = catalog.get("query") if catalog else catalog_query if catalog_query: offers, next_page = self.get_offers_from_query( request, voucher, catalog_query) else: product_range = voucher.offers.first().benefit.range products = product_range.all_products() if products: product = products[0] else: raise Product.DoesNotExist course_id = product.course_id course = get_object_or_404(Course, id=course_id) stock_record = get_object_or_404(StockRecord, product__id=product.id) course_info = get_course_info_from_lms(course_id) if course_info: course_info['image'] = { 'src': get_lms_url(course_info['media']['course_image']['uri']) } offers.append( self.get_course_offer_data( benefit=benefit, course=course, course_info=course_info, credit_provider_price=None, multiple_credit_providers=False, is_verified=(course.type == 'verified'), product=product, stock_record=stock_record, voucher=voucher)) return {'next': next_page, 'results': offers}
def test_core(self): request = get_current_request() self.assertDictEqual( core(request), { 'lms_base_url': get_lms_url(), 'lms_dashboard_url': get_lms_dashboard_url(), 'platform_name': request.site.name, 'support_url': SUPPORT_URL } )
def mock_creditcourse_endpoint(self, course_id, status, body=None): self.assertTrue(httpretty.is_enabled(), 'httpretty must be enabled to mock Credit API calls.') url = get_lms_url('/api/credit/v1/courses/{}/'.format(course_id)) httpretty.register_uri( httpretty.PUT, url, status=status, body=json.dumps(body), content_type=JSON )
def send_email(self, order): """ Sends an email with enrollment code order information. """ # Note (multi-courses): Change from a course_name to a list of course names. product = order.lines.first().product course = Course.objects.get(id=product.attr.course_key) send_notification( order.user, 'ORDER_WITH_CSV', context={ 'contact_url': get_lms_url('/contact'), 'course_name': course.name, 'download_csv_link': get_ecommerce_url(reverse('coupons:enrollment_code_csv', args=[order.number])), 'enrollment_code_title': product.title, 'order_number': order.number, 'partner_name': order.site.siteconfiguration.partner.name, 'lms_url': get_lms_url(), 'receipt_page_url': get_lms_url('{}?orderNum={}'.format(settings.RECEIPT_PAGE_PATH, order.number)), }, site=order.site )
def test_get_provider_data(self): """ Check if correct data returns on the full filled request. """ httpretty.register_uri( httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'), body='{"display_name": "Arizona State University"}', content_type="application/json" ) provider_data = get_provider_data('ASU') self.assertDictEqual(provider_data, {"display_name": "Arizona State University"})
def mock_courses_api(self, status, body=None): """ Mock Courses API with specific status and body. """ body = body or {} url = get_lms_url('/api/courses/v1/courses?page_size=50&page=1') responses.add( responses.GET, url, status=status, json=body, content_type=JSON )
def test_core(self): request = get_current_request() self.assertDictEqual( core(request), { 'lms_base_url': get_lms_url(), 'lms_dashboard_url': get_lms_dashboard_url(), 'platform_name': request.site.name, 'support_url': request.site.siteconfiguration.payment_support_url, } )
def test_successful_redirect(self): """ Verify redirect to the receipt page. """ self.prepare_basket(0) self.assertEqual(Order.objects.count(), 0) receipt_page = get_lms_url(settings.RECEIPT_PAGE_PATH) response = self.client.get(self.path) self.assertEqual(Order.objects.count(), 1) expected_url = '{}?orderNum={}'.format(receipt_page, Order.objects.first().number) self.assertRedirects(response, expected_url, fetch_redirect_response=False)
def core(request): site = request.site site_configuration = site.siteconfiguration return { 'lms_base_url': get_lms_url(), 'lms_dashboard_url': get_lms_dashboard_url(), 'platform_name': site.name, 'support_url': site_configuration.payment_support_url, 'optimizely_snippet_src': site_configuration.optimizely_snippet_src, }
def mock_courses_api(self, status, body=None): """ Mock Courses API with specific status and body. """ self.assertTrue(httpretty.is_enabled(), 'httpretty must be enabled to mock Course API calls.') body = body or {} url = get_lms_url('/api/courses/v1/courses/?page_size=1') httpretty.register_uri( httpretty.GET, url, status=status, body=json.dumps(body), content_type=JSON )
def test_get_receipt_for_existing_order(self): """ Order owner should be able to see the Receipt Page.""" order = self._create_order_for_receipt(self.user) response = self._get_receipt_response(order.number) context_data = { 'payment_method': None, 'fire_tracking_events': False, 'display_credit_messaging': False, 'verification_url': get_lms_url( 'verify_student/verify-now/{course_id}'.format(course_id=self.course.id) ), } self.assertEqual(response.status_code, 200) self.assertDictContainsSubset(context_data, response.context_data)
def mock_course_api_response(self, course=None): """ Helper function to register an API endpoint for the course information. """ course_info = { 'media': { 'course_image': { 'uri': '/asset-v1:test+test+test+type@asset+block@images_course_image.jpg' } }, 'name': course.name if course else 'Test course', 'org': 'test' } course_info_json = json.dumps(course_info) course_id = course.id if course else 'course-v1:test+test+test' course_url = get_lms_url('api/courses/v1/courses/{}/'.format(course_id)) httpretty.register_uri(httpretty.GET, course_url, body=course_info_json, content_type='application/json')
def setUp(self): super(CouponRedeemViewTests, self).setUp() self.user = self.create_user(email='*****@*****.**') self.client.login(username=self.user.username, password=self.password) self.course_mode = 'verified' self.course, self.seat = self.create_course_and_seat( seat_type=self.course_mode, id_verification=True, price=50, partner=self.partner ) self.stock_record = StockRecord.objects.get(product=self.seat) self.catalog = Catalog.objects.create(partner=self.partner) self.catalog.stock_records.add(StockRecord.objects.get(product=self.seat)) self.student_dashboard_url = get_lms_url(self.site.siteconfiguration.student_dashboard_url)
def _publish_creditcourse(self, course_id, access_token): """Creates or updates a CreditCourse object on the LMS.""" api = EdxRestApiClient( get_lms_url('api/credit/v1/'), oauth_access_token=access_token, timeout=self.timeout ) data = { 'course_key': course_id, 'enabled': True } api.courses(course_id).put(data)
def _get_courses_enrollment_info(self): """ Retrieve the enrollment information for all the courses. Returns: Dictionary representing the key-value pair (course_key, enrollment_end) of course. """ def _parse_response(api_response): response_data = api_response.get('results', []) # Map course_id with enrollment end date. courses_enrollment = dict( (course_info['course_id'], course_info['enrollment_end']) for course_info in response_data ) return courses_enrollment, api_response['pagination'].get('next', None) querystring = {'page_size': 50} api = EdxRestApiClient(get_lms_url('api/courses/v1/')) course_enrollments = {} page = 0 throttling_attempts = 0 next_page = True while next_page: page += 1 querystring['page'] = page try: response = api.courses().get(**querystring) throttling_attempts = 0 except HttpClientError as exc: # this is a known limitation; If we get HTTP429, we need to pause execution for a few seconds # before re-requesting the data. raise any other errors if exc.response.status_code == 429 and throttling_attempts < self.max_tries: logger.warning( 'API calls are being rate-limited. Waiting for [%d] seconds before retrying...', self.pause_time ) time.sleep(self.pause_time) page -= 1 throttling_attempts += 1 logger.info('Retrying [%d]...', throttling_attempts) continue else: raise enrollment_info, next_page = _parse_response(response) course_enrollments.update(enrollment_info) return course_enrollments