Merge pull request #23972 from edx/diana/remove-shoppingcart-references
Remove shoppingcart references from verify_student and instructor.
This commit is contained in:
@@ -72,17 +72,6 @@ from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
from openedx.core.lib.teams_config import TeamsConfig
|
||||
from openedx.core.lib.xblock_utils import grade_histogram
|
||||
from openedx.features.course_experience import RELATIVE_DATES_FLAG
|
||||
from shoppingcart.models import (
|
||||
Coupon,
|
||||
CouponRedemption,
|
||||
CourseRegistrationCode,
|
||||
CourseRegistrationCodeInvoiceItem,
|
||||
Invoice,
|
||||
InvoiceTransaction,
|
||||
Order,
|
||||
PaidCourseRegistration,
|
||||
RegistrationCodeRedemption
|
||||
)
|
||||
from student.models import (
|
||||
ALLOWEDTOENROLL_TO_ENROLLED,
|
||||
ALLOWEDTOENROLL_TO_UNENROLLED,
|
||||
@@ -115,8 +104,6 @@ EXPECTED_CSV_HEADER = (
|
||||
'"code","redeem_code_url","course_id","company_name","created_by","redeemed_by","invoice_id","purchaser",'
|
||||
'"customer_reference_number","internal_reference"'
|
||||
)
|
||||
EXPECTED_COUPON_CSV_HEADER = u'"Coupon Code","Course Id","% Discount","Description","Expiration Date",' \
|
||||
u'"Is Active","Code Redeemed Count","Total Discounted Seats","Total Discounted Amount"'
|
||||
|
||||
# ddt data for test cases involving reports
|
||||
REPORTS_DATA = (
|
||||
@@ -172,35 +159,27 @@ EXECUTIVE_SUMMARY_DATA = (
|
||||
|
||||
INSTRUCTOR_GET_ENDPOINTS = set([
|
||||
'get_anon_ids',
|
||||
'get_coupon_codes',
|
||||
'get_issued_certificates',
|
||||
'get_sale_order_records',
|
||||
'get_sale_records',
|
||||
])
|
||||
INSTRUCTOR_POST_ENDPOINTS = set([
|
||||
'active_registration_codes',
|
||||
'add_users_to_cohorts',
|
||||
'bulk_beta_modify_access',
|
||||
'calculate_grades_csv',
|
||||
'change_due_date',
|
||||
'export_ora2_data',
|
||||
'generate_registration_codes',
|
||||
'get_enrollment_report',
|
||||
'get_exec_summary_report',
|
||||
'get_grading_config',
|
||||
'get_problem_responses',
|
||||
'get_proctored_exam_results',
|
||||
'get_registration_codes',
|
||||
'get_student_enrollment_status',
|
||||
'get_student_progress_url',
|
||||
'get_students_features',
|
||||
'get_students_who_may_enroll',
|
||||
'get_user_invoice_preference',
|
||||
'list_background_email_tasks',
|
||||
'list_course_role_members',
|
||||
'list_email_content',
|
||||
'list_entrance_exam_instructor_tasks',
|
||||
'list_financial_report_downloads',
|
||||
'list_forum_members',
|
||||
'list_instructor_tasks',
|
||||
'list_report_downloads',
|
||||
@@ -212,11 +191,9 @@ INSTRUCTOR_POST_ENDPOINTS = set([
|
||||
'reset_due_date',
|
||||
'reset_student_attempts',
|
||||
'reset_student_attempts_for_entrance_exam',
|
||||
'sale_validation',
|
||||
'show_student_extensions',
|
||||
'show_unit_extensions',
|
||||
'send_email',
|
||||
'spent_registration_codes',
|
||||
'students_update_enrollment',
|
||||
'update_forum_role_membership',
|
||||
'override_problem_score',
|
||||
@@ -465,7 +442,6 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest
|
||||
('list_instructor_tasks', {}),
|
||||
('list_background_email_tasks', {}),
|
||||
('list_report_downloads', {}),
|
||||
('list_financial_report_downloads', {}),
|
||||
('calculate_grades_csv', {}),
|
||||
('get_students_features', {}),
|
||||
('get_enrollment_report', {}),
|
||||
@@ -1062,32 +1038,6 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(SharedModuleStoreTestCas
|
||||
for enrollment in manual_enrollments:
|
||||
self.assertEqual(enrollment.enrollment.mode, CourseMode.HONOR)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'ALLOW_AUTOMATED_SIGNUPS': True})
|
||||
def test_default_shopping_cart_enrollment_mode_for_white_label(self):
|
||||
"""
|
||||
Test that enrollment mode for white label courses (paid courses) is DEFAULT_SHOPPINGCART_MODE_SLUG.
|
||||
"""
|
||||
# Login white label course instructor
|
||||
self.client.login(username=self.white_label_course_instructor.username, password='test')
|
||||
|
||||
csv_content = b"test_student_wl@example.com,test_student_wl,Test Student,USA"
|
||||
uploaded_file = SimpleUploadedFile("temp.csv", csv_content)
|
||||
response = self.client.post(self.white_label_course_url, {'students_list': uploaded_file})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(len(data['row_errors']), 0)
|
||||
self.assertEqual(len(data['warnings']), 0)
|
||||
self.assertEqual(len(data['general_errors']), 0)
|
||||
|
||||
manual_enrollments = ManualEnrollmentAudit.objects.all()
|
||||
self.assertEqual(manual_enrollments.count(), 1)
|
||||
self.assertEqual(manual_enrollments[0].state_transition, UNENROLLED_TO_ENROLLED)
|
||||
|
||||
# Verify enrollment modes to be CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG
|
||||
for enrollment in manual_enrollments:
|
||||
self.assertEqual(enrollment.enrollment.mode, CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestInstructorAPIEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
@@ -2612,24 +2562,6 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
CourseDataResearcherRole(self.course.id).add_users(self.instructor)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
self.cart = Order.get_cart_for_user(self.instructor)
|
||||
self.coupon_code = 'abcde'
|
||||
self.coupon = Coupon(code=self.coupon_code, description='testing code', course_id=self.course.id,
|
||||
percentage_discount=10, created_by=self.instructor, is_active=True)
|
||||
self.coupon.save()
|
||||
|
||||
# Create testing invoice 1
|
||||
self.sale_invoice_1 = Invoice.objects.create(
|
||||
total_amount=1234.32, company_name='Test1', company_contact_name='TestName', company_contact_email='Test@company.com',
|
||||
recipient_name='Testw', recipient_email='test1@test.com', customer_reference_number='2Fwe23S',
|
||||
internal_reference="A", course_id=self.course.id, is_valid=True
|
||||
)
|
||||
self.invoice_item = CourseRegistrationCodeInvoiceItem.objects.create(
|
||||
invoice=self.sale_invoice_1,
|
||||
qty=1,
|
||||
unit_price=1234.32,
|
||||
course_id=self.course.id
|
||||
)
|
||||
|
||||
self.students = [UserFactory() for _ in range(6)]
|
||||
for student in self.students:
|
||||
@@ -2641,292 +2573,6 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
|
||||
email=student.email, course_id=self.course.id
|
||||
)
|
||||
|
||||
def register_with_redemption_code(self, user, code):
|
||||
"""
|
||||
enroll user using a registration code
|
||||
"""
|
||||
redeem_url = reverse('register_code_redemption', args=[code], is_dashboard_endpoint=False)
|
||||
self.client.login(username=user.username, password='test')
|
||||
response = self.client.get(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# check button text
|
||||
self.assertContains(response, 'Activate Course Enrollment')
|
||||
|
||||
response = self.client.post(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_invalidate_sale_record(self):
|
||||
"""
|
||||
Testing the sale invalidating scenario.
|
||||
"""
|
||||
for i in range(2):
|
||||
course_registration_code = CourseRegistrationCode(
|
||||
code='sale_invoice{}'.format(i),
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice_1,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor'
|
||||
)
|
||||
course_registration_code.save()
|
||||
|
||||
data = {'invoice_number': self.sale_invoice_1.id, 'event_type': "invalidate"}
|
||||
url = reverse('sale_validation', kwargs={'course_id': text_type(self.course.id)})
|
||||
self.assert_request_status_code(200, url, method="POST", data=data)
|
||||
|
||||
#Now try to fetch data against not existing invoice number
|
||||
test_data_1 = {'invoice_number': 100, 'event_type': "invalidate"}
|
||||
self.assert_request_status_code(404, url, method="POST", data=test_data_1)
|
||||
|
||||
# Now invalidate the same invoice number and expect an Bad request
|
||||
response = self.assert_request_status_code(400, url, method="POST", data=data)
|
||||
self.assertContains(
|
||||
response,
|
||||
"The sale associated with this invoice has already been invalidated.",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
# now re_validate the invoice number
|
||||
data['event_type'] = "re_validate"
|
||||
self.assert_request_status_code(200, url, method="POST", data=data)
|
||||
|
||||
# Now re_validate the same active invoice number and expect an Bad request
|
||||
response = self.assert_request_status_code(400, url, method="POST", data=data)
|
||||
self.assertContains(response, "This invoice is already active.", status_code=400)
|
||||
|
||||
test_data_2 = {'invoice_number': self.sale_invoice_1.id}
|
||||
response = self.assert_request_status_code(400, url, method="POST", data=test_data_2)
|
||||
self.assertContains(response, "Missing required event_type parameter", status_code=400)
|
||||
|
||||
test_data_3 = {'event_type': "re_validate"}
|
||||
response = self.assert_request_status_code(400, url, method="POST", data=test_data_3)
|
||||
self.assertContains(response, "Missing required invoice_number parameter", status_code=400)
|
||||
|
||||
# submitting invalid invoice number
|
||||
data['invoice_number'] = 'testing'
|
||||
response = self.assert_request_status_code(400, url, method="POST", data=data)
|
||||
self.assertContains(
|
||||
response,
|
||||
u"invoice_number must be an integer, {value} provided".format(value=data['invoice_number']),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
def test_get_sale_order_records_features_csv(self):
|
||||
"""
|
||||
Test that the response from get_sale_order_records is in csv format.
|
||||
"""
|
||||
# add the coupon code for the course
|
||||
coupon = Coupon(
|
||||
code='test_code', description='test_description', course_id=self.course.id,
|
||||
percentage_discount='10', created_by=self.instructor, is_active=True
|
||||
)
|
||||
coupon.save()
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
self.cart.add_billing_details(company_name='Test Company', company_contact_name='Test',
|
||||
company_contact_email='test@123', recipient_name='R1',
|
||||
recipient_email='', customer_reference_number='PO#23')
|
||||
|
||||
paid_course_reg_item = PaidCourseRegistration.add_to_order(
|
||||
self.cart,
|
||||
self.course.id,
|
||||
mode_slug=CourseMode.HONOR
|
||||
)
|
||||
# update the quantity of the cart item paid_course_reg_item
|
||||
resp = self.client.post(
|
||||
reverse('shoppingcart.views.update_user_cart', is_dashboard_endpoint=False),
|
||||
{'ItemId': paid_course_reg_item.id, 'qty': '4'}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
# apply the coupon code to the item in the cart
|
||||
resp = self.client.post(
|
||||
reverse('shoppingcart.views.use_code', is_dashboard_endpoint=False),
|
||||
{'code': coupon.code}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.cart.purchase()
|
||||
# get the updated item
|
||||
item = self.cart.orderitem_set.all().select_subclasses()[0]
|
||||
# get the redeemed coupon information
|
||||
coupon_redemption = CouponRedemption.objects.select_related('coupon').filter(order=self.cart)
|
||||
|
||||
sale_order_url = reverse('get_sale_order_records', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(sale_order_url)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
self.assertIn('36', response.content.decode('utf-8').split('\r\n')[1])
|
||||
self.assertIn(str(item.unit_cost), response.content.decode('utf-8').split('\r\n')[1],)
|
||||
self.assertIn(str(item.list_price), response.content.decode('utf-8').split('\r\n')[1],)
|
||||
self.assertIn(item.status, response.content.decode('utf-8').split('\r\n')[1],)
|
||||
self.assertIn(coupon_redemption[0].coupon.code, response.content.decode('utf-8').split('\r\n')[1],)
|
||||
|
||||
def test_coupon_redeem_count_in_ecommerce_section(self):
|
||||
"""
|
||||
Test that checks the redeem count in the instructor_dashboard coupon section
|
||||
"""
|
||||
# add the coupon code for the course
|
||||
coupon = Coupon(
|
||||
code='test_code', description='test_description', course_id=self.course.id,
|
||||
percentage_discount='10', created_by=self.instructor, is_active=True
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
# Coupon Redeem Count only visible for Financial Admins.
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course.id)
|
||||
# apply the coupon code to the item in the cart
|
||||
resp = self.client.post(
|
||||
reverse('shoppingcart.views.use_code', is_dashboard_endpoint=False),
|
||||
{'code': coupon.code}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# URL for instructor dashboard
|
||||
instructor_dashboard = reverse(
|
||||
'instructor_dashboard',
|
||||
kwargs={'course_id': text_type(self.course.id)},
|
||||
is_dashboard_endpoint=False
|
||||
)
|
||||
# visit the instructor dashboard page and
|
||||
# check that the coupon redeem count should be 0
|
||||
resp = self.client.get(instructor_dashboard)
|
||||
self.assertContains(resp, 'Number Redeemed')
|
||||
self.assertContains(resp, '<td>0</td>')
|
||||
|
||||
# now make the payment of your cart items
|
||||
self.cart.purchase()
|
||||
# visit the instructor dashboard page and
|
||||
# check that the coupon redeem count should be 1
|
||||
resp = self.client.get(instructor_dashboard)
|
||||
|
||||
self.assertContains(resp, 'Number Redeemed')
|
||||
self.assertContains(resp, '<td>1</td>')
|
||||
|
||||
def test_get_sale_records_features_csv(self):
|
||||
"""
|
||||
Test that the response from get_sale_records is in csv format.
|
||||
"""
|
||||
for i in range(2):
|
||||
course_registration_code = CourseRegistrationCode(
|
||||
code='sale_invoice{}'.format(i),
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice_1,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor'
|
||||
)
|
||||
course_registration_code.save()
|
||||
|
||||
url = reverse(
|
||||
'get_sale_records',
|
||||
kwargs={'course_id': text_type(self.course.id)}
|
||||
)
|
||||
response = self.client.post(url + '/csv', {})
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
|
||||
def test_get_sale_records_features_json(self):
|
||||
"""
|
||||
Test that the response from get_sale_records is in json format.
|
||||
"""
|
||||
for i in range(5):
|
||||
course_registration_code = CourseRegistrationCode(
|
||||
code='sale_invoice{}'.format(i),
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice_1,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor'
|
||||
)
|
||||
course_registration_code.save()
|
||||
|
||||
url = reverse('get_sale_records', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
res_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertIn('sale', res_json)
|
||||
|
||||
for res in res_json['sale']:
|
||||
self.validate_sale_records_response(
|
||||
res,
|
||||
course_registration_code,
|
||||
self.sale_invoice_1,
|
||||
0,
|
||||
invoice_item=self.invoice_item
|
||||
)
|
||||
|
||||
def test_get_sale_records_features_with_multiple_invoices(self):
|
||||
"""
|
||||
Test that the response from get_sale_records is in json format for multiple invoices
|
||||
"""
|
||||
for i in range(5):
|
||||
course_registration_code = CourseRegistrationCode(
|
||||
code='qwerty{}'.format(i),
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice_1,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor'
|
||||
)
|
||||
course_registration_code.save()
|
||||
|
||||
# Create test invoice 2
|
||||
sale_invoice_2 = Invoice.objects.create(
|
||||
total_amount=1234.32, company_name='Test1', company_contact_name='TestName', company_contact_email='Test@company.com',
|
||||
recipient_name='Testw_2', recipient_email='test2@test.com', customer_reference_number='2Fwe23S',
|
||||
internal_reference="B", course_id=self.course.id
|
||||
)
|
||||
|
||||
invoice_item_2 = CourseRegistrationCodeInvoiceItem.objects.create(
|
||||
invoice=sale_invoice_2,
|
||||
qty=1,
|
||||
unit_price=1234.32,
|
||||
course_id=self.course.id
|
||||
)
|
||||
|
||||
for i in range(5):
|
||||
course_registration_code = CourseRegistrationCode(
|
||||
code='xyzmn{}'.format(i), course_id=text_type(self.course.id),
|
||||
created_by=self.instructor, invoice=sale_invoice_2, invoice_item=invoice_item_2, mode_slug='honor'
|
||||
)
|
||||
course_registration_code.save()
|
||||
|
||||
url = reverse('get_sale_records', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
res_json = json.loads(response.content.decode('utf-8'))
|
||||
self.assertIn('sale', res_json)
|
||||
|
||||
self.validate_sale_records_response(
|
||||
res_json['sale'][0],
|
||||
course_registration_code,
|
||||
self.sale_invoice_1,
|
||||
0,
|
||||
invoice_item=self.invoice_item
|
||||
)
|
||||
self.validate_sale_records_response(
|
||||
res_json['sale'][1],
|
||||
course_registration_code,
|
||||
sale_invoice_2,
|
||||
0,
|
||||
invoice_item=invoice_item_2
|
||||
)
|
||||
|
||||
def validate_sale_records_response(self, res, course_registration_code, invoice, used_codes, invoice_item):
|
||||
"""
|
||||
validate sale records attribute values with the response object
|
||||
"""
|
||||
self.assertEqual(res['total_amount'], invoice.total_amount)
|
||||
self.assertEqual(res['recipient_email'], invoice.recipient_email)
|
||||
self.assertEqual(res['recipient_name'], invoice.recipient_name)
|
||||
self.assertEqual(res['company_name'], invoice.company_name)
|
||||
self.assertEqual(res['company_contact_name'], invoice.company_contact_name)
|
||||
self.assertEqual(res['company_contact_email'], invoice.company_contact_email)
|
||||
self.assertEqual(res['internal_reference'], invoice.internal_reference)
|
||||
self.assertEqual(res['customer_reference_number'], invoice.customer_reference_number)
|
||||
self.assertEqual(res['invoice_number'], invoice.id)
|
||||
self.assertEqual(res['created_by'], course_registration_code.created_by.username)
|
||||
self.assertEqual(res['course_id'], text_type(invoice_item.course_id))
|
||||
self.assertEqual(res['total_used_codes'], used_codes)
|
||||
self.assertEqual(res['total_codes'], 5)
|
||||
|
||||
def test_get_problem_responses_invalid_location(self):
|
||||
"""
|
||||
Test whether get_problem_responses returns an appropriate status
|
||||
@@ -3145,148 +2791,6 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
|
||||
decorated_func(request, text_type(self.course.id))
|
||||
self.assertTrue(func.called)
|
||||
|
||||
def test_enrollment_report_features_csv(self):
|
||||
"""
|
||||
test to generate enrollment report.
|
||||
enroll users, admin staff using registration codes.
|
||||
"""
|
||||
InvoiceTransaction.objects.create(
|
||||
invoice=self.sale_invoice_1,
|
||||
amount=self.sale_invoice_1.total_amount,
|
||||
status='completed',
|
||||
created_by=self.instructor,
|
||||
last_modified_by=self.instructor
|
||||
)
|
||||
course_registration_code = CourseRegistrationCode.objects.create(
|
||||
code='abcde',
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice_1,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor'
|
||||
)
|
||||
|
||||
admin_user = AdminFactory()
|
||||
admin_cart = Order.get_cart_for_user(admin_user)
|
||||
PaidCourseRegistration.add_to_order(admin_cart, self.course.id)
|
||||
admin_cart.purchase()
|
||||
|
||||
# create a new user/student and enroll
|
||||
# in the course using a registration code
|
||||
# and then validates the generated detailed enrollment report
|
||||
test_user = UserFactory()
|
||||
self.register_with_redemption_code(test_user, course_registration_code.code)
|
||||
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
UserProfileFactory.create(user=self.students[0], meta='{"company": "asdasda"}')
|
||||
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
url = reverse('get_enrollment_report', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
self.assertContains(response, 'The detailed enrollment report is being created.')
|
||||
|
||||
def test_bulk_purchase_detailed_report(self):
|
||||
"""
|
||||
test to generate detailed enrollment report.
|
||||
1 Purchase registration codes.
|
||||
2 Enroll users via registration code.
|
||||
3 Validate generated enrollment report.
|
||||
"""
|
||||
paid_course_reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course.id)
|
||||
# update the quantity of the cart item paid_course_reg_item
|
||||
resp = self.client.post(
|
||||
reverse('shoppingcart.views.update_user_cart', is_dashboard_endpoint=False),
|
||||
{'ItemId': paid_course_reg_item.id, 'qty': '4'}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
# apply the coupon code to the item in the cart
|
||||
resp = self.client.post(
|
||||
reverse('shoppingcart.views.use_code', is_dashboard_endpoint=False),
|
||||
{'code': self.coupon_code}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.cart.purchase()
|
||||
|
||||
course_reg_codes = CourseRegistrationCode.objects.filter(order=self.cart)
|
||||
self.register_with_redemption_code(self.instructor, course_reg_codes[0].code)
|
||||
|
||||
test_user = UserFactory()
|
||||
test_user_cart = Order.get_cart_for_user(test_user)
|
||||
PaidCourseRegistration.add_to_order(test_user_cart, self.course.id)
|
||||
test_user_cart.purchase()
|
||||
InvoiceTransaction.objects.create(
|
||||
invoice=self.sale_invoice_1,
|
||||
amount=-self.sale_invoice_1.total_amount,
|
||||
status='refunded',
|
||||
created_by=self.instructor,
|
||||
last_modified_by=self.instructor
|
||||
)
|
||||
course_registration_code = CourseRegistrationCode.objects.create(
|
||||
code='abcde',
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice_1,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor'
|
||||
)
|
||||
|
||||
test_user1 = UserFactory()
|
||||
self.register_with_redemption_code(test_user1, course_registration_code.code)
|
||||
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
|
||||
url = reverse('get_enrollment_report', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
self.assertContains(response, 'The detailed enrollment report is being created.')
|
||||
|
||||
def test_create_registration_code_without_invoice_and_order(self):
|
||||
"""
|
||||
test generate detailed enrollment report,
|
||||
used a registration codes which has been created via invoice or bulk
|
||||
purchase scenario.
|
||||
"""
|
||||
course_registration_code = CourseRegistrationCode.objects.create(
|
||||
code='abcde',
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
mode_slug='honor'
|
||||
)
|
||||
test_user1 = UserFactory()
|
||||
self.register_with_redemption_code(test_user1, course_registration_code.code)
|
||||
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
|
||||
url = reverse('get_enrollment_report', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
self.assertContains(response, 'The detailed enrollment report is being created.')
|
||||
|
||||
def test_invoice_payment_is_still_pending_for_registration_codes(self):
|
||||
"""
|
||||
test generate enrollment report
|
||||
enroll a user in a course using registration code
|
||||
whose invoice has not been paid yet
|
||||
"""
|
||||
course_registration_code = CourseRegistrationCode.objects.create(
|
||||
code='abcde',
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice_1,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor'
|
||||
)
|
||||
|
||||
test_user1 = UserFactory()
|
||||
self.register_with_redemption_code(test_user1, course_registration_code.code)
|
||||
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
|
||||
url = reverse('get_enrollment_report', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(url, {})
|
||||
self.assertContains(response, 'The detailed enrollment report is being created.')
|
||||
|
||||
@patch('lms.djangoapps.instructor.views.api.anonymous_id_for_user', Mock(return_value='42'))
|
||||
@patch('lms.djangoapps.instructor.views.api.unique_id_for_user', Mock(return_value='41'))
|
||||
def test_get_anon_ids(self):
|
||||
@@ -4792,448 +4296,6 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase):
|
||||
)
|
||||
|
||||
|
||||
@override_settings(REGISTRATION_CODE_LENGTH=8)
|
||||
class TestCourseRegistrationCodes(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Test data dumps for E-commerce Course Registration Codes.
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCourseRegistrationCodes, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
cls.url = reverse(
|
||||
'generate_registration_codes',
|
||||
kwargs={'course_id': text_type(cls.course.id)}
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Fixtures.
|
||||
"""
|
||||
super(TestCourseRegistrationCodes, self).setUp()
|
||||
|
||||
CourseModeFactory.create(course_id=self.course.id, min_price=50)
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
CourseSalesAdminRole(self.course.id).add_users(self.instructor)
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 12, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street',
|
||||
'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(self.url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
for i in range(5):
|
||||
order = Order(user=self.instructor, status='purchased')
|
||||
order.save()
|
||||
|
||||
# Spent(used) Registration Codes
|
||||
for i in range(5):
|
||||
i += 1
|
||||
registration_code_redemption = RegistrationCodeRedemption(
|
||||
registration_code_id=i,
|
||||
redeemed_by=self.instructor
|
||||
)
|
||||
registration_code_redemption.save()
|
||||
|
||||
@override_settings(FINANCE_EMAIL='finance@example.com')
|
||||
def test_finance_email_in_recipient_list_when_generating_registration_codes(self):
|
||||
"""
|
||||
Test to verify that the invoice will also be sent to the FINANCE_EMAIL when
|
||||
generating registration codes
|
||||
"""
|
||||
url_reg_code = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 5, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 121.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': 'True'
|
||||
}
|
||||
|
||||
response = self.client.post(url_reg_code, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
# check for the last mail.outbox, The FINANCE_EMAIL has been appended at the
|
||||
# very end, when generating registration codes
|
||||
self.assertEqual(mail.outbox[-1].to[0], 'finance@example.com')
|
||||
|
||||
def test_user_invoice_copy_preference(self):
|
||||
"""
|
||||
Test to remember user invoice copy preference
|
||||
"""
|
||||
url_reg_code = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 5, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 121.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': 'True'
|
||||
}
|
||||
|
||||
# user invoice copy preference will be saved in api user preference; model
|
||||
response = self.client.post(url_reg_code, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
|
||||
# get user invoice copy preference.
|
||||
url_user_invoice_preference = reverse('get_user_invoice_preference',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
response = self.client.post(url_user_invoice_preference, data)
|
||||
result = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(result['invoice_copy'], True)
|
||||
|
||||
# updating the user invoice copy preference during code generation flow
|
||||
data['invoice'] = ''
|
||||
response = self.client.post(url_reg_code, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
|
||||
# get user invoice copy preference.
|
||||
url_user_invoice_preference = reverse('get_user_invoice_preference',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
response = self.client.post(url_user_invoice_preference, data)
|
||||
result = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(result['invoice_copy'], False)
|
||||
|
||||
def test_generate_course_registration_codes_csv(self):
|
||||
"""
|
||||
Test to generate a response of all the generated course registration codes
|
||||
"""
|
||||
url = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 15, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
self.assertEqual(len(body.split('\n')), 17)
|
||||
|
||||
def test_generate_course_registration_with_redeem_url_codes_csv(self):
|
||||
"""
|
||||
Test to generate a response of all the generated course registration codes
|
||||
"""
|
||||
url = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 15, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(url, data, **{'HTTP_HOST': 'localhost'})
|
||||
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
self.assertEqual(len(body.split('\n')), 17)
|
||||
rows = body.split('\n')
|
||||
index = 1
|
||||
while index < len(rows):
|
||||
if rows[index]:
|
||||
row_data = rows[index].split(',')
|
||||
code = row_data[0].replace('"', '')
|
||||
self.assertTrue(row_data[1].startswith('"http')
|
||||
and row_data[1].endswith('/shoppingcart/register/redeem/{0}/"'.format(code)))
|
||||
index += 1
|
||||
|
||||
@patch('lms.djangoapps.instructor.views.api.random_code_generator',
|
||||
Mock(side_effect=['first', 'second', 'third', 'fourth']))
|
||||
def test_generate_course_registration_codes_matching_existing_coupon_code(self):
|
||||
"""
|
||||
Test the generated course registration code is already in the Coupon Table
|
||||
"""
|
||||
url = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
coupon = Coupon(code='first', course_id=text_type(self.course.id), created_by=self.instructor)
|
||||
coupon.save()
|
||||
data = {
|
||||
'total_registration_codes': 3, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
self.assertEqual(len(body.split('\n')), 5) # 1 for headers, 1 for new line at the end and 3 for the actual data
|
||||
|
||||
@patch('lms.djangoapps.instructor.views.api.random_code_generator',
|
||||
Mock(side_effect=['first', 'first', 'second', 'third']))
|
||||
def test_generate_course_registration_codes_integrity_error(self):
|
||||
"""
|
||||
Test for the Integrity error against the generated code
|
||||
"""
|
||||
url = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 2, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
self.assertEqual(len(body.split('\n')), 4)
|
||||
|
||||
def test_spent_course_registration_codes_csv(self):
|
||||
"""
|
||||
Test to generate a response of all the spent course registration codes
|
||||
"""
|
||||
url = reverse('spent_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {'spent_company_name': ''}
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
|
||||
self.assertEqual(len(body.split('\n')), 7)
|
||||
|
||||
generate_code_url = reverse(
|
||||
'generate_registration_codes', kwargs={'course_id': text_type(self.course.id)}
|
||||
)
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com',
|
||||
'unit_price': 122.45, 'company_contact_email': 'Test@company.com', 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(generate_code_url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
|
||||
for i in range(9):
|
||||
order = Order(user=self.instructor, status='purchased')
|
||||
order.save()
|
||||
|
||||
# Spent(used) Registration Codes
|
||||
for i in range(9):
|
||||
i += 13
|
||||
registration_code_redemption = RegistrationCodeRedemption(
|
||||
registration_code_id=i,
|
||||
redeemed_by=self.instructor
|
||||
)
|
||||
registration_code_redemption.save()
|
||||
|
||||
data = {'spent_company_name': 'Group Alpha'}
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
self.assertEqual(len(body.split('\n')), 11)
|
||||
|
||||
def test_active_course_registration_codes_csv(self):
|
||||
"""
|
||||
Test to generate a response of all the active course registration codes
|
||||
"""
|
||||
url = reverse('active_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {'active_company_name': ''}
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
self.assertEqual(len(body.split('\n')), 9)
|
||||
|
||||
generate_code_url = reverse(
|
||||
'generate_registration_codes', kwargs={'course_id': text_type(self.course.id)}
|
||||
)
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(generate_code_url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
|
||||
data = {'active_company_name': 'Group Alpha'}
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
self.assertEqual(len(body.split('\n')), 11)
|
||||
|
||||
def test_get_all_course_registration_codes_csv(self):
|
||||
"""
|
||||
Test to generate a response of all the course registration codes
|
||||
"""
|
||||
url = reverse(
|
||||
'get_registration_codes', kwargs={'course_id': text_type(self.course.id)}
|
||||
)
|
||||
|
||||
data = {'download_company_name': ''}
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
self.assertEqual(len(body.split('\n')), 14)
|
||||
|
||||
generate_code_url = reverse(
|
||||
'generate_registration_codes', kwargs={'course_id': text_type(self.course.id)}
|
||||
)
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(generate_code_url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
|
||||
data = {'download_company_name': 'Group Alpha'}
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
self.assertEqual(len(body.split('\n')), 11)
|
||||
|
||||
def test_get_codes_with_sale_invoice(self):
|
||||
"""
|
||||
Test to generate a response of all the course registration codes
|
||||
"""
|
||||
generate_code_url = reverse(
|
||||
'generate_registration_codes', kwargs={'course_id': text_type(self.course.id)}
|
||||
)
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 5.5, 'company_name': 'Group Invoice', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': True
|
||||
}
|
||||
|
||||
response = self.client.post(generate_code_url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
|
||||
url = reverse('get_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
data = {'download_company_name': 'Group Invoice'}
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_CSV_HEADER))
|
||||
|
||||
def test_with_invalid_unit_price(self):
|
||||
"""
|
||||
Test to generate a response of all the course registration codes
|
||||
"""
|
||||
generate_code_url = reverse(
|
||||
'generate_registration_codes', kwargs={'course_id': text_type(self.course.id)}
|
||||
)
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 10, 'company_name': 'Group Invoice', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 'invalid', 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': True
|
||||
}
|
||||
|
||||
response = self.client.post(generate_code_url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertContains(response, 'Could not parse amount as', status_code=400)
|
||||
|
||||
def test_get_historical_coupon_codes(self):
|
||||
"""
|
||||
Test to download a response of all the active coupon codes
|
||||
"""
|
||||
get_coupon_code_url = reverse(
|
||||
'get_coupon_codes', kwargs={'course_id': text_type(self.course.id)}
|
||||
)
|
||||
for i in range(10):
|
||||
coupon = Coupon(
|
||||
code='test_code{0}'.format(i), description='test_description', course_id=self.course.id,
|
||||
percentage_discount='{0}'.format(i), created_by=self.instructor, is_active=True
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
#now create coupons with the expiration dates
|
||||
for i in range(5):
|
||||
coupon = Coupon(
|
||||
code='coupon{0}'.format(i), description='test_description', course_id=self.course.id,
|
||||
percentage_discount='{0}'.format(i), created_by=self.instructor, is_active=True,
|
||||
expiration_date=datetime.datetime.now(UTC) + datetime.timedelta(days=2)
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
response = self.client.post(get_coupon_code_url)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
# filter all the coupons
|
||||
for coupon in Coupon.objects.all():
|
||||
self.assertIn(
|
||||
'"{coupon_code}","{course_id}","{discount}","{description}","{expiration_date}","{is_active}",'
|
||||
'"{code_redeemed_count}","{total_discounted_seats}","{total_discounted_amount}"'.format(
|
||||
coupon_code=coupon.code,
|
||||
course_id=coupon.course_id,
|
||||
discount=coupon.percentage_discount,
|
||||
description=coupon.description,
|
||||
expiration_date=coupon.display_expiry_date,
|
||||
is_active=coupon.is_active,
|
||||
code_redeemed_count="0",
|
||||
total_discounted_seats="0",
|
||||
total_discounted_amount="0",
|
||||
), response.content.decode("utf-8")
|
||||
)
|
||||
|
||||
self.assertEqual(response['Content-Type'], 'text/csv')
|
||||
body = response.content.decode('utf-8').replace('\r', '')
|
||||
self.assertTrue(body.startswith(EXPECTED_COUPON_CSV_HEADER))
|
||||
|
||||
|
||||
class TestBulkCohorting(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Test adding users to cohorts in bulk via CSV upload.
|
||||
|
||||
@@ -1,412 +0,0 @@
|
||||
"""
|
||||
Unit tests for Ecommerce feature flag in new instructor dashboard.
|
||||
"""
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
import pytz
|
||||
import six
|
||||
from django.urls import reverse
|
||||
from six import text_type
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
|
||||
from shoppingcart.models import Coupon, CourseRegistrationCode
|
||||
from student.roles import CourseFinanceAdminRole
|
||||
from student.tests.factories import AdminFactory
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
class TestECommerceDashboardViews(SiteMixin, SharedModuleStoreTestCase):
|
||||
"""
|
||||
Check for E-commerce view on the new instructor dashboard
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestECommerceDashboardViews, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
# URL for instructor dash
|
||||
cls.url = reverse('instructor_dashboard', kwargs={'course_id': text_type(cls.course.id)})
|
||||
cls.ecommerce_link = '<button type="button" class="btn-link e-commerce" data-section="e-commerce">E-Commerce</button>'
|
||||
|
||||
def setUp(self):
|
||||
super(TestECommerceDashboardViews, self).setUp()
|
||||
|
||||
# Create instructor account
|
||||
self.instructor = AdminFactory.create()
|
||||
self.client.login(username=self.instructor.username, password="test")
|
||||
mode = CourseModeFactory(
|
||||
course_id=text_type(self.course.id), mode_slug='honor',
|
||||
mode_display_name='honor', min_price=10, currency='usd'
|
||||
)
|
||||
mode.save()
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
|
||||
def test_pass_e_commerce_tab_in_instructor_dashboard(self):
|
||||
"""
|
||||
Test Pass E-commerce Tab is in the Instructor Dashboard
|
||||
"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, self.ecommerce_link)
|
||||
# Coupons should show up for White Label sites with priced honor modes.
|
||||
self.assertContains(response, 'Coupon Code List')
|
||||
|
||||
def test_reports_section_under_e_commerce_tab(self):
|
||||
"""
|
||||
Test reports section, under E-commerce Tab, is in the Instructor Dashboard
|
||||
"""
|
||||
self.use_site(site=self.site_other)
|
||||
self.client.login(username=self.instructor.username, password="test")
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, self.ecommerce_link)
|
||||
self.assertContains(response, 'Create Enrollment Report')
|
||||
|
||||
def test_reports_section_not_under_e_commerce_tab(self):
|
||||
"""
|
||||
Test reports section, under E-commerce Tab, should not be available in the Instructor Dashboard with default
|
||||
value
|
||||
"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, self.ecommerce_link)
|
||||
self.assertNotContains(response, 'Create Enrollment Report')
|
||||
|
||||
def test_user_has_finance_admin_rights_in_e_commerce_tab(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, self.ecommerce_link)
|
||||
|
||||
# Order/Invoice sales csv button text should render in e-commerce page
|
||||
self.assertContains(response, 'Total Credit Card Purchases')
|
||||
self.assertContains(response, 'Download All Credit Card Purchases')
|
||||
self.assertContains(response, 'Download All Invoices')
|
||||
|
||||
# removing the course finance_admin role of login user
|
||||
CourseFinanceAdminRole(self.course.id).remove_users(self.instructor)
|
||||
|
||||
# Order/Invoice sales csv button text should not be visible in e-commerce page if the user is not finance admin
|
||||
url = reverse('instructor_dashboard', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(url)
|
||||
self.assertNotContains(response, 'Download All Invoices')
|
||||
|
||||
def test_user_view_course_price(self):
|
||||
"""
|
||||
test to check if the user views the set price button and price in
|
||||
the instructor dashboard
|
||||
"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, self.ecommerce_link)
|
||||
|
||||
# Total amount html should render in e-commerce page, total amount will be 0
|
||||
course_honor_mode = CourseMode.mode_for_course(self.course.id, 'honor')
|
||||
|
||||
price = course_honor_mode.min_price
|
||||
self.assertContains(response, 'Course price per seat: <span>$' + str(price) + '</span>')
|
||||
self.assertNotContains(response, '+ Set Price</a></span>')
|
||||
|
||||
# removing the course finance_admin role of login user
|
||||
CourseFinanceAdminRole(self.course.id).remove_users(self.instructor)
|
||||
|
||||
# total amount should not be visible in e-commerce page if the user is not finance admin
|
||||
url = reverse('instructor_dashboard', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.get(url)
|
||||
self.assertNotContains(response, '+ Set Price</a></span>')
|
||||
|
||||
def test_update_course_price_check(self):
|
||||
price = 200
|
||||
# course B
|
||||
course2 = CourseFactory.create(org='EDX', display_name='test_course', number='100')
|
||||
mode = CourseModeFactory(
|
||||
course_id=text_type(course2.id), mode_slug='honor',
|
||||
mode_display_name='honor', min_price=30, currency='usd'
|
||||
)
|
||||
mode.save()
|
||||
# course A update
|
||||
CourseMode.objects.filter(course_id=self.course.id).update(min_price=price)
|
||||
|
||||
set_course_price_url = reverse('set_course_mode_price', kwargs={'course_id': text_type(self.course.id)})
|
||||
data = {'course_price': price, 'currency': 'usd'}
|
||||
response = self.client.post(set_course_price_url, data)
|
||||
self.assertContains(response, 'CourseMode price updated successfully')
|
||||
|
||||
# Course A updated total amount should be visible in e-commerce page if the user is finance admin
|
||||
url = reverse('instructor_dashboard', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertContains(response, 'Course price per seat: <span>$' + str(price) + '</span>')
|
||||
|
||||
def test_user_admin_set_course_price(self):
|
||||
"""
|
||||
test to set the course price related functionality.
|
||||
test al the scenarios for setting a new course price
|
||||
"""
|
||||
set_course_price_url = reverse('set_course_mode_price', kwargs={'course_id': text_type(self.course.id)})
|
||||
data = {'course_price': '12%', 'currency': 'usd'}
|
||||
|
||||
# Value Error course price should be a numeric value
|
||||
response = self.client.post(set_course_price_url, data)
|
||||
self.assertContains(response, "Please Enter the numeric value for the course price", status_code=400)
|
||||
|
||||
# validation check passes and course price is successfully added
|
||||
data['course_price'] = 100
|
||||
response = self.client.post(set_course_price_url, data)
|
||||
self.assertContains(response, "CourseMode price updated successfully")
|
||||
|
||||
course_honor_mode = CourseMode.objects.get(mode_slug='honor')
|
||||
course_honor_mode.delete()
|
||||
# Course Mode not exist with mode slug honor
|
||||
response = self.client.post(set_course_price_url, data)
|
||||
self.assertContains(
|
||||
response,
|
||||
u"CourseMode with the mode slug({mode_slug}) DoesNotExist".format(mode_slug='honor'),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
def test_add_coupon(self):
|
||||
"""
|
||||
Test Add Coupon Scenarios. Handle all the HttpResponses return by add_coupon view
|
||||
"""
|
||||
# URL for add_coupon
|
||||
add_coupon_url = reverse('add_coupon', kwargs={'course_id': text_type(self.course.id)})
|
||||
expiration_date = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
|
||||
data = {
|
||||
'code': 'A2314', 'course_id': text_type(self.course.id),
|
||||
'description': 'ADSADASDSAD', 'created_by': self.instructor, 'discount': 5,
|
||||
'expiration_date': '{month}/{day}/{year}'.format(
|
||||
month=expiration_date.month, day=expiration_date.day, year=expiration_date.year
|
||||
)
|
||||
}
|
||||
response = self.client.post(add_coupon_url, data)
|
||||
self.assertContains(
|
||||
response,
|
||||
u"coupon with the coupon code ({code}) added successfully".format(code=data['code']),
|
||||
)
|
||||
|
||||
#now add the coupon with the wrong value in the expiration_date
|
||||
# server will through the ValueError Exception in the expiration_date field
|
||||
data = {
|
||||
'code': '213454', 'course_id': text_type(self.course.id),
|
||||
'description': 'ADSADASDSAD', 'created_by': self.instructor, 'discount': 5,
|
||||
'expiration_date': expiration_date.strftime('"%d/%m/%Y')
|
||||
}
|
||||
response = self.client.post(add_coupon_url, data)
|
||||
self.assertContains(
|
||||
response,
|
||||
"Please enter the date in this format i-e month/day/year",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
data = {
|
||||
'code': 'A2314', 'course_id': text_type(self.course.id),
|
||||
'description': 'asdsasda', 'created_by': self.instructor, 'discount': 99
|
||||
}
|
||||
response = self.client.post(add_coupon_url, data)
|
||||
self.assertContains(
|
||||
response,
|
||||
u"coupon with the coupon code ({code}) already exist".format(code='A2314'),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
response = self.client.post(self.url)
|
||||
self.assertContains(response, '<td>ADSADASDSAD</td>')
|
||||
self.assertContains(response, '<td>A2314</td>')
|
||||
self.assertNotContains(response, '<td>111</td>')
|
||||
|
||||
data = {
|
||||
'code': 'A2345314', 'course_id': text_type(self.course.id),
|
||||
'description': 'asdsasda', 'created_by': self.instructor, 'discount': 199
|
||||
}
|
||||
response = self.client.post(add_coupon_url, data)
|
||||
self.assertContains(
|
||||
response,
|
||||
"Please Enter the Coupon Discount Value Less than or Equal to 100",
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
data['discount'] = '25%'
|
||||
response = self.client.post(add_coupon_url, data=data)
|
||||
self.assertContains(response, 'Please Enter the Integer Value for Coupon Discount', status_code=400)
|
||||
|
||||
course_registration = CourseRegistrationCode(
|
||||
code='Vs23Ws4j', course_id=text_type(self.course.id), created_by=self.instructor,
|
||||
mode_slug='honor'
|
||||
)
|
||||
course_registration.save()
|
||||
|
||||
data['code'] = 'Vs23Ws4j'
|
||||
response = self.client.post(add_coupon_url, data)
|
||||
msg = u"The code ({code}) that you have tried to define is already in use as a registration code"
|
||||
self.assertContains(response, msg.format(code=data['code']), status_code=400)
|
||||
|
||||
def test_delete_coupon(self):
|
||||
"""
|
||||
Test Delete Coupon Scenarios. Handle all the HttpResponses return by remove_coupon view
|
||||
"""
|
||||
coupon = Coupon(
|
||||
code='AS452', description='asdsadsa', course_id=text_type(self.course.id),
|
||||
percentage_discount=10, created_by=self.instructor
|
||||
)
|
||||
|
||||
coupon.save()
|
||||
|
||||
response = self.client.post(self.url)
|
||||
self.assertContains(response, '<td>AS452</td>')
|
||||
|
||||
# URL for remove_coupon
|
||||
delete_coupon_url = reverse('remove_coupon', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(delete_coupon_url, {'id': coupon.id})
|
||||
self.assertContains(
|
||||
response,
|
||||
u'coupon with the coupon id ({coupon_id}) updated successfully'.format(coupon_id=coupon.id),
|
||||
)
|
||||
|
||||
coupon.is_active = False
|
||||
coupon.save()
|
||||
|
||||
response = self.client.post(delete_coupon_url, {'id': coupon.id})
|
||||
self.assertContains(
|
||||
response,
|
||||
u'coupon with the coupon id ({coupon_id}) is already inactive'.format(coupon_id=coupon.id),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
response = self.client.post(delete_coupon_url, {'id': 24454})
|
||||
self.assertContains(
|
||||
response,
|
||||
u'coupon with the coupon id ({coupon_id}) DoesNotExist'.format(coupon_id=24454),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
response = self.client.post(delete_coupon_url, {'id': ''})
|
||||
self.assertContains(response, 'coupon id is None', status_code=400)
|
||||
|
||||
def test_get_coupon_info(self):
|
||||
"""
|
||||
Test Edit Coupon Info Scenarios. Handle all the HttpResponses return by edit_coupon_info view
|
||||
"""
|
||||
coupon = Coupon(
|
||||
code='AS452', description='asdsadsa', course_id=text_type(self.course.id),
|
||||
percentage_discount=10, created_by=self.instructor,
|
||||
expiration_date=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
)
|
||||
coupon.save()
|
||||
# URL for edit_coupon_info
|
||||
edit_url = reverse('get_coupon_info', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(edit_url, {'id': coupon.id})
|
||||
self.assertContains(
|
||||
response,
|
||||
u'coupon with the coupon id ({coupon_id}) updated successfully'.format(coupon_id=coupon.id),
|
||||
)
|
||||
self.assertContains(response, coupon.display_expiry_date)
|
||||
|
||||
response = self.client.post(edit_url, {'id': 444444})
|
||||
self.assertContains(
|
||||
response,
|
||||
u'coupon with the coupon id ({coupon_id}) DoesNotExist'.format(coupon_id=444444),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
response = self.client.post(edit_url, {'id': ''})
|
||||
self.assertContains(response, 'coupon id not found"', status_code=400)
|
||||
|
||||
coupon.is_active = False
|
||||
coupon.save()
|
||||
|
||||
response = self.client.post(edit_url, {'id': coupon.id})
|
||||
self.assertContains(
|
||||
response,
|
||||
u"coupon with the coupon id ({coupon_id}) is already inactive".format(coupon_id=coupon.id),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
def test_update_coupon(self):
|
||||
"""
|
||||
Test Update Coupon Info Scenarios. Handle all the HttpResponses return by update_coupon view
|
||||
"""
|
||||
coupon = Coupon(
|
||||
code='AS452', description='asdsadsa', course_id=text_type(self.course.id),
|
||||
percentage_discount=10, created_by=self.instructor
|
||||
)
|
||||
coupon.save()
|
||||
response = self.client.post(self.url)
|
||||
self.assertContains(response, '<td>AS452</td>')
|
||||
data = {
|
||||
'coupon_id': coupon.id, 'code': 'AS452', 'discount': '10', 'description': 'updated_description',
|
||||
'course_id': text_type(coupon.course_id)
|
||||
}
|
||||
# URL for update_coupon
|
||||
update_coupon_url = reverse('update_coupon', kwargs={'course_id': text_type(self.course.id)})
|
||||
response = self.client.post(update_coupon_url, data=data)
|
||||
self.assertContains(
|
||||
response,
|
||||
u'coupon with the coupon id ({coupon_id}) updated Successfully'.format(coupon_id=coupon.id),
|
||||
)
|
||||
|
||||
response = self.client.post(self.url)
|
||||
self.assertContains(response, '<td>updated_description</td>')
|
||||
|
||||
data['coupon_id'] = 1000 # Coupon Not Exist with this ID
|
||||
response = self.client.post(update_coupon_url, data=data)
|
||||
self.assertContains(
|
||||
response,
|
||||
u'coupon with the coupon id ({coupon_id}) DoesNotExist'.format(coupon_id=1000),
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
data['coupon_id'] = '' # Coupon id is not provided
|
||||
response = self.client.post(update_coupon_url, data=data)
|
||||
self.assertContains(response, 'coupon id not found', status_code=400)
|
||||
|
||||
def test_verified_course(self):
|
||||
"""Verify the e-commerce panel shows up for verified courses as well, without Coupons """
|
||||
# Change honor mode to verified.
|
||||
original_mode = CourseMode.objects.get(course_id=self.course.id, mode_slug='honor')
|
||||
original_mode.delete()
|
||||
new_mode = CourseModeFactory(
|
||||
course_id=six.text_type(self.course.id), mode_slug='verified',
|
||||
mode_display_name='verified', min_price=10, currency='usd'
|
||||
)
|
||||
new_mode.save()
|
||||
|
||||
# Get the response value, ensure the Coupon section is not included.
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, self.ecommerce_link)
|
||||
# Coupons should show up for White Label sites with priced honor modes.
|
||||
self.assertNotContains(response, 'Coupons List')
|
||||
|
||||
def test_coupon_code_section_not_under_e_commerce_tab(self):
|
||||
"""
|
||||
Test Coupon Creation UI, under E-commerce Tab, should not be available in the Instructor Dashboard with
|
||||
e-commerce course
|
||||
"""
|
||||
# Setup e-commerce course
|
||||
CourseMode.objects.filter(course_id=self.course.id).update(sku='test_sku')
|
||||
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, self.ecommerce_link)
|
||||
self.assertNotContains(response, 'Coupon Code List')
|
||||
|
||||
def test_enrollment_codes_section_not_under_e_commerce_tab(self):
|
||||
"""
|
||||
Test Enrollment Codes UI, under E-commerce Tab, should not be available in the Instructor Dashboard with
|
||||
e-commerce course
|
||||
"""
|
||||
# Setup e-commerce course
|
||||
CourseMode.objects.filter(course_id=self.course.id).update(sku='test_sku')
|
||||
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, self.ecommerce_link)
|
||||
self.assertNotContains(response, '<h3 class="hd hd-3">Enrollment Codes</h3>')
|
||||
|
||||
def test_enrollment_codes_section_visible_for_non_ecommerce_course(self):
|
||||
"""
|
||||
Test Enrollment Codes UI, under E-commerce Tab, should be available in the Instructor Dashboard with non
|
||||
e-commerce course
|
||||
"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, self.ecommerce_link)
|
||||
self.assertContains(response, '<h3 class="hd hd-3">Enrollment Codes</h3>')
|
||||
@@ -1,306 +0,0 @@
|
||||
"""
|
||||
Test for the registration code status information.
|
||||
"""
|
||||
|
||||
|
||||
import json
|
||||
|
||||
import six
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from six import text_type
|
||||
from six.moves import range
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
from lms.djangoapps.courseware.tests.factories import InstructorFactory
|
||||
from shoppingcart.models import (
|
||||
CourseRegCodeItem,
|
||||
CourseRegistrationCode,
|
||||
CourseRegistrationCodeInvoiceItem,
|
||||
Invoice,
|
||||
Order,
|
||||
RegistrationCodeRedemption
|
||||
)
|
||||
from student.models import CourseEnrollment
|
||||
from student.roles import CourseSalesAdminRole
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
@override_settings(REGISTRATION_CODE_LENGTH=8)
|
||||
class TestCourseRegistrationCodeStatus(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Test registration code status.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestCourseRegistrationCodeStatus, cls).setUpClass()
|
||||
cls.course = CourseFactory.create()
|
||||
|
||||
def setUp(self):
|
||||
super(TestCourseRegistrationCodeStatus, self).setUp()
|
||||
CourseModeFactory.create(course_id=self.course.id, min_price=50)
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
CourseSalesAdminRole(self.course.id).add_users(self.instructor)
|
||||
|
||||
# create testing invoice
|
||||
self.sale_invoice = Invoice.objects.create(
|
||||
total_amount=1234.32, company_name='Test1', company_contact_name='TestName',
|
||||
company_contact_email='Test@company.com', recipient_name='Testw', recipient_email='test1@test.com',
|
||||
customer_reference_number='2Fwe23S', internal_reference="A", course_id=self.course.id, is_valid=True
|
||||
)
|
||||
self.invoice_item = CourseRegistrationCodeInvoiceItem.objects.create(
|
||||
invoice=self.sale_invoice,
|
||||
qty=1,
|
||||
unit_price=1234.32,
|
||||
course_id=self.course.id
|
||||
)
|
||||
self.lookup_code_url = reverse('look_up_registration_code',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
self.registration_code_detail_url = reverse('registration_code_details',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
url = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 12,
|
||||
'company_name': 'Test Group',
|
||||
'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com',
|
||||
'unit_price': 122.45,
|
||||
'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com',
|
||||
'address_line_1': 'Portland Street',
|
||||
'address_line_2': '',
|
||||
'address_line_3': '',
|
||||
'city': '',
|
||||
'state': '',
|
||||
'zip': '',
|
||||
'country': '',
|
||||
'customer_reference_number': '123A23F',
|
||||
'internal_reference': '',
|
||||
'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
|
||||
def test_look_up_invalid_registration_code(self):
|
||||
"""
|
||||
Verify the view returns HTTP status 400 if an invalid registration code is passed.
|
||||
Also, verify the data returned includes a message indicating the error,
|
||||
and the is_registration_code_valid is set to False.
|
||||
"""
|
||||
data = {
|
||||
'registration_code': 'invalid_reg_code'
|
||||
}
|
||||
response = self.client.get(self.lookup_code_url, data)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
json_dict = json.loads(response.content.decode('utf-8'))
|
||||
message = _(u'The enrollment code ({code}) was not found for the {course_name} course.').format(
|
||||
course_name=self.course.display_name, code=data['registration_code']
|
||||
)
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
self.assertFalse(json_dict['is_registration_code_valid'])
|
||||
self.assertFalse(json_dict['is_registration_code_redeemed'])
|
||||
|
||||
def test_look_up_valid_registration_code(self):
|
||||
"""
|
||||
test lookup for the valid registration code
|
||||
and that registration code has been redeemed by user
|
||||
and then mark the registration code as in_valid
|
||||
when marking as invalidate, it also lookup for
|
||||
registration redemption entry and also delete
|
||||
that redemption entry and un_enroll the student
|
||||
who used that registration code for their enrollment.
|
||||
"""
|
||||
for i in range(2):
|
||||
CourseRegistrationCode.objects.create(
|
||||
code='reg_code{}'.format(i),
|
||||
course_id=six.text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug=CourseMode.DEFAULT_MODE_SLUG
|
||||
)
|
||||
|
||||
reg_code = CourseRegistrationCode.objects.all()[0]
|
||||
student = UserFactory()
|
||||
enrollment = CourseEnrollment.enroll(student, self.course.id)
|
||||
|
||||
RegistrationCodeRedemption.objects.create(
|
||||
registration_code=reg_code,
|
||||
redeemed_by=student,
|
||||
course_enrollment=enrollment
|
||||
)
|
||||
|
||||
data = {
|
||||
'registration_code': reg_code.code
|
||||
}
|
||||
response = self.client.get(self.lookup_code_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
json_dict = json.loads(response.content.decode('utf-8'))
|
||||
self.assertTrue(json_dict['is_registration_code_valid'])
|
||||
self.assertTrue(json_dict['is_registration_code_redeemed'])
|
||||
|
||||
# now mark that registration code as invalid
|
||||
data = {
|
||||
'registration_code': reg_code.code,
|
||||
'action_type': 'invalidate_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
json_dict = json.loads(response.content.decode('utf-8'))
|
||||
message = _('This enrollment code has been canceled. It can no longer be used.')
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
|
||||
# now check that the registration code should be marked as invalid in the db.
|
||||
reg_code = CourseRegistrationCode.objects.get(code=reg_code.code)
|
||||
self.assertEqual(reg_code.is_valid, False)
|
||||
|
||||
redemption = RegistrationCodeRedemption.get_registration_code_redemption(reg_code.code, self.course.id)
|
||||
self.assertIsNone(redemption)
|
||||
|
||||
# now the student course enrollment should be false.
|
||||
enrollment = CourseEnrollment.get_enrollment(student, self.course.id)
|
||||
self.assertEqual(enrollment.is_active, False)
|
||||
|
||||
def test_lookup_valid_redeemed_registration_code(self):
|
||||
"""
|
||||
test to lookup for the valid and redeemed registration code
|
||||
and then mark that registration code as un_redeemed
|
||||
which will unenroll the user and delete the redemption
|
||||
entry from the database.
|
||||
"""
|
||||
student = UserFactory()
|
||||
self.client.login(username=student.username, password='test')
|
||||
cart = Order.get_cart_for_user(student)
|
||||
cart.order_type = 'business'
|
||||
cart.save()
|
||||
CourseRegCodeItem.add_to_order(cart, self.course.id, 2)
|
||||
cart.purchase()
|
||||
|
||||
reg_code = CourseRegistrationCode.objects.filter(order=cart)[0]
|
||||
|
||||
enrollment = CourseEnrollment.enroll(student, self.course.id)
|
||||
|
||||
RegistrationCodeRedemption.objects.create(
|
||||
registration_code=reg_code,
|
||||
redeemed_by=student,
|
||||
course_enrollment=enrollment
|
||||
)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
data = {
|
||||
'registration_code': reg_code.code
|
||||
}
|
||||
response = self.client.get(self.lookup_code_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
json_dict = json.loads(response.content.decode('utf-8'))
|
||||
self.assertTrue(json_dict['is_registration_code_valid'])
|
||||
self.assertTrue(json_dict['is_registration_code_redeemed'])
|
||||
|
||||
# now mark the registration code as unredeemed
|
||||
# this will unenroll the user and removed the redemption entry from
|
||||
# the database.
|
||||
|
||||
data = {
|
||||
'registration_code': reg_code.code,
|
||||
'action_type': 'unredeem_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
json_dict = json.loads(response.content.decode('utf-8'))
|
||||
message = _('This enrollment code has been marked as unused.')
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
|
||||
redemption = RegistrationCodeRedemption.get_registration_code_redemption(reg_code.code, self.course.id)
|
||||
self.assertIsNone(redemption)
|
||||
|
||||
# now the student course enrollment should be false.
|
||||
enrollment = CourseEnrollment.get_enrollment(student, self.course.id)
|
||||
self.assertEqual(enrollment.is_active, False)
|
||||
|
||||
def test_apply_invalid_reg_code_when_updating_code_information(self):
|
||||
"""
|
||||
test to apply an invalid registration code
|
||||
when updating the registration code information.
|
||||
"""
|
||||
data = {
|
||||
'registration_code': 'invalid_registration_code',
|
||||
'action_type': 'unredeem_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
json_dict = json.loads(response.content.decode('utf-8'))
|
||||
message = _(u'The enrollment code ({code}) was not found for the {course_name} course.').format(
|
||||
course_name=self.course.display_name, code=data['registration_code']
|
||||
)
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
|
||||
def test_mark_registration_code_as_valid(self):
|
||||
"""
|
||||
test to mark the invalid registration code
|
||||
as valid
|
||||
"""
|
||||
for i in range(2):
|
||||
CourseRegistrationCode.objects.create(
|
||||
code='reg_code{}'.format(i),
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug=CourseMode.DEFAULT_MODE_SLUG,
|
||||
is_valid=False
|
||||
)
|
||||
|
||||
reg_code = CourseRegistrationCode.objects.all()[0]
|
||||
data = {
|
||||
'registration_code': reg_code.code,
|
||||
'action_type': 'validate_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
json_dict = json.loads(response.content.decode('utf-8'))
|
||||
message = _('The enrollment code has been restored.')
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
|
||||
# now check that the registration code should be marked as valid in the db.
|
||||
reg_code = CourseRegistrationCode.objects.get(code=reg_code.code)
|
||||
self.assertEqual(reg_code.is_valid, True)
|
||||
|
||||
def test_returns_error_when_unredeeming_already_unredeemed_registration_code_redemption(self):
|
||||
"""
|
||||
test to mark the already unredeemed registration code as unredeemed.
|
||||
"""
|
||||
for i in range(2):
|
||||
CourseRegistrationCode.objects.create(
|
||||
code='reg_code{}'.format(i),
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug=CourseMode.DEFAULT_MODE_SLUG,
|
||||
)
|
||||
|
||||
reg_code = CourseRegistrationCode.objects.all()[0]
|
||||
data = {
|
||||
'registration_code': reg_code.code,
|
||||
'action_type': 'unredeem_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
json_dict = json.loads(response.content.decode('utf-8'))
|
||||
message = _(u'The redemption does not exist against enrollment code ({code}).').format(code=reg_code.code)
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
@@ -28,7 +28,6 @@ from lms.djangoapps.grades.config.waffle import WRITABLE_GRADEBOOK, waffle_flags
|
||||
from lms.djangoapps.instructor.views.gradebook_api import calculate_page_info
|
||||
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
from shoppingcart.models import CourseRegCodeItem, Order, PaidCourseRegistration
|
||||
from student.models import CourseEnrollment
|
||||
from student.roles import CourseFinanceAdminRole
|
||||
from student.tests.factories import AdminFactory, CourseAccessRoleFactory, CourseEnrollmentFactory
|
||||
@@ -369,15 +368,6 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT
|
||||
)
|
||||
self.assertContains(response, 'View Gradebook')
|
||||
|
||||
def test_default_currency_in_the_html_response(self):
|
||||
"""
|
||||
Test that checks the default currency_symbol ($) in the response
|
||||
"""
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
total_amount = PaidCourseRegistration.get_total_amount_of_purchased_item(self.course.id)
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, '${amount}'.format(amount=total_amount))
|
||||
|
||||
def test_course_name_xss(self):
|
||||
"""Test that the instructor dashboard correctly escapes course names
|
||||
with script tags.
|
||||
@@ -385,16 +375,6 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT
|
||||
response = self.client.get(self.url)
|
||||
self.assert_no_xss(response, '<script>alert("XSS")</script>')
|
||||
|
||||
@override_settings(PAID_COURSE_REGISTRATION_CURRENCY=['PKR', 'Rs'])
|
||||
def test_override_currency_settings_in_the_html_response(self):
|
||||
"""
|
||||
Test that checks the default currency_symbol ($) in the response
|
||||
"""
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
total_amount = PaidCourseRegistration.get_total_amount_of_purchased_item(self.course.id)
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, '{currency}{amount}'.format(currency='Rs', amount=total_amount))
|
||||
|
||||
@patch.dict(settings.FEATURES, {'DISPLAY_ANALYTICS_ENROLLMENTS': False})
|
||||
@override_settings(ANALYTICS_DASHBOARD_URL='')
|
||||
def test_no_enrollments(self):
|
||||
@@ -481,34 +461,6 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT
|
||||
expected_message = self.get_dashboard_analytics_message()
|
||||
self.assertIn(expected_message, response.content.decode(response.charset))
|
||||
|
||||
def add_course_to_user_cart(self, cart, course_key):
|
||||
"""
|
||||
adding course to user cart
|
||||
"""
|
||||
reg_item = PaidCourseRegistration.add_to_order(cart, course_key)
|
||||
return reg_item
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||
def test_total_credit_cart_sales_amount(self):
|
||||
"""
|
||||
Test to check the total amount for all the credit card purchases.
|
||||
"""
|
||||
student = UserFactory.create()
|
||||
self.client.login(username=student.username, password="test")
|
||||
student_cart = Order.get_cart_for_user(student)
|
||||
item = self.add_course_to_user_cart(student_cart, self.course.id)
|
||||
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': item.id, 'qty': 4})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
student_cart.purchase()
|
||||
|
||||
self.client.login(username=self.instructor.username, password="test")
|
||||
CourseFinanceAdminRole(self.course.id).add_users(self.instructor)
|
||||
single_purchase_total = PaidCourseRegistration.get_total_amount_of_purchased_item(self.course.id)
|
||||
bulk_purchase_total = CourseRegCodeItem.get_total_amount_of_purchased_item(self.course.id)
|
||||
total_amount = single_purchase_total + bulk_purchase_total
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, '{currency}{amount}'.format(currency='$', amount=total_amount))
|
||||
|
||||
@ddt.data(
|
||||
(True, True, True),
|
||||
(True, False, False),
|
||||
|
||||
@@ -50,6 +50,7 @@ import instructor_analytics.csvs
|
||||
import instructor_analytics.distributions
|
||||
from bulk_email.api import is_bulk_email_feature_enabled
|
||||
from bulk_email.models import CourseEmail
|
||||
from course_modes.models import CourseMode
|
||||
from edxmako.shortcuts import render_to_string
|
||||
from lms.djangoapps.certificates import api as certs_api
|
||||
from lms.djangoapps.certificates.models import (
|
||||
@@ -96,14 +97,6 @@ from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
|
||||
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
|
||||
from shoppingcart.models import (
|
||||
Coupon,
|
||||
CourseMode,
|
||||
CourseRegistrationCode,
|
||||
CourseRegistrationCodeInvoiceItem,
|
||||
Invoice,
|
||||
RegistrationCodeRedemption
|
||||
)
|
||||
from student import auth
|
||||
from student.models import (
|
||||
ALLOWEDTOENROLL_TO_ENROLLED,
|
||||
@@ -1097,112 +1090,6 @@ def get_sale_records(request, course_id, csv=False): # pylint: disable=redefine
|
||||
return instructor_analytics.csvs.create_csv_response("e-commerce_sale_invoice_records.csv", header, datarows)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
def get_sale_order_records(request, course_id):
|
||||
"""
|
||||
return the summary of all sales records for a particular course
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
query_features = [
|
||||
('id', 'Order Id'),
|
||||
('company_name', 'Company Name'),
|
||||
('company_contact_name', 'Company Contact Name'),
|
||||
('company_contact_email', 'Company Contact Email'),
|
||||
('logged_in_username', 'Login Username'),
|
||||
('logged_in_email', 'Login User Email'),
|
||||
('purchase_time', 'Date of Sale'),
|
||||
('customer_reference_number', 'Customer Reference Number'),
|
||||
('recipient_name', 'Recipient Name'),
|
||||
('recipient_email', 'Recipient Email'),
|
||||
('bill_to_street1', 'Street 1'),
|
||||
('bill_to_street2', 'Street 2'),
|
||||
('bill_to_city', 'City'),
|
||||
('bill_to_state', 'State'),
|
||||
('bill_to_postalcode', 'Postal Code'),
|
||||
('bill_to_country', 'Country'),
|
||||
('order_type', 'Order Type'),
|
||||
('status', 'Order Item Status'),
|
||||
('coupon_code', 'Coupon Code'),
|
||||
('list_price', 'List Price'),
|
||||
('unit_cost', 'Unit Price'),
|
||||
('quantity', 'Quantity'),
|
||||
('total_discount', 'Total Discount'),
|
||||
('total_amount', 'Total Amount Paid'),
|
||||
]
|
||||
|
||||
db_columns = [x[0] for x in query_features]
|
||||
csv_columns = [x[1] for x in query_features]
|
||||
sale_data = instructor_analytics.basic.sale_order_record_features(course_id, db_columns)
|
||||
__, datarows = instructor_analytics.csvs.format_dictlist(sale_data, db_columns)
|
||||
return instructor_analytics.csvs.create_csv_response("e-commerce_sale_order_records.csv", csv_columns, datarows)
|
||||
|
||||
|
||||
@require_course_permission(permissions.EDIT_INVOICE_VALIDATION)
|
||||
@require_POST
|
||||
def sale_validation(request, course_id):
|
||||
"""
|
||||
This method either invalidate or re validate the sale against the invoice number depending upon the event type
|
||||
"""
|
||||
try:
|
||||
invoice_number = request.POST["invoice_number"]
|
||||
except KeyError:
|
||||
return HttpResponseBadRequest("Missing required invoice_number parameter")
|
||||
try:
|
||||
invoice_number = int(invoice_number)
|
||||
except ValueError:
|
||||
return HttpResponseBadRequest(
|
||||
u"invoice_number must be an integer, {value} provided".format(
|
||||
value=invoice_number
|
||||
)
|
||||
)
|
||||
try:
|
||||
event_type = request.POST["event_type"]
|
||||
except KeyError:
|
||||
return HttpResponseBadRequest("Missing required event_type parameter")
|
||||
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
try:
|
||||
obj_invoice = CourseRegistrationCodeInvoiceItem.objects.select_related('invoice').get(
|
||||
invoice_id=invoice_number,
|
||||
course_id=course_id
|
||||
)
|
||||
obj_invoice = obj_invoice.invoice
|
||||
except CourseRegistrationCodeInvoiceItem.DoesNotExist: # Check for old type invoices
|
||||
return HttpResponseNotFound(_(u"Invoice number '{num}' does not exist.").format(num=invoice_number))
|
||||
|
||||
if event_type == "invalidate":
|
||||
return invalidate_invoice(obj_invoice)
|
||||
else:
|
||||
return re_validate_invoice(obj_invoice)
|
||||
|
||||
|
||||
def invalidate_invoice(obj_invoice):
|
||||
"""
|
||||
This method invalidate the sale against the invoice number
|
||||
"""
|
||||
if not obj_invoice.is_valid:
|
||||
return HttpResponseBadRequest(_("The sale associated with this invoice has already been invalidated."))
|
||||
obj_invoice.is_valid = False
|
||||
obj_invoice.save()
|
||||
message = _(u'Invoice number {0} has been invalidated.').format(obj_invoice.id)
|
||||
return JsonResponse({'message': message})
|
||||
|
||||
|
||||
def re_validate_invoice(obj_invoice):
|
||||
"""
|
||||
This method re-validate the sale against the invoice number
|
||||
"""
|
||||
if obj_invoice.is_valid:
|
||||
return HttpResponseBadRequest(_("This invoice is already active."))
|
||||
|
||||
obj_invoice.is_valid = True
|
||||
obj_invoice.save()
|
||||
message = _(u'The registration codes for invoice {0} have been re-activated.').format(obj_invoice.id)
|
||||
return JsonResponse({'message': message})
|
||||
|
||||
|
||||
@transaction.non_atomic_requests
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@@ -1459,35 +1346,6 @@ class CohortCSV(DeveloperErrorViewMixin, APIView):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.VIEW_COUPONS)
|
||||
def get_coupon_codes(request, course_id):
|
||||
"""
|
||||
Respond with csv which contains a summary of all Active Coupons.
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
coupons = Coupon.objects.filter(course_id=course_id)
|
||||
|
||||
query_features = [
|
||||
('code', _('Coupon Code')),
|
||||
('course_id', _('Course Id')),
|
||||
('percentage_discount', _('% Discount')),
|
||||
('description', _('Description')),
|
||||
('expiration_date', _('Expiration Date')),
|
||||
('is_active', _('Is Active')),
|
||||
('code_redeemed_count', _('Code Redeemed Count')),
|
||||
('total_discounted_seats', _('Total Discounted Seats')),
|
||||
('total_discounted_amount', _('Total Discounted Amount')),
|
||||
]
|
||||
db_columns = [x[0] for x in query_features]
|
||||
csv_columns = [x[1] for x in query_features]
|
||||
|
||||
coupons_list = instructor_analytics.basic.coupon_codes_features(db_columns, coupons, course_id)
|
||||
__, data_rows = instructor_analytics.csvs.format_dictlist(coupons_list, db_columns)
|
||||
return instructor_analytics.csvs.create_csv_response('Coupons.csv', csv_columns, data_rows)
|
||||
|
||||
|
||||
@transaction.non_atomic_requests
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@@ -1562,336 +1420,6 @@ def get_proctored_exam_results(request, course_id):
|
||||
return JsonResponse({"status": success_status})
|
||||
|
||||
|
||||
def save_registration_code(user, course_id, mode_slug, invoice=None, order=None, invoice_item=None):
|
||||
"""
|
||||
recursive function that generate a new code every time and saves in the Course Registration Table
|
||||
if validation check passes
|
||||
|
||||
Args:
|
||||
user (User): The user creating the course registration codes.
|
||||
course_id (str): The string representation of the course ID.
|
||||
mode_slug (str): The Course Mode Slug associated with any enrollment made by these codes.
|
||||
invoice (Invoice): (Optional) The associated invoice for this code.
|
||||
order (Order): (Optional) The associated order for this code.
|
||||
invoice_item (CourseRegistrationCodeInvoiceItem) : (Optional) The associated CourseRegistrationCodeInvoiceItem
|
||||
|
||||
Returns:
|
||||
The newly created CourseRegistrationCode.
|
||||
|
||||
"""
|
||||
code = random_code_generator()
|
||||
|
||||
# check if the generated code is in the Coupon Table
|
||||
matching_coupons = Coupon.objects.filter(code=code, is_active=True)
|
||||
if matching_coupons:
|
||||
return save_registration_code(
|
||||
user, course_id, mode_slug, invoice=invoice, order=order, invoice_item=invoice_item
|
||||
)
|
||||
|
||||
course_registration = CourseRegistrationCode(
|
||||
code=code,
|
||||
course_id=six.text_type(course_id),
|
||||
created_by=user,
|
||||
invoice=invoice,
|
||||
order=order,
|
||||
mode_slug=mode_slug,
|
||||
invoice_item=invoice_item
|
||||
)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
course_registration.save()
|
||||
return course_registration
|
||||
except IntegrityError:
|
||||
return save_registration_code(
|
||||
user, course_id, mode_slug, invoice=invoice, order=order, invoice_item=invoice_item
|
||||
)
|
||||
|
||||
|
||||
def registration_codes_csv(file_name, codes_list, csv_type=None):
|
||||
"""
|
||||
Respond with the csv headers and data rows
|
||||
given a dict of codes list
|
||||
:param file_name:
|
||||
:param codes_list:
|
||||
:param csv_type:
|
||||
"""
|
||||
# csv headers
|
||||
query_features = [
|
||||
'code', 'redeem_code_url', 'course_id', 'company_name', 'created_by',
|
||||
'redeemed_by', 'invoice_id', 'purchaser', 'customer_reference_number', 'internal_reference', 'is_valid'
|
||||
]
|
||||
|
||||
registration_codes = instructor_analytics.basic.course_registration_features(query_features, codes_list, csv_type)
|
||||
header, data_rows = instructor_analytics.csvs.format_dictlist(registration_codes, query_features)
|
||||
return instructor_analytics.csvs.create_csv_response(file_name, header, data_rows)
|
||||
|
||||
|
||||
def random_code_generator():
|
||||
"""
|
||||
generate a random alphanumeric code of length defined in
|
||||
REGISTRATION_CODE_LENGTH settings
|
||||
"""
|
||||
code_length = getattr(settings, 'REGISTRATION_CODE_LENGTH', 8)
|
||||
return generate_random_string(code_length)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.VIEW_COUPONS)
|
||||
@require_POST
|
||||
def get_registration_codes(request, course_id):
|
||||
"""
|
||||
Respond with csv which contains a summary of all Registration Codes.
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
|
||||
#filter all the course registration codes
|
||||
registration_codes = CourseRegistrationCode.objects.filter(
|
||||
course_id=course_id
|
||||
).order_by('invoice_item__invoice__company_name')
|
||||
|
||||
company_name = request.POST['download_company_name']
|
||||
if company_name:
|
||||
registration_codes = registration_codes.filter(invoice_item__invoice__company_name=company_name)
|
||||
|
||||
csv_type = 'download'
|
||||
return registration_codes_csv("Registration_Codes.csv", registration_codes, csv_type)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_sales_admin
|
||||
@require_POST
|
||||
def generate_registration_codes(request, course_id):
|
||||
"""
|
||||
Respond with csv which contains a summary of all Generated Codes.
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
invoice_copy = False
|
||||
|
||||
# covert the course registration code number into integer
|
||||
try:
|
||||
course_code_number = int(request.POST['total_registration_codes'])
|
||||
except ValueError:
|
||||
course_code_number = int(float(request.POST['total_registration_codes']))
|
||||
|
||||
company_name = request.POST['company_name']
|
||||
company_contact_name = request.POST['company_contact_name']
|
||||
company_contact_email = request.POST['company_contact_email']
|
||||
unit_price = request.POST['unit_price']
|
||||
|
||||
try:
|
||||
unit_price = (
|
||||
decimal.Decimal(unit_price)
|
||||
).quantize(
|
||||
decimal.Decimal('.01'),
|
||||
rounding=decimal.ROUND_DOWN
|
||||
)
|
||||
except decimal.InvalidOperation:
|
||||
return HttpResponse(
|
||||
status=400,
|
||||
content=_(u"Could not parse amount as a decimal")
|
||||
)
|
||||
|
||||
recipient_name = request.POST['recipient_name']
|
||||
recipient_email = request.POST['recipient_email']
|
||||
address_line_1 = request.POST['address_line_1']
|
||||
address_line_2 = request.POST['address_line_2']
|
||||
address_line_3 = request.POST['address_line_3']
|
||||
city = request.POST['city']
|
||||
state = request.POST['state']
|
||||
zip_code = request.POST['zip']
|
||||
country = request.POST['country']
|
||||
internal_reference = request.POST['internal_reference']
|
||||
customer_reference_number = request.POST['customer_reference_number']
|
||||
recipient_list = [recipient_email]
|
||||
if request.POST.get('invoice', False):
|
||||
recipient_list.append(request.user.email)
|
||||
invoice_copy = True
|
||||
|
||||
sale_price = unit_price * course_code_number
|
||||
set_user_preference(request.user, INVOICE_KEY, invoice_copy)
|
||||
sale_invoice = Invoice.objects.create(
|
||||
total_amount=sale_price,
|
||||
company_name=company_name,
|
||||
company_contact_email=company_contact_email,
|
||||
company_contact_name=company_contact_name,
|
||||
course_id=course_id,
|
||||
recipient_name=recipient_name,
|
||||
recipient_email=recipient_email,
|
||||
address_line_1=address_line_1,
|
||||
address_line_2=address_line_2,
|
||||
address_line_3=address_line_3,
|
||||
city=city,
|
||||
state=state,
|
||||
zip=zip_code,
|
||||
country=country,
|
||||
internal_reference=internal_reference,
|
||||
customer_reference_number=customer_reference_number
|
||||
)
|
||||
|
||||
invoice_item = CourseRegistrationCodeInvoiceItem.objects.create(
|
||||
invoice=sale_invoice,
|
||||
qty=course_code_number,
|
||||
unit_price=unit_price,
|
||||
course_id=course_id
|
||||
)
|
||||
|
||||
course = get_course_by_id(course_id, depth=0)
|
||||
paid_modes = CourseMode.paid_modes_for_course(course_id)
|
||||
|
||||
if len(paid_modes) != 1:
|
||||
msg = (
|
||||
u"Generating Code Redeem Codes for Course '{course_id}', which must have a single paid course mode. "
|
||||
u"This is a configuration issue. Current course modes with payment options: {paid_modes}"
|
||||
).format(course_id=course_id, paid_modes=paid_modes)
|
||||
log.error(msg)
|
||||
return HttpResponse(
|
||||
status=500,
|
||||
content=_(u"Unable to generate redeem codes because of course misconfiguration.")
|
||||
)
|
||||
|
||||
course_mode = paid_modes[0]
|
||||
course_price = course_mode.min_price
|
||||
|
||||
registration_codes = []
|
||||
for __ in range(course_code_number):
|
||||
generated_registration_code = save_registration_code(
|
||||
request.user, course_id, course_mode.slug, invoice=sale_invoice, order=None, invoice_item=invoice_item
|
||||
)
|
||||
registration_codes.append(generated_registration_code)
|
||||
|
||||
site_name = configuration_helpers.get_value('SITE_NAME', 'localhost')
|
||||
quantity = course_code_number
|
||||
discount = (float(quantity * course_price) - float(sale_price))
|
||||
course_url = '{base_url}{course_about}'.format(
|
||||
base_url=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
|
||||
course_about=reverse('about_course', kwargs={'course_id': text_type(course_id)})
|
||||
)
|
||||
dashboard_url = '{base_url}{dashboard}'.format(
|
||||
base_url=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
|
||||
dashboard=reverse('dashboard')
|
||||
)
|
||||
|
||||
from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
|
||||
context = {
|
||||
'invoice': sale_invoice,
|
||||
'site_name': site_name,
|
||||
'course': course,
|
||||
'course_price': course_price,
|
||||
'sub_total': course_price * quantity,
|
||||
'discount': discount,
|
||||
'sale_price': sale_price,
|
||||
'quantity': quantity,
|
||||
'registration_codes': registration_codes,
|
||||
'currency_symbol': settings.PAID_COURSE_REGISTRATION_CURRENCY[1],
|
||||
'course_url': course_url,
|
||||
'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
'dashboard_url': dashboard_url,
|
||||
'contact_email': from_address,
|
||||
'corp_address': configuration_helpers.get_value('invoice_corp_address', settings.INVOICE_CORP_ADDRESS),
|
||||
'payment_instructions': configuration_helpers.get_value(
|
||||
'invoice_payment_instructions',
|
||||
settings. INVOICE_PAYMENT_INSTRUCTIONS,
|
||||
),
|
||||
'date': time.strftime("%m/%d/%Y")
|
||||
}
|
||||
# composes registration codes invoice email
|
||||
subject = u'Confirmation and Invoice for {course_name}'.format(course_name=course.display_name)
|
||||
message = render_to_string('emails/registration_codes_sale_email.txt', context)
|
||||
|
||||
invoice_attachment = render_to_string('emails/registration_codes_sale_invoice_attachment.txt', context)
|
||||
|
||||
#send_mail(subject, message, from_address, recipient_list, fail_silently=False)
|
||||
csv_file = StringIO()
|
||||
csv_writer = csv.writer(csv_file)
|
||||
for registration_code in registration_codes:
|
||||
full_redeem_code_url = 'http://{base_url}{redeem_code_url}'.format(
|
||||
base_url=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
|
||||
redeem_code_url=reverse('register_code_redemption', kwargs={'registration_code': registration_code.code})
|
||||
)
|
||||
csv_writer.writerow([registration_code.code, full_redeem_code_url])
|
||||
finance_email = configuration_helpers.get_value('finance_email', settings.FINANCE_EMAIL)
|
||||
if finance_email:
|
||||
# append the finance email into the recipient_list
|
||||
recipient_list.append(finance_email)
|
||||
|
||||
# send a unique email for each recipient, don't put all email addresses in a single email
|
||||
for recipient in recipient_list:
|
||||
email = EmailMessage()
|
||||
email.subject = subject
|
||||
email.body = message
|
||||
email.from_email = from_address
|
||||
email.to = [recipient]
|
||||
email.attach(u'RegistrationCodes.csv', csv_file.getvalue(), 'text/csv')
|
||||
email.attach(u'Invoice.txt', invoice_attachment, 'text/plain')
|
||||
email.send()
|
||||
|
||||
return registration_codes_csv("Registration_Codes.csv", registration_codes)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.VIEW_COUPONS)
|
||||
@require_POST
|
||||
def active_registration_codes(request, course_id):
|
||||
"""
|
||||
Respond with csv which contains a summary of all Active Registration Codes.
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
|
||||
# find all the registration codes in this course
|
||||
registration_codes_list = CourseRegistrationCode.objects.filter(
|
||||
course_id=course_id
|
||||
).order_by('invoice_item__invoice__company_name')
|
||||
|
||||
company_name = request.POST['active_company_name']
|
||||
if company_name:
|
||||
registration_codes_list = registration_codes_list.filter(invoice_item__invoice__company_name=company_name)
|
||||
# find the redeemed registration codes if any exist in the db
|
||||
code_redemption_set = RegistrationCodeRedemption.objects.select_related(
|
||||
'registration_code', 'registration_code__invoice_item__invoice'
|
||||
).filter(registration_code__course_id=course_id)
|
||||
if code_redemption_set.exists():
|
||||
redeemed_registration_codes = [code.registration_code.code for code in code_redemption_set]
|
||||
# exclude the redeemed registration codes from the registration codes list and you will get
|
||||
# all the registration codes that are active
|
||||
registration_codes_list = registration_codes_list.exclude(code__in=redeemed_registration_codes)
|
||||
|
||||
return registration_codes_csv("Active_Registration_Codes.csv", registration_codes_list)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.VIEW_COUPONS)
|
||||
@require_POST
|
||||
def spent_registration_codes(request, course_id):
|
||||
"""
|
||||
Respond with csv which contains a summary of all Spent(used) Registration Codes.
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
|
||||
# find the redeemed registration codes if any exist in the db
|
||||
code_redemption_set = RegistrationCodeRedemption.objects.select_related('registration_code').filter(
|
||||
registration_code__course_id=course_id
|
||||
)
|
||||
spent_codes_list = []
|
||||
if code_redemption_set.exists():
|
||||
redeemed_registration_codes = [code.registration_code.code for code in code_redemption_set]
|
||||
# filter the Registration Codes by course id and the redeemed codes and
|
||||
# you will get a list of all the spent(Redeemed) Registration Codes
|
||||
spent_codes_list = CourseRegistrationCode.objects.filter(
|
||||
course_id=course_id, code__in=redeemed_registration_codes
|
||||
).order_by('invoice_item__invoice__company_name').select_related('invoice_item__invoice')
|
||||
|
||||
company_name = request.POST['spent_company_name']
|
||||
if company_name:
|
||||
spent_codes_list = spent_codes_list.filter(invoice_item__invoice__company_name=company_name)
|
||||
|
||||
csv_type = 'spent'
|
||||
return registration_codes_csv("Spent_Registration_Codes.csv", spent_codes_list, csv_type)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
|
||||
@@ -18,10 +18,6 @@ urlpatterns = [
|
||||
url(r'^get_students_features(?P<csv>/csv)?$', api.get_students_features, name='get_students_features'),
|
||||
url(r'^get_issued_certificates/$', api.get_issued_certificates, name='get_issued_certificates'),
|
||||
url(r'^get_students_who_may_enroll$', api.get_students_who_may_enroll, name='get_students_who_may_enroll'),
|
||||
url(r'^get_user_invoice_preference$', api.get_user_invoice_preference, name='get_user_invoice_preference'),
|
||||
url(r'^get_sale_records(?P<csv>/csv)?$', api.get_sale_records, name='get_sale_records'),
|
||||
url(r'^get_sale_order_records$', api.get_sale_order_records, name='get_sale_order_records'),
|
||||
url(r'^sale_validation_url$', api.sale_validation, name='sale_validation'),
|
||||
url(r'^get_anon_ids$', api.get_anon_ids, name='get_anon_ids'),
|
||||
url(r'^get_student_enrollment_status$', api.get_student_enrollment_status, name="get_student_enrollment_status"),
|
||||
url(r'^get_student_progress_url$', api.get_student_progress_url, name='get_student_progress_url'),
|
||||
@@ -54,25 +50,12 @@ urlpatterns = [
|
||||
url(r'^calculate_grades_csv$', api.calculate_grades_csv, name='calculate_grades_csv'),
|
||||
url(r'^problem_grade_report$', api.problem_grade_report, name='problem_grade_report'),
|
||||
|
||||
# Financial Report downloads..
|
||||
url(r'^list_financial_report_downloads$', api.list_financial_report_downloads,
|
||||
name='list_financial_report_downloads'),
|
||||
|
||||
# Registration Codes..
|
||||
url(r'^get_registration_codes$', api.get_registration_codes, name='get_registration_codes'),
|
||||
url(r'^generate_registration_codes$', api.generate_registration_codes, name='generate_registration_codes'),
|
||||
url(r'^active_registration_codes$', api.active_registration_codes, name='active_registration_codes'),
|
||||
url(r'^spent_registration_codes$', api.spent_registration_codes, name='spent_registration_codes'),
|
||||
|
||||
# Reports..
|
||||
url(r'^get_enrollment_report$', api.get_enrollment_report, name='get_enrollment_report'),
|
||||
url(r'^get_exec_summary_report$', api.get_exec_summary_report, name='get_exec_summary_report'),
|
||||
url(r'^get_course_survey_results$', api.get_course_survey_results, name='get_course_survey_results'),
|
||||
url(r'^export_ora2_data', api.export_ora2_data, name='export_ora2_data'),
|
||||
|
||||
# Coupon Codes..
|
||||
url(r'^get_coupon_codes', api.get_coupon_codes, name='get_coupon_codes'),
|
||||
|
||||
# spoc gradebook
|
||||
url(r'^gradebook$', gradebook_api.spoc_gradebook, name='spoc_gradebook'),
|
||||
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
"""
|
||||
E-commerce Tab Instructor Dashboard Coupons Operations views
|
||||
"""
|
||||
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
import pytz
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.http import require_POST
|
||||
from opaque_keys.edx.locator import CourseKey
|
||||
from six import text_type
|
||||
|
||||
from shoppingcart.models import Coupon, CourseRegistrationCode
|
||||
from util.json_request import JsonResponse
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def remove_coupon(request, course_id):
|
||||
"""
|
||||
remove the coupon against the coupon id
|
||||
set the coupon is_active flag to false
|
||||
"""
|
||||
coupon_id = request.POST.get('id', None)
|
||||
if not coupon_id:
|
||||
return JsonResponse({
|
||||
'message': _('coupon id is None')
|
||||
}, status=400) # status code 400: Bad Request
|
||||
|
||||
try:
|
||||
coupon = Coupon.objects.get(id=coupon_id)
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse({
|
||||
'message': _(u'coupon with the coupon id ({coupon_id}) DoesNotExist').format(coupon_id=coupon_id)
|
||||
}, status=400) # status code 400: Bad Request
|
||||
if not coupon.is_active:
|
||||
return JsonResponse({
|
||||
'message': _(u'coupon with the coupon id ({coupon_id}) is already inactive').format(coupon_id=coupon_id)
|
||||
}, status=400) # status code 400: Bad Request
|
||||
coupon.is_active = False
|
||||
coupon.save()
|
||||
return JsonResponse({
|
||||
'message': _(u'coupon with the coupon id ({coupon_id}) updated successfully').format(coupon_id=coupon_id)
|
||||
}) # status code 200: OK by default
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def add_coupon(request, course_id):
|
||||
"""
|
||||
add coupon in the Coupons Table
|
||||
"""
|
||||
code = request.POST.get('code')
|
||||
|
||||
# check if the code is already in the Coupons Table and active
|
||||
try:
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
coupon = Coupon.objects.get(is_active=True, code=code, course_id=course_id)
|
||||
except Coupon.DoesNotExist:
|
||||
# check if the coupon code is in the CourseRegistrationCode Table
|
||||
course_registration_code = CourseRegistrationCode.objects.filter(code=code)
|
||||
if course_registration_code:
|
||||
return JsonResponse(
|
||||
{'message': _(u"The code ({code}) that you have tried to define is already in use as a registration code").format(code=code)},
|
||||
status=400) # status code 400: Bad Request
|
||||
|
||||
description = request.POST.get('description')
|
||||
course_id = request.POST.get('course_id')
|
||||
try:
|
||||
discount = int(request.POST.get('discount'))
|
||||
except ValueError:
|
||||
return JsonResponse({
|
||||
'message': _("Please Enter the Integer Value for Coupon Discount")
|
||||
}, status=400) # status code 400: Bad Request
|
||||
|
||||
if discount > 100 or discount < 0:
|
||||
return JsonResponse({
|
||||
'message': _("Please Enter the Coupon Discount Value Less than or Equal to 100")
|
||||
}, status=400) # status code 400: Bad Request
|
||||
expiration_date = None
|
||||
if request.POST.get('expiration_date'):
|
||||
expiration_date = request.POST.get('expiration_date')
|
||||
try:
|
||||
expiration_date = datetime.datetime.strptime(expiration_date, "%m/%d/%Y").replace(tzinfo=pytz.UTC) + datetime.timedelta(days=1)
|
||||
except ValueError:
|
||||
return JsonResponse({
|
||||
'message': _("Please enter the date in this format i-e month/day/year")
|
||||
}, status=400) # status code 400: Bad Request
|
||||
|
||||
coupon = Coupon(
|
||||
code=code, description=description,
|
||||
course_id=course_id,
|
||||
percentage_discount=discount,
|
||||
created_by_id=request.user.id,
|
||||
expiration_date=expiration_date
|
||||
)
|
||||
coupon.save()
|
||||
return JsonResponse(
|
||||
{'message': _(u"coupon with the coupon code ({code}) added successfully").format(code=code)}
|
||||
)
|
||||
|
||||
if coupon:
|
||||
return JsonResponse(
|
||||
{'message': _(u"coupon with the coupon code ({code}) already exists for this course").format(code=code)},
|
||||
status=400) # status code 400: Bad Request
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def update_coupon(request, course_id):
|
||||
"""
|
||||
update the coupon object in the database
|
||||
"""
|
||||
coupon_id = request.POST.get('coupon_id', None)
|
||||
if not coupon_id:
|
||||
return JsonResponse({'message': _("coupon id not found")}, status=400) # status code 400: Bad Request
|
||||
|
||||
try:
|
||||
coupon = Coupon.objects.get(pk=coupon_id)
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse(
|
||||
{'message': _(u"coupon with the coupon id ({coupon_id}) DoesNotExist").format(coupon_id=coupon_id)},
|
||||
status=400) # status code 400: Bad Request
|
||||
|
||||
description = request.POST.get('description')
|
||||
coupon.description = description
|
||||
coupon.save()
|
||||
return JsonResponse(
|
||||
{'message': _(u"coupon with the coupon id ({coupon_id}) updated Successfully").format(coupon_id=coupon_id)}
|
||||
)
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def get_coupon_info(request, course_id):
|
||||
"""
|
||||
get the coupon information to display in the pop up form
|
||||
"""
|
||||
coupon_id = request.POST.get('id', None)
|
||||
if not coupon_id:
|
||||
return JsonResponse({
|
||||
'message': _("coupon id not found")
|
||||
}, status=400) # status code 400: Bad Request
|
||||
|
||||
try:
|
||||
coupon = Coupon.objects.get(id=coupon_id)
|
||||
except ObjectDoesNotExist:
|
||||
return JsonResponse({
|
||||
'message': _(u"coupon with the coupon id ({coupon_id}) DoesNotExist").format(coupon_id=coupon_id)
|
||||
}, status=400) # status code 400: Bad Request
|
||||
|
||||
if not coupon.is_active:
|
||||
return JsonResponse({
|
||||
'message': _(u"coupon with the coupon id ({coupon_id}) is already inactive").format(coupon_id=coupon_id)
|
||||
}, status=400) # status code 400: Bad Request
|
||||
|
||||
expiry_date = coupon.display_expiry_date
|
||||
return JsonResponse({
|
||||
'coupon_code': coupon.code,
|
||||
'coupon_description': coupon.description,
|
||||
'coupon_course_id': text_type(coupon.course_id),
|
||||
'coupon_discount': coupon.percentage_discount,
|
||||
'expiry_date': expiry_date,
|
||||
'message': _(u'coupon with the coupon id ({coupon_id}) updated successfully').format(coupon_id=coupon_id)
|
||||
}) # status code 200: OK by default
|
||||
@@ -1,132 +0,0 @@
|
||||
"""
|
||||
E-commerce Tab Instructor Dashboard Query Registration Code Status.
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.decorators.http import require_GET, require_POST
|
||||
from opaque_keys.edx.locator import CourseKey
|
||||
|
||||
from lms.djangoapps.courseware.courses import get_course_by_id
|
||||
from lms.djangoapps.instructor.enrollment import get_email_params, send_mail_to_student
|
||||
from lms.djangoapps.instructor.views.api import require_course_permission
|
||||
from shoppingcart.models import CourseRegistrationCode, RegistrationCodeRedemption
|
||||
from student.models import CourseEnrollment
|
||||
from util.json_request import JsonResponse
|
||||
|
||||
from .. import permissions
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.VIEW_REGISTRATION)
|
||||
@require_GET
|
||||
def look_up_registration_code(request, course_id):
|
||||
"""
|
||||
Look for the registration_code in the database.
|
||||
and check if it is still valid, allowed to redeem or not.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
code = request.GET.get('registration_code')
|
||||
course = get_course_by_id(course_key, depth=0)
|
||||
try:
|
||||
registration_code = CourseRegistrationCode.objects.get(code=code)
|
||||
except CourseRegistrationCode.DoesNotExist:
|
||||
return JsonResponse({
|
||||
'is_registration_code_exists': False,
|
||||
'is_registration_code_valid': False,
|
||||
'is_registration_code_redeemed': False,
|
||||
'message': _(u'The enrollment code ({code}) was not found for the {course_name} course.').format(
|
||||
code=code, course_name=course.display_name
|
||||
)
|
||||
}, status=400) # status code 200: OK by default
|
||||
|
||||
reg_code_already_redeemed = RegistrationCodeRedemption.is_registration_code_redeemed(code)
|
||||
|
||||
registration_code_detail_url = reverse('registration_code_details', kwargs={'course_id': str(course_id)})
|
||||
|
||||
return JsonResponse({
|
||||
'is_registration_code_exists': True,
|
||||
'is_registration_code_valid': registration_code.is_valid,
|
||||
'is_registration_code_redeemed': reg_code_already_redeemed,
|
||||
'registration_code_detail_url': registration_code_detail_url
|
||||
}) # status code 200: OK by default
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(permissions.VIEW_REGISTRATION)
|
||||
@require_POST
|
||||
def registration_code_details(request, course_id):
|
||||
"""
|
||||
Post handler to mark the registration code as
|
||||
1) valid
|
||||
2) invalid
|
||||
3) Unredeem.
|
||||
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
code = request.POST.get('registration_code')
|
||||
action_type = request.POST.get('action_type')
|
||||
course = get_course_by_id(course_key, depth=0)
|
||||
action_type_messages = {
|
||||
'invalidate_registration_code': _('This enrollment code has been canceled. It can no longer be used.'),
|
||||
'unredeem_registration_code': _('This enrollment code has been marked as unused.'),
|
||||
'validate_registration_code': _('The enrollment code has been restored.')
|
||||
}
|
||||
try:
|
||||
registration_code = CourseRegistrationCode.objects.get(code=code)
|
||||
except CourseRegistrationCode.DoesNotExist:
|
||||
return JsonResponse({
|
||||
'message': _(u'The enrollment code ({code}) was not found for the {course_name} course.').format(
|
||||
code=code, course_name=course.display_name
|
||||
)}, status=400)
|
||||
|
||||
if action_type == 'invalidate_registration_code':
|
||||
registration_code.is_valid = False
|
||||
registration_code.save()
|
||||
if RegistrationCodeRedemption.is_registration_code_redeemed(code):
|
||||
code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(code, course_key)
|
||||
delete_redemption_entry(request, code_redemption, course_key)
|
||||
|
||||
if action_type == 'validate_registration_code':
|
||||
registration_code.is_valid = True
|
||||
registration_code.save()
|
||||
|
||||
if action_type == 'unredeem_registration_code':
|
||||
code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(code, course_key)
|
||||
if code_redemption is None:
|
||||
return JsonResponse({
|
||||
'message': _(u'The redemption does not exist against enrollment code ({code}).').format(
|
||||
code=code)}, status=400)
|
||||
|
||||
delete_redemption_entry(request, code_redemption, course_key)
|
||||
|
||||
return JsonResponse({'message': action_type_messages[action_type]})
|
||||
|
||||
|
||||
def delete_redemption_entry(request, code_redemption, course_key):
|
||||
"""
|
||||
delete the redemption entry from the table and
|
||||
unenroll the user who used the registration code
|
||||
for the enrollment and send him/her the unenrollment email.
|
||||
"""
|
||||
user = code_redemption.redeemed_by
|
||||
email_address = code_redemption.redeemed_by.email
|
||||
full_name = code_redemption.redeemed_by.profile.name
|
||||
CourseEnrollment.unenroll(user, course_key, skip_refund=True)
|
||||
|
||||
course = get_course_by_id(course_key, depth=0)
|
||||
email_params = get_email_params(course, True, secure=request.is_secure())
|
||||
email_params['message_type'] = 'enrolled_unenroll'
|
||||
email_params['email_address'] = email_address
|
||||
email_params['full_name'] = full_name
|
||||
send_mail_to_student(email_address, email_params)
|
||||
|
||||
# remove the redemption entry from the database.
|
||||
log.info(u'deleting redemption entry (%s) from the database.', code_redemption.id)
|
||||
code_redemption.delete()
|
||||
@@ -37,16 +37,6 @@ from lms.djangoapps.instructor_analytics.basic import (
|
||||
sale_record_features
|
||||
)
|
||||
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
|
||||
from shoppingcart.models import (
|
||||
Coupon,
|
||||
CouponRedemption,
|
||||
CourseRegCodeItem,
|
||||
CourseRegistrationCode,
|
||||
CourseRegistrationCodeInvoiceItem,
|
||||
Invoice,
|
||||
Order,
|
||||
RegistrationCodeRedemption
|
||||
)
|
||||
from student.models import CourseEnrollment, CourseEnrollmentAllowed
|
||||
from student.roles import CourseSalesAdminRole
|
||||
from student.tests.factories import UserFactory
|
||||
@@ -296,362 +286,3 @@ class TestAnalyticsBasic(ModuleStoreTestCase):
|
||||
self.assertEqual(len(proctored_exam_attempts), 3)
|
||||
for proctored_exam_attempt in proctored_exam_attempts:
|
||||
self.assertEqual(set(proctored_exam_attempt.keys()), set(query_features))
|
||||
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||
class TestCourseSaleRecordsAnalyticsBasic(ModuleStoreTestCase):
|
||||
""" Test basic course sale records analytics functions. """
|
||||
def setUp(self):
|
||||
"""
|
||||
Fixtures.
|
||||
"""
|
||||
super(TestCourseSaleRecordsAnalyticsBasic, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.cost = 40
|
||||
self.course_mode = CourseMode(
|
||||
course_id=self.course.id, mode_slug="honor",
|
||||
mode_display_name="honor cert", min_price=self.cost
|
||||
)
|
||||
self.course_mode.save()
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
|
||||
def test_course_sale_features(self):
|
||||
|
||||
query_features = [
|
||||
'company_name', 'company_contact_name', 'company_contact_email', 'total_codes', 'total_used_codes',
|
||||
'total_amount', 'created', 'customer_reference_number', 'recipient_name', 'recipient_email',
|
||||
'created_by', 'internal_reference', 'invoice_number', 'codes', 'course_id'
|
||||
]
|
||||
|
||||
#create invoice
|
||||
sale_invoice = Invoice.objects.create(
|
||||
total_amount=1234.32, company_name='Test1', company_contact_name='TestName',
|
||||
company_contact_email='test@company.com', recipient_name='Testw_1', recipient_email='test2@test.com',
|
||||
customer_reference_number='2Fwe23S', internal_reference="ABC", course_id=self.course.id
|
||||
)
|
||||
invoice_item = CourseRegistrationCodeInvoiceItem.objects.create(
|
||||
invoice=sale_invoice,
|
||||
qty=1,
|
||||
unit_price=1234.32,
|
||||
course_id=self.course.id
|
||||
)
|
||||
for i in range(5):
|
||||
course_code = CourseRegistrationCode(
|
||||
code="test_code{}".format(i), course_id=text_type(self.course.id),
|
||||
created_by=self.instructor, invoice=sale_invoice, invoice_item=invoice_item, mode_slug='honor'
|
||||
)
|
||||
course_code.save()
|
||||
|
||||
course_sale_records_list = sale_record_features(self.course.id, query_features)
|
||||
|
||||
for sale_record in course_sale_records_list:
|
||||
self.assertEqual(sale_record['total_amount'], sale_invoice.total_amount)
|
||||
self.assertEqual(sale_record['recipient_email'], sale_invoice.recipient_email)
|
||||
self.assertEqual(sale_record['recipient_name'], sale_invoice.recipient_name)
|
||||
self.assertEqual(sale_record['company_name'], sale_invoice.company_name)
|
||||
self.assertEqual(sale_record['company_contact_name'], sale_invoice.company_contact_name)
|
||||
self.assertEqual(sale_record['company_contact_email'], sale_invoice.company_contact_email)
|
||||
self.assertEqual(sale_record['internal_reference'], sale_invoice.internal_reference)
|
||||
self.assertEqual(sale_record['customer_reference_number'], sale_invoice.customer_reference_number)
|
||||
self.assertEqual(sale_record['invoice_number'], sale_invoice.id)
|
||||
self.assertEqual(sale_record['created_by'], self.instructor)
|
||||
self.assertEqual(sale_record['total_used_codes'], 0)
|
||||
self.assertEqual(sale_record['total_codes'], 5)
|
||||
|
||||
def test_course_sale_no_codes(self):
|
||||
|
||||
query_features = [
|
||||
'company_name', 'company_contact_name', 'company_contact_email', 'total_codes', 'total_used_codes',
|
||||
'total_amount', 'created', 'customer_reference_number', 'recipient_name', 'recipient_email',
|
||||
'created_by', 'internal_reference', 'invoice_number', 'codes', 'course_id'
|
||||
]
|
||||
|
||||
#create invoice
|
||||
sale_invoice = Invoice.objects.create(
|
||||
total_amount=0.00, company_name='Test1', company_contact_name='TestName',
|
||||
company_contact_email='test@company.com', recipient_name='Testw_1', recipient_email='test2@test.com',
|
||||
customer_reference_number='2Fwe23S', internal_reference="ABC", course_id=self.course.id
|
||||
)
|
||||
CourseRegistrationCodeInvoiceItem.objects.create(
|
||||
invoice=sale_invoice,
|
||||
qty=0,
|
||||
unit_price=0.00,
|
||||
course_id=self.course.id
|
||||
)
|
||||
|
||||
course_sale_records_list = sale_record_features(self.course.id, query_features)
|
||||
|
||||
for sale_record in course_sale_records_list:
|
||||
self.assertEqual(sale_record['total_amount'], sale_invoice.total_amount)
|
||||
self.assertEqual(sale_record['recipient_email'], sale_invoice.recipient_email)
|
||||
self.assertEqual(sale_record['recipient_name'], sale_invoice.recipient_name)
|
||||
self.assertEqual(sale_record['company_name'], sale_invoice.company_name)
|
||||
self.assertEqual(sale_record['company_contact_name'], sale_invoice.company_contact_name)
|
||||
self.assertEqual(sale_record['company_contact_email'], sale_invoice.company_contact_email)
|
||||
self.assertEqual(sale_record['internal_reference'], sale_invoice.internal_reference)
|
||||
self.assertEqual(sale_record['customer_reference_number'], sale_invoice.customer_reference_number)
|
||||
self.assertEqual(sale_record['invoice_number'], sale_invoice.id)
|
||||
self.assertEqual(sale_record['created_by'], None)
|
||||
self.assertEqual(sale_record['total_used_codes'], 0)
|
||||
self.assertEqual(sale_record['total_codes'], 0)
|
||||
|
||||
def test_sale_order_features_with_discount(self):
|
||||
"""
|
||||
Test Order Sales Report CSV
|
||||
"""
|
||||
query_features = [
|
||||
('id', 'Order Id'),
|
||||
('company_name', 'Company Name'),
|
||||
('company_contact_name', 'Company Contact Name'),
|
||||
('company_contact_email', 'Company Contact Email'),
|
||||
('total_amount', 'Total Amount'),
|
||||
('total_codes', 'Total Codes'),
|
||||
('total_used_codes', 'Total Used Codes'),
|
||||
('logged_in_username', 'Login Username'),
|
||||
('logged_in_email', 'Login User Email'),
|
||||
('purchase_time', 'Date of Sale'),
|
||||
('customer_reference_number', 'Customer Reference Number'),
|
||||
('recipient_name', 'Recipient Name'),
|
||||
('recipient_email', 'Recipient Email'),
|
||||
('bill_to_street1', 'Street 1'),
|
||||
('bill_to_street2', 'Street 2'),
|
||||
('bill_to_city', 'City'),
|
||||
('bill_to_state', 'State'),
|
||||
('bill_to_postalcode', 'Postal Code'),
|
||||
('bill_to_country', 'Country'),
|
||||
('order_type', 'Order Type'),
|
||||
('status', 'Order Item Status'),
|
||||
('coupon_code', 'Coupon Code'),
|
||||
('unit_cost', 'Unit Price'),
|
||||
('list_price', 'List Price'),
|
||||
('codes', 'Registration Codes'),
|
||||
('course_id', 'Course Id')
|
||||
]
|
||||
# add the coupon code for the course
|
||||
coupon = Coupon(
|
||||
code='test_code',
|
||||
description='test_description',
|
||||
course_id=self.course.id,
|
||||
percentage_discount='10',
|
||||
created_by=self.instructor,
|
||||
is_active=True
|
||||
)
|
||||
coupon.save()
|
||||
order = Order.get_cart_for_user(self.instructor)
|
||||
order.order_type = 'business'
|
||||
order.save()
|
||||
order.add_billing_details(
|
||||
company_name='Test Company',
|
||||
company_contact_name='Test',
|
||||
company_contact_email='test@123',
|
||||
recipient_name='R1', recipient_email='',
|
||||
customer_reference_number='PO#23'
|
||||
)
|
||||
CourseRegCodeItem.add_to_order(order, self.course.id, 4)
|
||||
# apply the coupon code to the item in the cart
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': coupon.code})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
order.purchase()
|
||||
|
||||
# get the updated item
|
||||
item = order.orderitem_set.all().select_subclasses()[0]
|
||||
# get the redeemed coupon information
|
||||
coupon_redemption = CouponRedemption.objects.select_related('coupon').filter(order=order)
|
||||
|
||||
db_columns = [x[0] for x in query_features]
|
||||
sale_order_records_list = sale_order_record_features(self.course.id, db_columns)
|
||||
|
||||
for sale_order_record in sale_order_records_list:
|
||||
self.assertEqual(sale_order_record['recipient_email'], order.recipient_email)
|
||||
self.assertEqual(sale_order_record['recipient_name'], order.recipient_name)
|
||||
self.assertEqual(sale_order_record['company_name'], order.company_name)
|
||||
self.assertEqual(sale_order_record['company_contact_name'], order.company_contact_name)
|
||||
self.assertEqual(sale_order_record['company_contact_email'], order.company_contact_email)
|
||||
self.assertEqual(sale_order_record['customer_reference_number'], order.customer_reference_number)
|
||||
self.assertEqual(sale_order_record['unit_cost'], item.unit_cost)
|
||||
self.assertEqual(sale_order_record['list_price'], item.list_price)
|
||||
self.assertEqual(sale_order_record['status'], item.status)
|
||||
self.assertEqual(sale_order_record['coupon_code'], coupon_redemption[0].coupon.code)
|
||||
|
||||
def test_sale_order_features_without_discount(self):
|
||||
"""
|
||||
Test Order Sales Report CSV
|
||||
"""
|
||||
query_features = [
|
||||
('id', 'Order Id'),
|
||||
('company_name', 'Company Name'),
|
||||
('company_contact_name', 'Company Contact Name'),
|
||||
('company_contact_email', 'Company Contact Email'),
|
||||
('total_amount', 'Total Amount'),
|
||||
('total_codes', 'Total Codes'),
|
||||
('total_used_codes', 'Total Used Codes'),
|
||||
('logged_in_username', 'Login Username'),
|
||||
('logged_in_email', 'Login User Email'),
|
||||
('purchase_time', 'Date of Sale'),
|
||||
('customer_reference_number', 'Customer Reference Number'),
|
||||
('recipient_name', 'Recipient Name'),
|
||||
('recipient_email', 'Recipient Email'),
|
||||
('bill_to_street1', 'Street 1'),
|
||||
('bill_to_street2', 'Street 2'),
|
||||
('bill_to_city', 'City'),
|
||||
('bill_to_state', 'State'),
|
||||
('bill_to_postalcode', 'Postal Code'),
|
||||
('bill_to_country', 'Country'),
|
||||
('order_type', 'Order Type'),
|
||||
('status', 'Order Item Status'),
|
||||
('coupon_code', 'Coupon Code'),
|
||||
('unit_cost', 'Unit Price'),
|
||||
('list_price', 'List Price'),
|
||||
('codes', 'Registration Codes'),
|
||||
('course_id', 'Course Id'),
|
||||
('quantity', 'Quantity'),
|
||||
('total_discount', 'Total Discount'),
|
||||
('total_amount', 'Total Amount Paid'),
|
||||
]
|
||||
# add the coupon code for the course
|
||||
order = Order.get_cart_for_user(self.instructor)
|
||||
order.order_type = 'business'
|
||||
order.save()
|
||||
order.add_billing_details(
|
||||
company_name='Test Company',
|
||||
company_contact_name='Test',
|
||||
company_contact_email='test@123',
|
||||
recipient_name='R1', recipient_email='',
|
||||
customer_reference_number='PO#23'
|
||||
)
|
||||
CourseRegCodeItem.add_to_order(order, self.course.id, 4)
|
||||
order.purchase()
|
||||
|
||||
# get the updated item
|
||||
item = order.orderitem_set.all().select_subclasses()[0]
|
||||
|
||||
db_columns = [x[0] for x in query_features]
|
||||
sale_order_records_list = sale_order_record_features(self.course.id, db_columns)
|
||||
|
||||
for sale_order_record in sale_order_records_list:
|
||||
self.assertEqual(sale_order_record['recipient_email'], order.recipient_email)
|
||||
self.assertEqual(sale_order_record['recipient_name'], order.recipient_name)
|
||||
self.assertEqual(sale_order_record['company_name'], order.company_name)
|
||||
self.assertEqual(sale_order_record['company_contact_name'], order.company_contact_name)
|
||||
self.assertEqual(sale_order_record['company_contact_email'], order.company_contact_email)
|
||||
self.assertEqual(sale_order_record['customer_reference_number'], order.customer_reference_number)
|
||||
self.assertEqual(sale_order_record['unit_cost'], item.unit_cost)
|
||||
# Make sure list price is not None and matches the unit price since no discount was applied.
|
||||
self.assertIsNotNone(sale_order_record['list_price'])
|
||||
self.assertEqual(sale_order_record['list_price'], item.unit_cost)
|
||||
self.assertEqual(sale_order_record['status'], item.status)
|
||||
self.assertEqual(sale_order_record['coupon_code'], 'N/A')
|
||||
self.assertEqual(sale_order_record['total_amount'], item.unit_cost * item.qty)
|
||||
self.assertEqual(sale_order_record['total_discount'], 0)
|
||||
self.assertEqual(sale_order_record['quantity'], item.qty)
|
||||
|
||||
|
||||
class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
|
||||
""" Test basic course registration codes analytics functions. """
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Fixtures.
|
||||
"""
|
||||
super(TestCourseRegistrationCodeAnalyticsBasic, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
CourseSalesAdminRole(self.course.id).add_users(self.instructor)
|
||||
|
||||
# Create a paid course mode.
|
||||
mode = CourseModeFactory.create(course_id=self.course.id, min_price=1)
|
||||
|
||||
url = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 12, 'company_name': 'Test Group', 'unit_price': 122.45,
|
||||
'company_contact_name': 'TestName', 'company_contact_email': 'test@company.com', 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street', 'address_line_2': '',
|
||||
'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(url, data, **{'HTTP_HOST': 'localhost'})
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
|
||||
def test_course_registration_features(self):
|
||||
query_features = [
|
||||
'code', 'redeem_code_url', 'course_id', 'company_name', 'created_by',
|
||||
'redeemed_by', 'invoice_id', 'purchaser', 'customer_reference_number', 'internal_reference'
|
||||
]
|
||||
order = Order(user=self.instructor, status='purchased')
|
||||
order.save()
|
||||
|
||||
registration_code_redemption = RegistrationCodeRedemption(
|
||||
registration_code_id=1, redeemed_by=self.instructor
|
||||
)
|
||||
registration_code_redemption.save()
|
||||
registration_codes = CourseRegistrationCode.objects.all()
|
||||
course_registration_list = course_registration_features(query_features, registration_codes, csv_type='download')
|
||||
self.assertEqual(len(course_registration_list), len(registration_codes))
|
||||
for course_registration in course_registration_list:
|
||||
self.assertEqual(set(course_registration.keys()), set(query_features))
|
||||
self.assertIn(course_registration['code'], [registration_code.code for registration_code in registration_codes])
|
||||
self.assertIn(
|
||||
course_registration['course_id'],
|
||||
[text_type(registration_code.course_id) for registration_code in registration_codes]
|
||||
)
|
||||
self.assertIn(
|
||||
course_registration['company_name'],
|
||||
[
|
||||
registration_code.invoice_item.invoice.company_name
|
||||
for registration_code in registration_codes
|
||||
]
|
||||
)
|
||||
self.assertIn(
|
||||
course_registration['invoice_id'],
|
||||
[
|
||||
registration_code.invoice_item.invoice_id
|
||||
for registration_code in registration_codes
|
||||
]
|
||||
)
|
||||
|
||||
def test_coupon_codes_features(self):
|
||||
query_features = [
|
||||
'course_id', 'percentage_discount', 'code_redeemed_count', 'description', 'expiration_date',
|
||||
'total_discounted_amount', 'total_discounted_seats'
|
||||
]
|
||||
for i in range(10):
|
||||
coupon = Coupon(
|
||||
code='test_code{0}'.format(i),
|
||||
description='test_description',
|
||||
course_id=self.course.id, percentage_discount='{0}'.format(i),
|
||||
created_by=self.instructor,
|
||||
is_active=True
|
||||
)
|
||||
coupon.save()
|
||||
#now create coupons with the expiration dates
|
||||
for i in range(5):
|
||||
coupon = Coupon(
|
||||
code='coupon{0}'.format(i), description='test_description', course_id=self.course.id,
|
||||
percentage_discount='{0}'.format(i), created_by=self.instructor, is_active=True,
|
||||
expiration_date=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=2)
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
active_coupons = Coupon.objects.filter(
|
||||
Q(course_id=self.course.id),
|
||||
Q(is_active=True),
|
||||
Q(expiration_date__gt=datetime.datetime.now(pytz.UTC)) |
|
||||
Q(expiration_date__isnull=True)
|
||||
)
|
||||
active_coupons_list = coupon_codes_features(query_features, active_coupons, self.course.id)
|
||||
self.assertEqual(len(active_coupons_list), len(active_coupons))
|
||||
for active_coupon in active_coupons_list:
|
||||
self.assertEqual(set(active_coupon.keys()), set(query_features))
|
||||
self.assertIn(active_coupon['percentage_discount'], [coupon.percentage_discount for coupon in active_coupons])
|
||||
self.assertIn(active_coupon['description'], [coupon.description for coupon in active_coupons])
|
||||
if active_coupon['expiration_date']:
|
||||
self.assertIn(active_coupon['expiration_date'], [coupon.display_expiry_date for coupon in active_coupons])
|
||||
self.assertIn(
|
||||
active_coupon['course_id'],
|
||||
[text_type(coupon.course_id) for coupon in active_coupons]
|
||||
)
|
||||
|
||||
@@ -73,15 +73,6 @@ from openedx.core.djangoapps.credit.tests.factories import CreditCourseFactory
|
||||
from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme
|
||||
from openedx.core.djangoapps.util.testing import ContentGroupTestCase, TestConditionalContent
|
||||
from openedx.core.lib.teams_config import TeamsConfig
|
||||
from shoppingcart.models import (
|
||||
Coupon,
|
||||
CourseRegistrationCode,
|
||||
CourseRegistrationCodeInvoiceItem,
|
||||
Invoice,
|
||||
InvoiceTransaction,
|
||||
Order,
|
||||
PaidCourseRegistration
|
||||
)
|
||||
from student.models import ALLOWEDTOENROLL_TO_ENROLLED, CourseEnrollment, CourseEnrollmentAllowed, ManualEnrollmentAudit
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from survey.models import SurveyAnswer, SurveyForm
|
||||
@@ -668,209 +659,6 @@ class TestProblemResponsesReport(TestReportMixin, InstructorTaskModuleTestCase):
|
||||
self.assertIn("report_name", result)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||
class TestInstructorDetailedEnrollmentReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
"""
|
||||
Tests that CSV detailed enrollment generation works.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestInstructorDetailedEnrollmentReport, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
CourseModeFactory.create(
|
||||
course_id=self.course.id,
|
||||
min_price=50,
|
||||
mode_slug=CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG
|
||||
)
|
||||
|
||||
# create testing invoice 1
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.sale_invoice_1 = Invoice.objects.create(
|
||||
total_amount=1234.32, company_name='Test1', company_contact_name='TestName',
|
||||
company_contact_email='Test@company.com',
|
||||
recipient_name='Testw', recipient_email='test1@test.com', customer_reference_number='2Fwe23S',
|
||||
internal_reference="A", course_id=self.course.id, is_valid=True
|
||||
)
|
||||
self.invoice_item = CourseRegistrationCodeInvoiceItem.objects.create(
|
||||
invoice=self.sale_invoice_1,
|
||||
qty=1,
|
||||
unit_price=1234.32,
|
||||
course_id=self.course.id
|
||||
)
|
||||
|
||||
def test_success(self):
|
||||
self.create_student('student', 'student@example.com')
|
||||
task_input = {'features': []}
|
||||
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
|
||||
result = upload_enrollment_report(None, None, self.course.id, task_input, 'generating_enrollment_report')
|
||||
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
|
||||
def test_student_paid_course_enrollment_report(self):
|
||||
"""
|
||||
test to check the paid user enrollment csv report status
|
||||
and enrollment source.
|
||||
"""
|
||||
student = UserFactory()
|
||||
student_cart = Order.get_cart_for_user(student)
|
||||
PaidCourseRegistration.add_to_order(student_cart, self.course.id)
|
||||
student_cart.purchase()
|
||||
|
||||
task_input = {'features': []}
|
||||
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
|
||||
result = upload_enrollment_report(None, None, self.course.id, task_input, 'generating_enrollment_report')
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
self._verify_cell_data_in_csv(student.username, 'Enrollment Source', 'Credit Card - Individual')
|
||||
self._verify_cell_data_in_csv(student.username, 'Payment Status', 'purchased')
|
||||
|
||||
def test_student_manually_enrolled_in_detailed_enrollment_source(self):
|
||||
"""
|
||||
test to check the manually enrolled user enrollment report status
|
||||
and enrollment source.
|
||||
"""
|
||||
student = UserFactory()
|
||||
enrollment = CourseEnrollment.enroll(student, self.course.id)
|
||||
ManualEnrollmentAudit.create_manual_enrollment_audit(
|
||||
self.instructor, student.email, ALLOWEDTOENROLL_TO_ENROLLED,
|
||||
'manually enrolling unenrolled user', enrollment
|
||||
)
|
||||
|
||||
task_input = {'features': []}
|
||||
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
|
||||
result = upload_enrollment_report(None, None, self.course.id, task_input, 'generating_enrollment_report')
|
||||
|
||||
enrollment_source = u'manually enrolled by username: {username}'.format(
|
||||
username=self.instructor.username)
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
self._verify_cell_data_in_csv(student.username, 'Enrollment Source', enrollment_source)
|
||||
self._verify_cell_data_in_csv(
|
||||
student.username,
|
||||
'Manual (Un)Enrollment Reason',
|
||||
'manually enrolling unenrolled user'
|
||||
)
|
||||
self._verify_cell_data_in_csv(student.username, 'Payment Status', 'TBD')
|
||||
|
||||
def test_student_used_enrollment_code_for_course_enrollment(self):
|
||||
"""
|
||||
test to check the user enrollment source and payment status in the
|
||||
enrollment detailed report
|
||||
"""
|
||||
student = UserFactory()
|
||||
self.client.login(username=student.username, password='test')
|
||||
student_cart = Order.get_cart_for_user(student)
|
||||
paid_course_reg_item = PaidCourseRegistration.add_to_order(student_cart, self.course.id)
|
||||
# update the quantity of the cart item paid_course_reg_item
|
||||
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'),
|
||||
{'ItemId': paid_course_reg_item.id, 'qty': '4'})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
student_cart.purchase()
|
||||
|
||||
course_reg_codes = CourseRegistrationCode.objects.filter(order=student_cart)
|
||||
redeem_url = reverse('register_code_redemption', args=[course_reg_codes[0].code])
|
||||
response = self.client.get(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# check button text
|
||||
self.assertContains(response, 'Activate Course Enrollment')
|
||||
|
||||
response = self.client.post(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
task_input = {'features': []}
|
||||
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
|
||||
result = upload_enrollment_report(None, None, self.course.id, task_input, 'generating_enrollment_report')
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
self._verify_cell_data_in_csv(student.username, 'Enrollment Source', 'Used Registration Code')
|
||||
self._verify_cell_data_in_csv(student.username, 'Payment Status', 'purchased')
|
||||
|
||||
def test_student_used_invoice_unpaid_enrollment_code_for_course_enrollment(self):
|
||||
"""
|
||||
test to check the user enrollment source and payment status in the
|
||||
enrollment detailed report
|
||||
"""
|
||||
student = UserFactory()
|
||||
self.client.login(username=student.username, password='test')
|
||||
|
||||
course_registration_code = CourseRegistrationCode(
|
||||
code='abcde',
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice_1,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug=CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG
|
||||
)
|
||||
course_registration_code.save()
|
||||
|
||||
redeem_url = reverse('register_code_redemption', args=['abcde'])
|
||||
response = self.client.get(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# check button text
|
||||
self.assertContains(response, 'Activate Course Enrollment')
|
||||
|
||||
response = self.client.post(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
task_input = {'features': []}
|
||||
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
|
||||
result = upload_enrollment_report(None, None, self.course.id, task_input, 'generating_enrollment_report')
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
self._verify_cell_data_in_csv(student.username, 'Enrollment Source', 'Used Registration Code')
|
||||
self._verify_cell_data_in_csv(student.username, 'Payment Status', 'Invoice Outstanding')
|
||||
|
||||
def test_student_used_invoice_paid_enrollment_code_for_course_enrollment(self):
|
||||
"""
|
||||
test to check the user enrollment source and payment status in the
|
||||
enrollment detailed report
|
||||
"""
|
||||
student = UserFactory()
|
||||
self.client.login(username=student.username, password='test')
|
||||
invoice_transaction = InvoiceTransaction(
|
||||
invoice=self.sale_invoice_1,
|
||||
amount=self.sale_invoice_1.total_amount,
|
||||
status='completed',
|
||||
created_by=self.instructor,
|
||||
last_modified_by=self.instructor
|
||||
)
|
||||
invoice_transaction.save()
|
||||
course_registration_code = CourseRegistrationCode(
|
||||
code='abcde',
|
||||
course_id=text_type(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice_1,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug=CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG
|
||||
)
|
||||
course_registration_code.save()
|
||||
|
||||
redeem_url = reverse('register_code_redemption', args=['abcde'])
|
||||
response = self.client.get(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# check button text
|
||||
self.assertContains(response, 'Activate Course Enrollment')
|
||||
|
||||
response = self.client.post(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
task_input = {'features': []}
|
||||
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
|
||||
result = upload_enrollment_report(None, None, self.course.id, task_input, 'generating_enrollment_report')
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
self._verify_cell_data_in_csv(student.username, 'Enrollment Source', 'Used Registration Code')
|
||||
self._verify_cell_data_in_csv(student.username, 'Payment Status', 'Invoice Paid')
|
||||
|
||||
def _verify_cell_data_in_csv(self, username, column_header, expected_cell_content):
|
||||
"""
|
||||
Verify that the last ReportStore CSV contains the expected content.
|
||||
"""
|
||||
report_store = ReportStore.from_config(config_name='FINANCIAL_REPORTS')
|
||||
report_csv_filename = report_store.links_for(self.course.id)[0][0]
|
||||
report_path = report_store.path_to(self.course.id, report_csv_filename)
|
||||
with report_store.storage.open(report_path) as csv_file:
|
||||
# Expand the dict reader generator so we don't lose it's content
|
||||
for row in unicodecsv.DictReader(csv_file):
|
||||
if row.get('Username') == username:
|
||||
self.assertEqual(row[column_header], expected_cell_content)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestProblemGradeReport(TestReportMixin, InstructorTaskModuleTestCase):
|
||||
"""
|
||||
@@ -1258,34 +1046,6 @@ class TestExecutiveSummaryReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.student1 = UserFactory()
|
||||
self.student2 = UserFactory()
|
||||
self.student1_cart = Order.get_cart_for_user(self.student1)
|
||||
self.student2_cart = Order.get_cart_for_user(self.student2)
|
||||
|
||||
self.sale_invoice_1 = Invoice.objects.create(
|
||||
total_amount=1234.32, company_name='Test1', company_contact_name='TestName',
|
||||
company_contact_email='Test@company.com',
|
||||
recipient_name='Testw', recipient_email='test1@test.com', customer_reference_number='2Fwe23S',
|
||||
internal_reference="A", course_id=self.course.id, is_valid=True
|
||||
)
|
||||
InvoiceTransaction.objects.create(
|
||||
invoice=self.sale_invoice_1,
|
||||
amount=self.sale_invoice_1.total_amount,
|
||||
status='completed',
|
||||
created_by=self.instructor,
|
||||
last_modified_by=self.instructor
|
||||
)
|
||||
self.invoice_item = CourseRegistrationCodeInvoiceItem.objects.create(
|
||||
invoice=self.sale_invoice_1,
|
||||
qty=10,
|
||||
unit_price=1234.32,
|
||||
course_id=self.course.id
|
||||
)
|
||||
for i in range(5):
|
||||
coupon = Coupon(
|
||||
code='coupon{0}'.format(i), description='test_description', course_id=self.course.id,
|
||||
percentage_discount='{0}'.format(i), created_by=self.instructor, is_active=True,
|
||||
)
|
||||
coupon.save()
|
||||
|
||||
def test_successfully_generate_executive_summary_report(self):
|
||||
"""
|
||||
@@ -1300,66 +1060,6 @@ class TestExecutiveSummaryReport(TestReportMixin, InstructorTaskCourseTestCase):
|
||||
ReportStore.from_config(config_name='FINANCIAL_REPORTS')
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
|
||||
def students_purchases(self):
|
||||
"""
|
||||
Students purchases the courses using enrollment
|
||||
and coupon codes.
|
||||
"""
|
||||
self.client.login(username=self.student1.username, password='test')
|
||||
paid_course_reg_item = PaidCourseRegistration.add_to_order(self.student1_cart, self.course.id)
|
||||
# update the quantity of the cart item paid_course_reg_item
|
||||
resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {
|
||||
'ItemId': paid_course_reg_item.id, 'qty': '4'
|
||||
})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
# apply the coupon code to the item in the cart
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': 'coupon1'})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
self.student1_cart.purchase()
|
||||
|
||||
course_reg_codes = CourseRegistrationCode.objects.filter(order=self.student1_cart)
|
||||
redeem_url = reverse('register_code_redemption', args=[course_reg_codes[0].code])
|
||||
response = self.client.get(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# check button text
|
||||
self.assertContains(response, 'Activate Course Enrollment')
|
||||
|
||||
response = self.client.post(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
self.client.login(username=self.student2.username, password='test')
|
||||
PaidCourseRegistration.add_to_order(self.student2_cart, self.course.id)
|
||||
|
||||
# apply the coupon code to the item in the cart
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': 'coupon1'})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
self.student2_cart.purchase()
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||
def test_generate_executive_summary_report(self):
|
||||
"""
|
||||
test to generate executive summary report
|
||||
and then test the report authenticity.
|
||||
"""
|
||||
self.students_purchases()
|
||||
task_input = {'features': []}
|
||||
with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'):
|
||||
result = upload_exec_summary_report(
|
||||
None, None, self.course.id,
|
||||
task_input, 'generating executive summary report'
|
||||
)
|
||||
report_store = ReportStore.from_config(config_name='FINANCIAL_REPORTS')
|
||||
expected_data = [
|
||||
'Gross Revenue Collected', '$1481.82',
|
||||
'Gross Revenue Pending', '$0.00',
|
||||
'Average Price per Seat', '$296.36',
|
||||
'Number of seats purchased using coupon codes', '<td>2</td>'
|
||||
]
|
||||
self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result)
|
||||
self._verify_html_file_report(report_store, expected_data)
|
||||
|
||||
def _verify_html_file_report(self, report_store, expected_data):
|
||||
"""
|
||||
Verify grade report data.
|
||||
|
||||
@@ -491,353 +491,6 @@ class OrderItemTest(TestCase):
|
||||
self.assertEqual(item.get_list_price(), item.list_price)
|
||||
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||
class PaidCourseRegistrationTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Paid Course Registration Tests.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(PaidCourseRegistrationTest, self).setUp()
|
||||
|
||||
self.user = UserFactory.create()
|
||||
self.user.set_password('password')
|
||||
self.user.save()
|
||||
self.cost = 40
|
||||
self.course = CourseFactory.create()
|
||||
self.course_key = self.course.id
|
||||
self.course_mode = CourseMode(
|
||||
course_id=self.course_key,
|
||||
mode_slug=CourseMode.HONOR,
|
||||
mode_display_name="honor cert",
|
||||
min_price=self.cost
|
||||
)
|
||||
self.course_mode.save()
|
||||
self.percentage_discount = 20.0
|
||||
self.cart = Order.get_cart_for_user(self.user)
|
||||
|
||||
def test_get_total_amount_of_purchased_items(self):
|
||||
"""
|
||||
Test to check the total amount of the
|
||||
purchased items.
|
||||
"""
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug=CourseMode.HONOR)
|
||||
self.cart.purchase()
|
||||
|
||||
total_amount = PaidCourseRegistration.get_total_amount_of_purchased_item(course_key=self.course_key)
|
||||
self.assertEqual(total_amount, 40.00)
|
||||
|
||||
def test_get_total_amount_empty(self):
|
||||
"""
|
||||
Test to check the total amount of the
|
||||
purchased items.
|
||||
"""
|
||||
total_amount = PaidCourseRegistration.get_total_amount_of_purchased_item(course_key=self.course_key)
|
||||
self.assertEqual(total_amount, 0.00)
|
||||
|
||||
def test_add_to_order(self):
|
||||
reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug=CourseMode.HONOR)
|
||||
|
||||
self.assertEqual(reg1.unit_cost, self.cost)
|
||||
self.assertEqual(reg1.line_cost, self.cost)
|
||||
self.assertEqual(reg1.unit_cost, self.course_mode.min_price)
|
||||
self.assertEqual(reg1.mode, "honor")
|
||||
self.assertEqual(reg1.user, self.user)
|
||||
self.assertEqual(reg1.status, "cart")
|
||||
self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_key))
|
||||
self.assertFalse(PaidCourseRegistration.contained_in_order(
|
||||
self.cart, CourseLocator(org="MITx", course="999", run="Robot_Super_Course_abcd"))
|
||||
)
|
||||
|
||||
self.assertEqual(self.cart.total_cost, self.cost)
|
||||
|
||||
def test_order_generated_registration_codes(self):
|
||||
"""
|
||||
Test to check for the order generated registration
|
||||
codes.
|
||||
"""
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
self.cart.purchase()
|
||||
registration_codes = CourseRegistrationCode.order_generated_registration_codes(self.course_key)
|
||||
self.assertEqual(registration_codes.count(), item.qty)
|
||||
|
||||
def test_order_generated_totals(self):
|
||||
"""
|
||||
Test to check for the order generated registration
|
||||
codes.
|
||||
"""
|
||||
|
||||
total_amount = CourseRegCodeItem.get_total_amount_of_purchased_item(self.course_key)
|
||||
self.assertEqual(total_amount, 0)
|
||||
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2, mode_slug=CourseMode.HONOR)
|
||||
self.cart.purchase()
|
||||
registration_codes = CourseRegistrationCode.order_generated_registration_codes(self.course_key)
|
||||
self.assertEqual(registration_codes.count(), item.qty)
|
||||
|
||||
total_amount = CourseRegCodeItem.get_total_amount_of_purchased_item(self.course_key)
|
||||
self.assertEqual(total_amount, 80.00)
|
||||
|
||||
def add_coupon(self, course_key, is_active, code):
|
||||
"""
|
||||
add dummy coupon into models
|
||||
"""
|
||||
Coupon.objects.create(
|
||||
code=code,
|
||||
description='testing code',
|
||||
course_id=course_key,
|
||||
percentage_discount=self.percentage_discount,
|
||||
created_by=self.user,
|
||||
is_active=is_active
|
||||
)
|
||||
|
||||
def login_user(self, username):
|
||||
"""
|
||||
login the user to the platform.
|
||||
"""
|
||||
self.client.login(username=username, password="password")
|
||||
|
||||
def test_get_top_discount_codes_used(self):
|
||||
"""
|
||||
Test to check for the top coupon codes used.
|
||||
"""
|
||||
self.login_user(self.user.username)
|
||||
self.add_coupon(self.course_key, True, 'Ad123asd')
|
||||
self.add_coupon(self.course_key, True, '32213asd')
|
||||
self.purchases_using_coupon_codes()
|
||||
top_discounted_codes = CouponRedemption.get_top_discount_codes_used(self.course_key)
|
||||
self.assertTrue(top_discounted_codes[0]['coupon__code'], 'Ad123asd')
|
||||
self.assertTrue(top_discounted_codes[0]['coupon__used_count'], 1)
|
||||
self.assertTrue(top_discounted_codes[1]['coupon__code'], '32213asd')
|
||||
self.assertTrue(top_discounted_codes[1]['coupon__used_count'], 2)
|
||||
|
||||
def test_get_total_coupon_code_purchases(self):
|
||||
"""
|
||||
Test to assert the number of coupon code purchases.
|
||||
"""
|
||||
self.login_user(self.user.username)
|
||||
self.add_coupon(self.course_key, True, 'Ad123asd')
|
||||
self.add_coupon(self.course_key, True, '32213asd')
|
||||
self.purchases_using_coupon_codes()
|
||||
|
||||
total_coupon_code_purchases = CouponRedemption.get_total_coupon_code_purchases(self.course_key)
|
||||
self.assertTrue(total_coupon_code_purchases['coupon__count'], 3)
|
||||
|
||||
def test_get_self_purchased_seat_count(self):
|
||||
"""
|
||||
Test to assert the number of seats
|
||||
purchased using individual purchases.
|
||||
"""
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
||||
self.cart.purchase()
|
||||
|
||||
test_student = UserFactory.create()
|
||||
test_student.set_password('password')
|
||||
test_student.save()
|
||||
|
||||
self.cart = Order.get_cart_for_user(test_student)
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
||||
self.cart.purchase()
|
||||
|
||||
total_seats_count = PaidCourseRegistration.get_self_purchased_seat_count(course_key=self.course_key)
|
||||
self.assertTrue(total_seats_count, 2)
|
||||
|
||||
def purchases_using_coupon_codes(self):
|
||||
"""
|
||||
helper method that uses coupon codes when purchasing courses.
|
||||
"""
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': 'Ad123asd'})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.cart.purchase()
|
||||
|
||||
self.cart.clear()
|
||||
self.cart = Order.get_cart_for_user(self.user)
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': 'Ad123asd'})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.cart.purchase()
|
||||
|
||||
self.cart.clear()
|
||||
self.cart = Order.get_cart_for_user(self.user)
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': '32213asd'})
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.cart.purchase()
|
||||
|
||||
def test_cart_type_business(self):
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
self.cart.purchase()
|
||||
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_key))
|
||||
# check that the registration codes are generated against the order
|
||||
registration_codes = CourseRegistrationCode.order_generated_registration_codes(self.course_key)
|
||||
self.assertEqual(registration_codes.count(), item.qty)
|
||||
|
||||
def test_regcode_redemptions(self):
|
||||
"""
|
||||
Asserts the data model around RegistrationCodeRedemption
|
||||
"""
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
self.cart.purchase()
|
||||
|
||||
reg_code = CourseRegistrationCode.order_generated_registration_codes(self.course_key)[0]
|
||||
|
||||
enrollment = CourseEnrollment.enroll(self.user, self.course_key)
|
||||
|
||||
redemption = RegistrationCodeRedemption(
|
||||
registration_code=reg_code,
|
||||
redeemed_by=self.user,
|
||||
course_enrollment=enrollment
|
||||
)
|
||||
redemption.save()
|
||||
|
||||
test_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment(enrollment)
|
||||
|
||||
self.assertEqual(test_redemption.id, redemption.id)
|
||||
|
||||
def test_regcode_multi_redemptions(self):
|
||||
"""
|
||||
Asserts the data model around RegistrationCodeRedemption and
|
||||
what happens when we do multiple redemptions by same user
|
||||
"""
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
self.cart.purchase()
|
||||
|
||||
reg_codes = CourseRegistrationCode.order_generated_registration_codes(self.course_key)
|
||||
|
||||
self.assertEqual(len(reg_codes), 2)
|
||||
|
||||
enrollment = CourseEnrollment.enroll(self.user, self.course_key)
|
||||
|
||||
ids = []
|
||||
for reg_code in reg_codes:
|
||||
redemption = RegistrationCodeRedemption(
|
||||
registration_code=reg_code,
|
||||
redeemed_by=self.user,
|
||||
course_enrollment=enrollment
|
||||
)
|
||||
redemption.save()
|
||||
ids.append(redemption.id)
|
||||
|
||||
test_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment(enrollment)
|
||||
|
||||
self.assertIn(test_redemption.id, ids)
|
||||
|
||||
def test_add_with_default_mode(self):
|
||||
"""
|
||||
Tests add_to_cart where the mode specified in the argument is NOT
|
||||
in the database and NOT the default "audit". In this case it
|
||||
just adds the user in the CourseMode.DEFAULT_MODE for free.
|
||||
"""
|
||||
reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key, mode_slug="DNE")
|
||||
|
||||
self.assertEqual(reg1.unit_cost, 0)
|
||||
self.assertEqual(reg1.line_cost, 0)
|
||||
self.assertEqual(reg1.mode, CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG)
|
||||
self.assertEqual(reg1.user, self.user)
|
||||
self.assertEqual(reg1.status, "cart")
|
||||
self.assertEqual(self.cart.total_cost, 0)
|
||||
self.assertTrue(PaidCourseRegistration.contained_in_order(self.cart, self.course_key))
|
||||
|
||||
course_reg_code_item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2, mode_slug="DNE")
|
||||
|
||||
self.assertEqual(course_reg_code_item.unit_cost, 0)
|
||||
self.assertEqual(course_reg_code_item.line_cost, 0)
|
||||
self.assertEqual(course_reg_code_item.mode, CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG)
|
||||
self.assertEqual(course_reg_code_item.user, self.user)
|
||||
self.assertEqual(course_reg_code_item.status, "cart")
|
||||
self.assertEqual(self.cart.total_cost, 0)
|
||||
self.assertTrue(CourseRegCodeItem.contained_in_order(self.cart, self.course_key))
|
||||
|
||||
def test_add_course_reg_item_with_no_course_item(self):
|
||||
fake_course_id = CourseLocator(org="edx", course="fake", run="course")
|
||||
with self.assertRaises(CourseDoesNotExistException):
|
||||
CourseRegCodeItem.add_to_order(self.cart, fake_course_id, 2)
|
||||
|
||||
def test_course_reg_item_already_in_cart(self):
|
||||
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
with self.assertRaises(ItemAlreadyInCartException):
|
||||
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
|
||||
def test_course_reg_item_already_enrolled_in_course(self):
|
||||
CourseEnrollment.enroll(self.user, self.course_key)
|
||||
with self.assertRaises(AlreadyEnrolledInCourseException):
|
||||
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
|
||||
def test_purchased_callback(self):
|
||||
reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
||||
self.cart.purchase()
|
||||
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_key))
|
||||
reg1 = PaidCourseRegistration.objects.get(id=reg1.id) # reload from DB to get side-effect
|
||||
self.assertEqual(reg1.status, "purchased")
|
||||
self.assertIsNotNone(reg1.course_enrollment)
|
||||
self.assertEqual(reg1.course_enrollment.id, CourseEnrollment.objects.get(user=self.user, course_id=self.course_key).id)
|
||||
|
||||
def test_generate_receipt_instructions(self):
|
||||
"""
|
||||
Add 2 courses to the order and make sure the instruction_set only contains 1 element (no dups)
|
||||
"""
|
||||
course2 = CourseFactory.create()
|
||||
course_mode2 = CourseMode(course_id=course2.id,
|
||||
mode_slug="honor",
|
||||
mode_display_name="honor cert",
|
||||
min_price=self.cost)
|
||||
course_mode2.save()
|
||||
pr1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
||||
pr2 = PaidCourseRegistration.add_to_order(self.cart, course2.id)
|
||||
self.cart.purchase()
|
||||
inst_dict, inst_set = self.cart.generate_receipt_instructions()
|
||||
self.assertEqual(2, len(inst_dict))
|
||||
self.assertEqual(1, len(inst_set))
|
||||
self.assertIn("dashboard", inst_set.pop())
|
||||
self.assertIn(pr1.pk_with_subclass, inst_dict)
|
||||
self.assertIn(pr2.pk_with_subclass, inst_dict)
|
||||
|
||||
def test_purchased_callback_exception(self):
|
||||
reg1 = PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
||||
reg1.course_id = CourseLocator(org="changed", course="forsome", run="reason")
|
||||
reg1.save()
|
||||
with self.assertRaises(PurchasedCallbackException):
|
||||
reg1.purchased_callback()
|
||||
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_key))
|
||||
|
||||
reg1.course_id = CourseLocator(org="abc", course="efg", run="hij")
|
||||
reg1.save()
|
||||
with self.assertRaises(PurchasedCallbackException):
|
||||
reg1.purchased_callback()
|
||||
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_key))
|
||||
|
||||
course_reg_code_item = CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2)
|
||||
course_reg_code_item.course_id = CourseLocator(org="changed1", course="forsome1", run="reason1")
|
||||
course_reg_code_item.save()
|
||||
with self.assertRaises(PurchasedCallbackException):
|
||||
course_reg_code_item.purchased_callback()
|
||||
|
||||
def test_user_cart_has_both_items(self):
|
||||
"""
|
||||
This test exists b/c having both CertificateItem and PaidCourseRegistration in an order used to break
|
||||
PaidCourseRegistration.contained_in_order
|
||||
"""
|
||||
cart = Order.get_cart_for_user(self.user)
|
||||
CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor')
|
||||
PaidCourseRegistration.add_to_order(self.cart, self.course_key)
|
||||
self.assertTrue(PaidCourseRegistration.contained_in_order(cart, self.course_key))
|
||||
|
||||
|
||||
class CertificateItemTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for verifying specific CertificateItem functionality
|
||||
|
||||
@@ -1239,94 +1239,6 @@ class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin):
|
||||
self.assertEqual(context['currency_symbol'], 'Rs')
|
||||
self.assertEqual(context['currency'], 'PKR')
|
||||
|
||||
@patch('shoppingcart.views.render_to_response', render_mock)
|
||||
def test_courseregcode_item_total_price(self):
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
CourseRegCodeItem.add_to_order(self.cart, self.course_key, 2, mode_slug=self.course_mode.mode_slug)
|
||||
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
||||
self.assertEqual(CourseRegCodeItem.get_total_amount_of_purchased_item(self.course_key), 80)
|
||||
|
||||
@patch('shoppingcart.views.render_to_response', render_mock)
|
||||
def test_show_receipt_success_with_order_type_business(self):
|
||||
self.cart.order_type = 'business'
|
||||
self.cart.save()
|
||||
reg_item = CourseRegCodeItem.add_to_order(
|
||||
self.cart,
|
||||
self.course_key,
|
||||
2,
|
||||
mode_slug=self.course_mode.mode_slug
|
||||
)
|
||||
self.cart.add_billing_details(company_name='T1Omega', company_contact_name='C1',
|
||||
company_contact_email='test@t1.com', recipient_email='test@t2.com')
|
||||
self.cart.purchase(first='FirstNameTesting123', street1='StreetTesting123')
|
||||
|
||||
# mail is sent to these emails recipient_email, company_contact_email, order.user.email
|
||||
self.assertEqual(len(mail.outbox), 3)
|
||||
|
||||
self.login_user()
|
||||
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# when order_type = 'business' the user is not enrolled in the
|
||||
# course but presented with the enrollment links
|
||||
self.assertFalse(CourseEnrollment.is_enrolled(self.cart.user, self.course_key))
|
||||
self.assertContains(resp, 'FirstNameTesting123')
|
||||
self.assertContains(resp, '80.00')
|
||||
# check for the enrollment codes content
|
||||
self.assertContains(
|
||||
resp,
|
||||
'Please send each professional one of these unique registration codes to enroll into the course.',
|
||||
)
|
||||
|
||||
# fetch the newly generated registration codes
|
||||
course_registration_codes = CourseRegistrationCode.objects.filter(order=self.cart)
|
||||
|
||||
((template, context), _) = render_mock.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'shoppingcart/receipt.html')
|
||||
self.assertEqual(context['order'], self.cart)
|
||||
self.assertIn(reg_item, context['shoppingcart_items'][0])
|
||||
# now check for all the registration codes in the receipt
|
||||
# and all the codes should be unused at this point
|
||||
self.assertIn(course_registration_codes[0].code, context['reg_code_info_list'][0]['code'])
|
||||
self.assertIn(course_registration_codes[1].code, context['reg_code_info_list'][1]['code'])
|
||||
self.assertFalse(context['reg_code_info_list'][0]['is_redeemed'])
|
||||
self.assertFalse(context['reg_code_info_list'][1]['is_redeemed'])
|
||||
|
||||
self.assertContains(
|
||||
resp,
|
||||
self.cart.purchase_time.strftime(u"%B %d, %Y"),
|
||||
)
|
||||
self.assertContains(resp, self.cart.company_name)
|
||||
self.assertContains(resp, self.cart.company_contact_name)
|
||||
self.assertContains(resp, self.cart.company_contact_email)
|
||||
self.assertContains(resp, self.cart.recipient_email)
|
||||
self.assertIn(u"Invoice #{order_id}".format(order_id=self.cart.id), resp.content.decode(resp.charset))
|
||||
codes_string = u'You have successfully purchased <b>{total_registration_codes} course registration codes'
|
||||
self.assertIn(codes_string.format(
|
||||
total_registration_codes=context['total_registration_codes']),
|
||||
resp.content.decode(resp.charset)
|
||||
)
|
||||
|
||||
# now redeem one of registration code from the previous order
|
||||
redeem_url = reverse('register_code_redemption', args=[context['reg_code_info_list'][0]['code']])
|
||||
|
||||
#now activate the user by enrolling him/her to the course
|
||||
response = self.client.post(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'View Dashboard')
|
||||
|
||||
# now view the receipt page again to see if any registration codes
|
||||
# has been expired or not
|
||||
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
((template, context), _) = render_mock.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'shoppingcart/receipt.html')
|
||||
# now check for all the registration codes in the receipt
|
||||
# and one of code should be used at this point
|
||||
self.assertTrue(context['reg_code_info_list'][0]['is_redeemed'])
|
||||
self.assertFalse(context['reg_code_info_list'][1]['is_redeemed'])
|
||||
|
||||
@patch('shoppingcart.views.render_to_response', render_mock)
|
||||
def test_show_receipt_success_with_upgrade(self):
|
||||
|
||||
@@ -1780,108 +1692,6 @@ class RegistrationCodeRedemptionCourseEnrollment(SharedModuleStoreTestCase):
|
||||
|
||||
cache.clear()
|
||||
|
||||
def test_course_enrollment_active_registration_code_redemption(self):
|
||||
"""
|
||||
Test for active registration code course enrollment
|
||||
"""
|
||||
cache.clear()
|
||||
instructor = InstructorFactory(course_key=self.course_key)
|
||||
self.client.login(username=instructor.username, password='test')
|
||||
|
||||
# Registration Code Generation only available to Sales Admins.
|
||||
CourseSalesAdminRole(self.course.id).add_users(instructor)
|
||||
|
||||
url = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': text_type(self.course.id)})
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 12, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com', 'unit_price': 122.45, 'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com', 'address_line_1': 'Portland Street',
|
||||
'address_line_2': '', 'address_line_3': '', 'city': '', 'state': '', 'zip': '', 'country': '',
|
||||
'customer_reference_number': '123A23F', 'internal_reference': '', 'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# get the first registration from the newly created registration codes
|
||||
registration_code = CourseRegistrationCode.objects.all()[0].code
|
||||
redeem_url = reverse('register_code_redemption', args=[registration_code])
|
||||
self.login_user()
|
||||
|
||||
response = self.client.get(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# check button text
|
||||
self.assertContains(response, 'Activate Course Enrollment')
|
||||
|
||||
#now activate the user by enrolling him/her to the course
|
||||
response = self.client.post(redeem_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'View Dashboard')
|
||||
|
||||
#now check that the registration code has already been redeemed and user is already registered in the course
|
||||
RegistrationCodeRedemption.objects.filter(registration_code__code=registration_code)
|
||||
response = self.client.get(redeem_url)
|
||||
self.assertEqual(len(RegistrationCodeRedemption.objects.filter(registration_code__code=registration_code)), 1)
|
||||
self.assertContains(response, "You've clicked a link for an enrollment code that has already been used.")
|
||||
|
||||
#now check that the registration code has already been redeemed
|
||||
response = self.client.post(redeem_url)
|
||||
self.assertContains(response, "You've clicked a link for an enrollment code that has already been used.")
|
||||
|
||||
#now check the response of the dashboard page
|
||||
dashboard_url = reverse('dashboard')
|
||||
response = self.client.get(dashboard_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, self.course.display_name.encode('utf-8'))
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class RedeemCodeEmbargoTests(UrlResetMixin, ModuleStoreTestCase):
|
||||
"""Test blocking redeem code redemption based on country access rules. """
|
||||
|
||||
USERNAME = 'bob'
|
||||
PASSWORD = 'test'
|
||||
|
||||
URLCONF_MODULES = ['openedx.core.djangoapps.embargo']
|
||||
|
||||
@patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
def setUp(self):
|
||||
super(RedeemCodeEmbargoTests, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
|
||||
result = self.client.login(username=self.user.username, password=self.PASSWORD)
|
||||
self.assertTrue(result, msg="Could not log in")
|
||||
|
||||
@ddt.data('get', 'post')
|
||||
@patch.dict(settings.FEATURES, {'EMBARGO': True})
|
||||
def test_registration_code_redemption_embargo(self, method):
|
||||
# Create a valid registration code
|
||||
reg_code = CourseRegistrationCode.objects.create(
|
||||
code="abcd1234",
|
||||
course_id=self.course.id,
|
||||
created_by=self.user
|
||||
)
|
||||
|
||||
# Try to redeem the code from a restricted country
|
||||
with restrict_course(self.course.id) as redirect_url:
|
||||
url = reverse(
|
||||
'register_code_redemption',
|
||||
kwargs={'registration_code': 'abcd1234'}
|
||||
)
|
||||
response = getattr(self.client, method)(url)
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
# The registration code should NOT be redeemed
|
||||
is_redeemed = RegistrationCodeRedemption.objects.filter(
|
||||
registration_code=reg_code
|
||||
).exists()
|
||||
self.assertFalse(is_redeemed)
|
||||
|
||||
# The user should NOT be enrolled
|
||||
is_enrolled = CourseEnrollment.is_enrolled(self.user, self.course.id)
|
||||
self.assertFalse(is_enrolled)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class DonationViewTest(SharedModuleStoreTestCase):
|
||||
|
||||
@@ -37,7 +37,6 @@ from lms.djangoapps.verify_student.views import PayAndVerifyView, checkout_with_
|
||||
from openedx.core.djangoapps.embargo.test_utils import restrict_course
|
||||
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
|
||||
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
|
||||
from shoppingcart.models import CertificateItem, Order
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from util.testing import UrlResetMixin
|
||||
@@ -1225,21 +1224,6 @@ class CheckoutTestMixin(object):
|
||||
self.assertEqual(data, {'foo': 'bar'})
|
||||
|
||||
|
||||
@patch('lms.djangoapps.verify_student.views.checkout_with_shoppingcart', return_value=TEST_PAYMENT_DATA, autospec=True)
|
||||
class TestCreateOrderShoppingCart(CheckoutTestMixin, ModuleStoreTestCase):
|
||||
""" Test view behavior when the shoppingcart is used. """
|
||||
|
||||
def make_sku(self):
|
||||
""" Checkout is handled by shoppingcart when the course mode's sku is empty. """
|
||||
return ''
|
||||
|
||||
def _get_checkout_args(self, patched_create_order):
|
||||
""" Assuming patched_create_order was called, return a mapping containing the call arguments."""
|
||||
return dict(
|
||||
list(zip(('request', 'user', 'course_key', 'course_mode', 'amount'), patched_create_order.call_args[0]))
|
||||
)
|
||||
|
||||
|
||||
@override_settings(ECOMMERCE_API_URL=TEST_API_URL)
|
||||
@patch(
|
||||
'lms.djangoapps.verify_student.views.checkout_with_ecommerce_service',
|
||||
@@ -1304,113 +1288,6 @@ class TestCheckoutWithEcommerceService(ModuleStoreTestCase):
|
||||
self.assertEqual(actual_payment_data, expected_payment_data)
|
||||
|
||||
|
||||
class TestCreateOrderView(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the create_order view of verified course enrollment process.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestCreateOrderView, self).setUp()
|
||||
|
||||
self.user = UserFactory.create(username="rusty", password="test")
|
||||
self.client.login(username="rusty", password="test")
|
||||
self.course_id = 'Robot/999/Test_Course'
|
||||
self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
|
||||
verified_mode = CourseMode(
|
||||
course_id=CourseKey.from_string("Robot/999/Test_Course"),
|
||||
mode_slug="verified",
|
||||
mode_display_name="Verified Certificate",
|
||||
min_price=50
|
||||
)
|
||||
verified_mode.save()
|
||||
course_mode_post_data = {
|
||||
'certificate_mode': 'Select Certificate',
|
||||
'contribution': 50,
|
||||
'contribution-other-amt': '',
|
||||
'explain': ''
|
||||
}
|
||||
self.client.post(
|
||||
reverse("course_modes_choose", kwargs={'course_id': self.course_id}),
|
||||
course_mode_post_data
|
||||
)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
|
||||
def test_invalid_amount(self):
|
||||
response = self._create_order('1.a', self.course_id, expect_status_code=400)
|
||||
self.assertContains(response, 'Selected price is not valid number.', status_code=400)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
|
||||
def test_invalid_mode(self):
|
||||
# Create a course that does not have a verified mode
|
||||
course_id = 'Fake/999/Test_Course'
|
||||
CourseFactory.create(org='Fake', number='999', display_name='Test Course')
|
||||
response = self._create_order('50', course_id, expect_status_code=400)
|
||||
self.assertContains(
|
||||
response,
|
||||
'This course doesn\'t support paid certificates',
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
|
||||
def test_create_order_fail_with_get(self):
|
||||
create_order_post_data = {
|
||||
'contribution': 50,
|
||||
'course_id': self.course_id,
|
||||
}
|
||||
|
||||
# Use the wrong HTTP method
|
||||
response = self.client.get(reverse('verify_student_create_order'), create_order_post_data)
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
|
||||
def test_create_order_success(self):
|
||||
response = self._create_order(50, self.course_id)
|
||||
json_response = json.loads(response.content.decode('utf-8'))
|
||||
self.assertIsNotNone(json_response['payment_form_data'].get('orderNumber')) # TODO not canonical
|
||||
|
||||
# Verify that the order exists and is configured correctly
|
||||
order = Order.objects.get(user=self.user)
|
||||
self.assertEqual(order.status, 'paying')
|
||||
item = CertificateItem.objects.get(order=order)
|
||||
self.assertEqual(item.status, 'paying')
|
||||
self.assertEqual(item.course_id, self.course.id)
|
||||
self.assertEqual(item.mode, 'verified')
|
||||
|
||||
def _create_order(self, contribution, course_id, expect_success=True, expect_status_code=200):
|
||||
"""Create a new order.
|
||||
|
||||
Arguments:
|
||||
contribution (int): The contribution amount.
|
||||
course_id (CourseKey): The course to purchase.
|
||||
|
||||
Keyword Arguments:
|
||||
expect_success (bool): If True, verify that the response was successful.
|
||||
expect_status_code (int): The expected HTTP status code
|
||||
|
||||
Returns:
|
||||
HttpResponse
|
||||
|
||||
"""
|
||||
url = reverse('verify_student_create_order')
|
||||
data = {
|
||||
'contribution': contribution,
|
||||
'course_id': course_id,
|
||||
'processor': '',
|
||||
}
|
||||
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, expect_status_code)
|
||||
|
||||
if expect_status_code == 200:
|
||||
json_response = json.loads(response.content.decode('utf-8'))
|
||||
if expect_success:
|
||||
self.assertEqual(set(json_response.keys()), PAYMENT_DATA_KEYS)
|
||||
else:
|
||||
self.assertFalse(json_response['success'])
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
|
||||
class TestSubmitPhotosForVerification(MockS3BotoMixin, TestVerificationBase):
|
||||
|
||||
@@ -47,8 +47,6 @@ from openedx.core.djangoapps.user_api.accounts import NAME_MIN_LENGTH
|
||||
from openedx.core.djangoapps.user_api.accounts.api import update_account_settings
|
||||
from openedx.core.djangoapps.user_api.errors import AccountValidationError, UserNotFound
|
||||
from openedx.core.lib.log_utils import audit_log
|
||||
from shoppingcart.models import CertificateItem, Order
|
||||
from shoppingcart.processors import get_purchase_endpoint, get_signed_purchase_params
|
||||
from student.models import CourseEnrollment
|
||||
from track import segment
|
||||
from util.db import outer_atomic
|
||||
@@ -757,38 +755,6 @@ def checkout_with_ecommerce_service(user, course_key, course_mode, processor):
|
||||
)
|
||||
|
||||
|
||||
def checkout_with_shoppingcart(request, user, course_key, course_mode, amount):
|
||||
""" Create an order and trigger checkout using shoppingcart."""
|
||||
cart = Order.get_cart_for_user(user)
|
||||
cart.clear()
|
||||
enrollment_mode = course_mode.slug
|
||||
CertificateItem.add_to_order(cart, course_key, amount, enrollment_mode)
|
||||
|
||||
# Change the order's status so that we don't accidentally modify it later.
|
||||
# We need to do this to ensure that the parameters we send to the payment system
|
||||
# match what we store in the database.
|
||||
# (Ordinarily we would do this client-side when the user submits the form, but since
|
||||
# the JavaScript on this page does that immediately, we make the change here instead.
|
||||
# This avoids a second AJAX call and some additional complication of the JavaScript.)
|
||||
# If a user later re-enters the verification / payment flow, she will create a new order.
|
||||
cart.start_purchase()
|
||||
|
||||
callback_url = request.build_absolute_uri(
|
||||
reverse("shoppingcart.views.postpay_callback")
|
||||
)
|
||||
|
||||
payment_data = {
|
||||
'payment_processor_name': settings.CC_PROCESSOR_NAME,
|
||||
'payment_page_url': get_purchase_endpoint(),
|
||||
'payment_form_data': get_signed_purchase_params(
|
||||
cart,
|
||||
callback_url=callback_url,
|
||||
extra_data=[six.text_type(course_key), course_mode.slug]
|
||||
),
|
||||
}
|
||||
return payment_data
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def create_order(request):
|
||||
@@ -835,16 +801,13 @@ def create_order(request):
|
||||
if amount < current_mode.min_price:
|
||||
return HttpResponseBadRequest(_("No selected price or selected price is below minimum."))
|
||||
|
||||
if current_mode.sku:
|
||||
# if request.POST doesn't contain 'processor' then the service's default payment processor will be used.
|
||||
payment_data = checkout_with_ecommerce_service(
|
||||
request.user,
|
||||
course_id,
|
||||
current_mode,
|
||||
request.POST.get('processor')
|
||||
)
|
||||
else:
|
||||
payment_data = checkout_with_shoppingcart(request, request.user, course_id, current_mode, amount)
|
||||
# if request.POST doesn't contain 'processor' then the service's default payment processor will be used.
|
||||
payment_data = checkout_with_ecommerce_service(
|
||||
request.user,
|
||||
course_id,
|
||||
current_mode,
|
||||
request.POST.get('processor')
|
||||
)
|
||||
|
||||
if 'processor' not in request.POST:
|
||||
# (XCOM-214) To be removed after release.
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
## mako
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
${_("Thank you for purchasing enrollments in {course_name}.").format(course_name=course.display_name)}
|
||||
|
||||
${_("An invoice for {currency_symbol}{total_price} is attached. Payment is due upon receipt. You can find information about payment methods on the invoice.").format(currency_symbol=currency_symbol, total_price=sale_price)}
|
||||
|
||||
${_("A .csv file that lists your enrollment codes is attached. You can use the email template below to distribute enrollment codes to your students. Each student must use a separate enrollment code.")}
|
||||
|
||||
## Translators: This is the signature of an email. "\n" is a newline character
|
||||
## and should be placed between the closing word and the signature.
|
||||
${_("Thanks,\nThe {platform_name} Team").format(platform_name=platform_name)}
|
||||
|
||||
|
||||
———————————————————————————————————————————
|
||||
|
||||
|
||||
## Translators: please translate the text inside [[ ]]. This is meant as a template for course teams to use.
|
||||
${_("Dear [[Name]]:")}
|
||||
|
||||
## Translators: please translate the text inside [[ ]]. This is meant as a template for course teams to use.
|
||||
${_("We have provided a course enrollment code for you in {course_name}. To enroll in the course, click the following link:").format(course_name=course.display_name)}
|
||||
|
||||
[[${_("HTML link from the attached CSV file")}]]
|
||||
|
||||
${_("After you enroll, you can see the course on your student dashboard. You can see course materials after the course start date.")}
|
||||
|
||||
## Translators: please translate the text inside [[ ]]. This is meant as a template for course teams to use.
|
||||
## This is the signature of an email. "\n" is a newline character
|
||||
## and should be placed between the closing word and the signature.
|
||||
${_("Sincerely,\n[[Your Signature]]")}
|
||||
@@ -1,43 +0,0 @@
|
||||
## mako
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
${_("INVOICE")}
|
||||
|
||||
———————————————————————————————————————————
|
||||
|
||||
${corp_address}
|
||||
|
||||
${_("Date: {date}").format(date=date)}
|
||||
${_("Invoice No: {invoice_number}").format(invoice_number=invoice.id)}
|
||||
${_("Terms: Due Upon Receipt")}
|
||||
${_("Due Date: {date}").format(date=date)}
|
||||
|
||||
${_("Bill to:")}
|
||||
${invoice.company_name}
|
||||
${invoice.address_line_1}
|
||||
% if invoice.address_line_2:
|
||||
${invoice.address_line_2}
|
||||
% endif
|
||||
% if invoice.address_line_3:
|
||||
${invoice.address_line_3}
|
||||
% endif
|
||||
${invoice.city}, ${invoice.state}, ${invoice.zip}
|
||||
${invoice.country}
|
||||
${_("Customer Reference Number: {reference_number}").format(reference_number=invoice.customer_reference_number if invoice.customer_reference_number else "")}
|
||||
|
||||
${_("Balance Due: {currency_symbol}{sale_price}").format(currency_symbol=currency_symbol, sale_price=sale_price)}
|
||||
|
||||
———————————————————————————————————————————
|
||||
|
||||
${_("Course: {course_name}").format(course_name=course.display_name)}
|
||||
${_("Price: {currency_symbol}{course_price} Quantity: {quantity} Sub-Total: {currency_symbol}{sub_total} Discount: {currency_symbol}{discount}").format(course_price=course_price, quantity=quantity, sub_total=sub_total, discount=discount, currency_symbol=currency_symbol)}
|
||||
${_("Total: {currency_symbol}{sale_price}").format(sale_price=sale_price, currency_symbol=currency_symbol)}
|
||||
|
||||
———————————————————————————————————————————
|
||||
|
||||
${_("Payment Instructions")}
|
||||
|
||||
${payment_instructions}
|
||||
|
||||
${_("If we do not receive payment, the learner enrollments that use these codes will be canceled and learners will not be able to access course materials. All purchases are final. For more information, see the {site_name} cancellation policy.").format(site_name=site_name)}
|
||||
|
||||
${_("For payment questions, contact {contact_email}").format(contact_email=contact_email)}
|
||||
50
lms/urls.py
50
lms/urls.py
@@ -27,9 +27,7 @@ from lms.djangoapps.courseware.views.index import CoursewareIndex
|
||||
from lms.djangoapps.courseware.views.views import CourseTabView, EnrollStaffView, StaticCourseTabView
|
||||
from lms.djangoapps.discussion import views as discussion_views
|
||||
from lms.djangoapps.discussion.notification_prefs import views as notification_prefs_views
|
||||
from lms.djangoapps.instructor.views import coupons as instructor_coupons_views
|
||||
from lms.djangoapps.instructor.views import instructor_dashboard as instructor_dashboard_views
|
||||
from lms.djangoapps.instructor.views import registration_codes as instructor_registration_codes_views
|
||||
from lms.djangoapps.instructor_task import views as instructor_task_views
|
||||
from openedx.core.apidocs import api_info
|
||||
from openedx.core.djangoapps.auth_exchange.views import LoginWithAccessTokenView
|
||||
@@ -229,19 +227,6 @@ if settings.WIKI_ENABLED:
|
||||
include((wiki_url_patterns, 'course_wiki_do_not_reverse'), namespace='course_wiki_do_not_reverse')),
|
||||
]
|
||||
|
||||
COURSE_URLS = [
|
||||
url(
|
||||
r'^look_up_registration_code$',
|
||||
instructor_registration_codes_views.look_up_registration_code,
|
||||
name='look_up_registration_code',
|
||||
),
|
||||
url(
|
||||
r'^registration_code_details$',
|
||||
instructor_registration_codes_views.registration_code_details,
|
||||
name='registration_code_details',
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
# jump_to URLs for direct access to a location in the course
|
||||
url(
|
||||
@@ -523,41 +508,6 @@ urlpatterns += [
|
||||
instructor_dashboard_views.set_course_mode_price,
|
||||
name='set_course_mode_price',
|
||||
),
|
||||
url(
|
||||
r'^courses/{}/remove_coupon$'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
instructor_coupons_views.remove_coupon,
|
||||
name='remove_coupon',
|
||||
),
|
||||
url(
|
||||
r'^courses/{}/add_coupon$'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
instructor_coupons_views.add_coupon,
|
||||
name='add_coupon',
|
||||
),
|
||||
url(
|
||||
r'^courses/{}/update_coupon$'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
instructor_coupons_views.update_coupon,
|
||||
name='update_coupon',
|
||||
),
|
||||
url(
|
||||
r'^courses/{}/get_coupon_info$'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
instructor_coupons_views.get_coupon_info,
|
||||
name='get_coupon_info',
|
||||
),
|
||||
|
||||
url(
|
||||
r'^courses/{}/'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
include(COURSE_URLS)
|
||||
),
|
||||
|
||||
# Discussions Management
|
||||
url(
|
||||
|
||||
Reference in New Issue
Block a user