Merge pull request #23972 from edx/diana/remove-shoppingcart-references

Remove shoppingcart references from verify_student and instructor.
This commit is contained in:
Diana Huang
2020-06-09 13:51:15 -04:00
committed by GitHub
17 changed files with 8 additions and 3994 deletions

View File

@@ -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.

View File

@@ -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>')

View File

@@ -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'])

View File

@@ -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),

View File

@@ -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)

View File

@@ -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'),

View File

@@ -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

View File

@@ -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()

View File

@@ -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]
)

View File

@@ -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.

View File

@@ -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

View File

@@ -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&#39;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&#39;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):

View File

@@ -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):

View File

@@ -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.

View File

@@ -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]]")}

View File

@@ -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)}

View File

@@ -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(