diff --git a/common/djangoapps/student/tests/test_course_listing.py b/common/djangoapps/student/tests/test_course_listing.py index 970a6110d4..13d838715e 100644 --- a/common/djangoapps/student/tests/test_course_listing.py +++ b/common/djangoapps/student/tests/test_course_listing.py @@ -15,6 +15,8 @@ from xmodule.error_module import ErrorDescriptor from django.test.client import Client from student.models import CourseEnrollment from student.views import get_course_enrollment_pairs +import unittest +from django.conf import settings class TestCourseListing(ModuleStoreTestCase): @@ -54,6 +56,7 @@ class TestCourseListing(ModuleStoreTestCase): self.client.logout() super(TestCourseListing, self).tearDown() + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') def test_get_course_list(self): """ Test getting courses diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 8b2639565a..7e899e4ef2 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -32,6 +32,7 @@ from student.tests.factories import UserFactory, CourseModeFactory from certificates.models import CertificateStatuses from certificates.tests.factories import GeneratedCertificateFactory import shoppingcart +from bulk_email.models import Optout log = logging.getLogger(__name__) @@ -265,6 +266,50 @@ class DashboardTest(TestCase): verified_mode.save() self.assertFalse(enrollment.refundable()) + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') + @patch('courseware.views.log.warning') + def test_blocked_course_scenario(self, log_warning): + + self.client.login(username="jack", password="test") + + #create testing invoice 1 + sale_invoice_1 = shoppingcart.models.Invoice.objects.create( + total_amount=1234.32, company_name='Test1', company_contact_name='Testw', + company_contact_email='test1@test.com', customer_reference_number='2Fwe23S', + recipient_name='Testw_1', recipient_email='test2@test.com', internal_reference="A", + course_id=self.course.id, is_valid=False + ) + course_reg_code = shoppingcart.models.CourseRegistrationCode(code="abcde", course_id=self.course.id, + created_by=self.user, invoice=sale_invoice_1) + course_reg_code.save() + + cart = shoppingcart.models.Order.get_cart_for_user(self.user) + shoppingcart.models.PaidCourseRegistration.add_to_order(cart, self.course.id) + resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': course_reg_code.code}) + self.assertEqual(resp.status_code, 200) + + # freely enroll the user into course + resp = self.client.get(reverse('shoppingcart.views.register_courses')) + self.assertIn('success', resp.content) + + response = self.client.get(reverse('dashboard')) + self.assertIn('You can no longer access this course because payment has not yet been received', response.content) + optout_object = Optout.objects.filter(user=self.user, course_id=self.course.id) + self.assertEqual(len(optout_object), 1) + + # Direct link to course redirect to user dashboard + self.client.get(reverse('courseware', kwargs={"course_id": self.course.id.to_deprecated_string()})) + log_warning.assert_called_with( + u'User %s cannot access the course %s because payment has not yet been received', self.user, self.course.id.to_deprecated_string()) + + # Now re-validating the invoice + invoice = shoppingcart.models.Invoice.objects.get(id=sale_invoice_1.id) + invoice.is_valid = True + invoice.save() + + response = self.client.get(reverse('dashboard')) + self.assertNotIn('You can no longer access this course because payment has not yet been received', response.content) + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') def test_refundable_of_purchased_course(self): @@ -315,6 +360,7 @@ class EnrollInCourseTest(TestCase): self.mock_tracker = patcher.start() self.addCleanup(patcher.stop) + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') def test_enrollment(self): user = User.objects.create_user("joe", "joe@joe.com", "password") course_id = SlashSeparatedCourseKey("edX", "Test101", "2013") @@ -419,6 +465,7 @@ class EnrollInCourseTest(TestCase): self.assertTrue(CourseEnrollment.is_enrolled(user, course_id)) self.assert_enrollment_event_was_emitted(user, course_id) + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') def test_enrollment_by_email(self): user = User.objects.create(username="jack", email="jack@fake.edx.org") course_id = SlashSeparatedCourseKey("edX", "Test101", "2013") @@ -456,6 +503,7 @@ class EnrollInCourseTest(TestCase): CourseEnrollment.unenroll_by_email("not_jack@fake.edx.org", course_id) self.assert_no_events_were_emitted() + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') def test_enrollment_multiple_classes(self): user = User(username="rusty", email="rusty@fake.edx.org") course_id1 = SlashSeparatedCourseKey("edX", "Test101", "2013") @@ -478,6 +526,7 @@ class EnrollInCourseTest(TestCase): self.assertFalse(CourseEnrollment.is_enrolled(user, course_id1)) self.assertFalse(CourseEnrollment.is_enrolled(user, course_id2)) + @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') def test_activation(self): user = User.objects.create(username="jack", email="jack@fake.edx.org") course_id = SlashSeparatedCourseKey("edX", "Test101", "2013") diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 2da6d459cb..9b0c880e3e 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -92,6 +92,7 @@ from util.password_policy_validators import ( from third_party_auth import pipeline, provider from xmodule.error_module import ErrorDescriptor +from shoppingcart.models import CourseRegistrationCode import analytics from eventtracking import tracker @@ -431,6 +432,20 @@ def complete_course_mode_info(course_id, enrollment): return mode_info +def is_course_blocked(request, redeemed_registration_codes, course_key): + """Checking either registration is blocked or not .""" + blocked = False + for redeemed_registration in redeemed_registration_codes: + if not getattr(redeemed_registration.invoice, 'is_valid'): + blocked = True + # disabling email notifications for unpaid registration courses + Optout.objects.get_or_create(user=request.user, course_id=course_key) + log.info(u"User {0} ({1}) opted out of receiving emails from course {2}".format(request.user.username, request.user.email, course_key)) + track.views.server_track(request, "change-email1-settings", {"receive_emails": "no", "course": course_key.to_deprecated_string()}, page='dashboard') + break + + return blocked + @login_required @ensure_csrf_cookie def dashboard(request): @@ -493,6 +508,10 @@ def dashboard(request): show_refund_option_for = frozenset(course.id for course, _enrollment in course_enrollment_pairs if _enrollment.refundable()) + block_courses = frozenset(course.id for course, enrollment in course_enrollment_pairs + if is_course_blocked(request, CourseRegistrationCode.objects.filter(course_id=course.id, registrationcoderedemption__redeemed_by=request.user), course.id)) + + enrolled_courses_either_paid = frozenset(course.id for course, _enrollment in course_enrollment_pairs if _enrollment.is_paid_course()) # get info w.r.t ExternalAuthMap @@ -544,6 +563,7 @@ def dashboard(request): 'verification_status': verification_status, 'verification_msg': verification_msg, 'show_refund_option_for': show_refund_option_for, + 'block_courses': block_courses, 'denied_banner': denied_banner, 'billing_email': settings.PAYMENT_SUPPORT_EMAIL, 'language_options': language_options, diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index b8d54c0503..1117e92f0f 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -46,6 +46,7 @@ from xmodule.modulestore.search import path_to_location from xmodule.tabs import CourseTabList, StaffGradingTab, PeerGradingTab, OpenEndedGradingTab from xmodule.x_module import STUDENT_VIEW import shoppingcart +from shoppingcart.models import CourseRegistrationCode from opaque_keys import InvalidKeyError from microsite_configuration import microsite @@ -291,6 +292,14 @@ def index(request, course_id, chapter=None, section=None, course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) user = User.objects.prefetch_related("groups").get(id=request.user.id) + + # Redirecting to dashboard if the course is blocked due to un payment. + redeemed_registration_codes = CourseRegistrationCode.objects.filter(course_id=course_key, registrationcoderedemption__redeemed_by=request.user) + for redeemed_registration in redeemed_registration_codes: + if not getattr(redeemed_registration.invoice, 'is_valid'): + log.warning(u'User %s cannot access the course %s because payment has not yet been received', user, course_key.to_deprecated_string()) + return redirect(reverse('dashboard')) + request.user = user # keep just one instance of User course = get_course_with_access(user, 'load', course_key, depth=2) staff_access = has_access(user, 'staff', course) diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index 3f9a011f34..7ecf8378fd 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -44,12 +44,17 @@ import instructor.views.api from instructor.views.api import _split_input_list, common_exceptions_400 from instructor_task.api_helper import AlreadyRunningError from opaque_keys.edx.locations import SlashSeparatedCourseKey -from shoppingcart.models import CourseRegistrationCode, RegistrationCodeRedemption, Order, PaidCourseRegistration, Coupon +from shoppingcart.models import ( + RegistrationCodeRedemption, Order, + PaidCourseRegistration, Coupon, Invoice, CourseRegistrationCode +) from course_modes.models import CourseMode from .test_tools import msk_from_problem_urlname from ..views.tools import get_extended_due +EXPECTED_CSV_HEADER = '"code","course_id","company_name","created_by","redeemed_by","invoice_id","purchaser","customer_reference_number","internal_reference"' +EXPECTED_COUPON_CSV_HEADER = '"course_id","percentage_discount","code_redeemed_count","description"' @common_exceptions_400 def view_success(request): # pylint: disable=W0613 @@ -73,6 +78,7 @@ class TestCommonExceptions400(unittest.TestCase): """ Testing the common_exceptions_400 decorator. """ + def setUp(self): self.request = Mock(spec=HttpRequest) self.request.META = {} @@ -114,6 +120,7 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase): """ Ensure that users cannot access endpoints they shouldn't be able to. """ + def setUp(self): self.course = CourseFactory.create() self.user = UserFactory.create() @@ -138,8 +145,10 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase): ('get_students_features', {}), ('get_distribution', {}), ('get_student_progress_url', {'unique_student_identifier': self.user.username}), - ('reset_student_attempts', {'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.user.email}), - ('update_forum_role_membership', {'unique_student_identifier': self.user.email, 'rolename': 'Moderator', 'action': 'allow'}), + ('reset_student_attempts', + {'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.user.email}), + ('update_forum_role_membership', + {'unique_student_identifier': self.user.email, 'rolename': 'Moderator', 'action': 'allow'}), ('list_forum_members', {'rolename': FORUM_ROLE_COMMUNITY_TA}), ('proxy_legacy_analytics', {'aname': 'ProblemGradeDistribution'}), ('send_email', {'send_to': 'staff', 'subject': 'test', 'message': 'asdf'}), @@ -153,7 +162,8 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase): ('bulk_beta_modify_access', {'identifiers': 'foo@example.org', 'action': 'add'}), ('modify_access', {'unique_student_identifier': self.user.email, 'rolename': 'beta', 'action': 'allow'}), ('list_course_role_members', {'rolename': 'beta'}), - ('rescore_problem', {'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.user.email}), + ('rescore_problem', + {'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.user.email}), ] def _access_endpoint(self, endpoint, args, status_code, msg): @@ -267,6 +277,7 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase): This test does NOT exhaustively test state changes, that is the job of test_enrollment. This tests the response and action switch. """ + def setUp(self): self.request = RequestFactory().request() self.course = CourseFactory.create() @@ -278,7 +289,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase): self.enrolled_student, self.course.id ) - self.notenrolled_student = UserFactory(username='NotEnrolledStudent', first_name='NotEnrolled', last_name='Student') + self.notenrolled_student = UserFactory(username='NotEnrolledStudent', first_name='NotEnrolled', + last_name='Student') # Create invited, but not registered, user cea = CourseEnrollmentAllowed(email='robot-allowed@robot.org', course_id=self.course.id) @@ -537,7 +549,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase): @ddt.data('http', 'https') def test_enroll_with_email_not_registered_autoenroll(self, protocol): url = reverse('students_update_enrollment', kwargs={'course_id': self.course.id.to_deprecated_string()}) - params = {'identifiers': self.notregistered_email, 'action': 'enroll', 'email_students': True, 'auto_enroll': True} + params = {'identifiers': self.notregistered_email, 'action': 'enroll', 'email_students': True, + 'auto_enroll': True} environ = {'wsgi.url_scheme': protocol} response = self.client.post(url, params, **environ) print "type(self.notregistered_email): {}".format(type(self.notregistered_email)) @@ -746,7 +759,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase): mock_uses_shib.return_value = True url = reverse('students_update_enrollment', kwargs={'course_id': self.course.id.to_deprecated_string()}) - params = {'identifiers': self.notregistered_email, 'action': 'enroll', 'email_students': True, 'auto_enroll': True} + params = {'identifiers': self.notregistered_email, 'action': 'enroll', 'email_students': True, + 'auto_enroll': True} environ = {'wsgi.url_scheme': protocol} response = self.client.post(url, params, **environ) print "type(self.notregistered_email): {}".format(type(self.notregistered_email)) @@ -775,6 +789,7 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe """ Test bulk beta modify access endpoint. """ + def setUp(self): self.course = CourseFactory.create() self.instructor = InstructorFactory(course_key=self.course.id) @@ -923,7 +938,8 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe @ddt.data('http', 'https') def test_add_notenrolled_with_email_autoenroll(self, protocol): url = reverse('bulk_beta_modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - params = {'identifiers': self.notenrolled_student.email, 'action': 'add', 'email_students': True, 'auto_enroll': True} + params = {'identifiers': self.notenrolled_student.email, 'action': 'add', 'email_students': True, + 'auto_enroll': True} environ = {'wsgi.url_scheme': protocol} response = self.client.post(url, params, **environ) self.assertEqual(response.status_code, 200) @@ -1089,6 +1105,7 @@ class TestInstructorAPILevelsAccess(ModuleStoreTestCase, LoginEnrollmentTestCase Actually, modify_access does not have a very meaningful response yet, so only the status code is tested. """ + def setUp(self): self.course = CourseFactory.create() self.instructor = InstructorFactory(course_key=self.course.id) @@ -1331,6 +1348,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa """ Test endpoints that show data without side effects. """ + def setUp(self): self.course = CourseFactory.create() self.course_mode = CourseMode(course_id=self.course.id, @@ -1346,10 +1364,61 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa 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.students = [UserFactory() for _ in xrange(6)] for student in self.students: CourseEnrollment.enroll(student, self.course.id) + 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=self.course.id.to_deprecated_string(), + created_by=self.instructor, invoice=self.sale_invoice_1 + ) + course_registration_code.save() + + data = {'invoice_number': self.sale_invoice_1.id, 'event_type': "invalidate"} + url = reverse('sale_validation', kwargs={'course_id': self.course.id.to_deprecated_string()}) + 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.assertIn("The sale associated with this invoice has already been invalidated.", response.content) + + # 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 actove invoice number and expect an Bad request + response = self.assert_request_status_code(400, url, method="POST", data=data) + self.assertIn("This invoice is already active.", response.content) + + 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.assertIn("Missing required event_type parameter", response.content) + + test_data_3 = {'event_type': "re_validate"} + response = self.assert_request_status_code(400, url, method="POST", data=test_data_3) + self.assertIn("Missing required invoice_number parameter", response.content) + + # submitting invalid invoice number + data['invoice_number'] = 'testing' + response = self.assert_request_status_code(400, url, method="POST", data=data) + self.assertIn("invoice_number must be an integer, {value} provided".format(value=data['invoice_number']), response.content) + def test_get_ecommerce_purchase_features_csv(self): """ Test that the response from get_purchase_transaction is in csv format. @@ -1360,6 +1429,115 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa response = self.client.get(url + '/csv', {}) self.assertEqual(response['Content-Type'], 'text/csv') + 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=self.course.id.to_deprecated_string(), + created_by=self.instructor, invoice=self.sale_invoice_1 + ) + course_registration_code.save() + + url = reverse('get_sale_records', kwargs={'course_id': self.course.id.to_deprecated_string()}) + response = self.client.get(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=self.course.id.to_deprecated_string(), + created_by=self.instructor, invoice=self.sale_invoice_1 + ) + course_registration_code.save() + + url = reverse('get_sale_records', kwargs={'course_id': self.course.id.to_deprecated_string()}) + response = self.client.get(url, {}) + res_json = json.loads(response.content) + 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) + + def test_get_sale_records_features_with_used_code(self): + """ + Test that the response from get_sale_records is in json format and using one of the registration codes. + """ + for i in range(5): + course_registration_code = CourseRegistrationCode( + code='qwerty{}'.format(i), course_id=self.course.id.to_deprecated_string(), + created_by=self.instructor, invoice=self.sale_invoice_1 + ) + course_registration_code.save() + + PaidCourseRegistration.add_to_order(self.cart, self.course.id) + + # now using registration code + self.client.post(reverse('shoppingcart.views.use_code'), {'code': 'qwerty0'}) + + url = reverse('get_sale_records', kwargs={'course_id': self.course.id.to_deprecated_string()}) + response = self.client.get(url, {}) + res_json = json.loads(response.content) + self.assertIn('sale', res_json) + + for res in res_json['sale']: + self.validate_sale_records_response(res, course_registration_code, self.sale_invoice_1, 1) + + 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=self.course.id.to_deprecated_string(), + created_by=self.instructor, invoice=self.sale_invoice_1 + ) + 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 + ) + + for i in range(5): + course_registration_code = CourseRegistrationCode( + code='xyzmn{}'.format(i), course_id=self.course.id.to_deprecated_string(), + created_by=self.instructor, invoice=sale_invoice_2 + ) + course_registration_code.save() + + url = reverse('get_sale_records', kwargs={'course_id': self.course.id.to_deprecated_string()}) + response = self.client.get(url, {}) + res_json = json.loads(response.content) + self.assertIn('sale', res_json) + + self.validate_sale_records_response(res_json['sale'][0], course_registration_code, self.sale_invoice_1, 0) + self.validate_sale_records_response(res_json['sale'][1], course_registration_code, sale_invoice_2, 0) + + def validate_sale_records_response(self, res, course_registration_code, invoice, used_codes): + """ + 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'], invoice.course_id.to_deprecated_string()) + self.assertEqual(res['total_used_codes'], used_codes) + self.assertEqual(res['total_codes'], 5) + def test_get_ecommerce_purchase_features_with_coupon_info(self): """ Test that some minimum of information is formatted @@ -1588,6 +1766,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase) This test does NOT test whether the actions had an effect on the database, that is the job of task tests and test_enrollment. """ + def setUp(self): self.course = CourseFactory.create() self.instructor = InstructorFactory(course_key=self.course.id) @@ -1726,6 +1905,7 @@ class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase): these endpoints are only accessible with courses that actually exist, only with valid email messages. """ + def setUp(self): self.course = CourseFactory.create() self.instructor = InstructorFactory(course_key=self.course.id) @@ -1967,6 +2147,7 @@ class TestInstructorEmailContentList(ModuleStoreTestCase, LoginEnrollmentTestCas """ Test the instructor email content history endpoint. """ + def setUp(self): self.course = CourseFactory.create() self.instructor = InstructorFactory(course_key=self.course.id) @@ -2105,12 +2286,14 @@ class TestInstructorAPIAnalyticsProxy(ModuleStoreTestCase, LoginEnrollmentTestCa class FakeProxyResponse(object): """ Fake successful requests response object. """ + def __init__(self): self.status_code = requests.status_codes.codes.OK self.content = '{"test_content": "robot test content"}' class FakeBadProxyResponse(object): """ Fake strange-failed requests response object. """ + def __init__(self): self.status_code = 'notok.' self.content = '{"test_content": "robot test content"}' @@ -2182,19 +2365,25 @@ class TestInstructorAPIAnalyticsProxy(ModuleStoreTestCase, LoginEnrollmentTestCa class TestInstructorAPIHelpers(TestCase): """ Test helpers for instructor.api """ + def test_split_input_list(self): strings = [] lists = [] - strings.append("Lorem@ipsum.dolor, sit@amet.consectetur\nadipiscing@elit.Aenean\r convallis@at.lacus\r, ut@lacinia.Sed") - lists.append(['Lorem@ipsum.dolor', 'sit@amet.consectetur', 'adipiscing@elit.Aenean', 'convallis@at.lacus', 'ut@lacinia.Sed']) + strings.append( + "Lorem@ipsum.dolor, sit@amet.consectetur\nadipiscing@elit.Aenean\r convallis@at.lacus\r, ut@lacinia.Sed") + lists.append(['Lorem@ipsum.dolor', 'sit@amet.consectetur', 'adipiscing@elit.Aenean', 'convallis@at.lacus', + 'ut@lacinia.Sed']) for (stng, lst) in zip(strings, lists): self.assertEqual(_split_input_list(stng), lst) def test_split_input_list_unicode(self): - self.assertEqual(_split_input_list('robot@robot.edu, robot2@robot.edu'), ['robot@robot.edu', 'robot2@robot.edu']) - self.assertEqual(_split_input_list(u'robot@robot.edu, robot2@robot.edu'), ['robot@robot.edu', 'robot2@robot.edu']) - self.assertEqual(_split_input_list(u'robot@robot.edu, robot2@robot.edu'), [u'robot@robot.edu', 'robot2@robot.edu']) + self.assertEqual(_split_input_list('robot@robot.edu, robot2@robot.edu'), + ['robot@robot.edu', 'robot2@robot.edu']) + self.assertEqual(_split_input_list(u'robot@robot.edu, robot2@robot.edu'), + ['robot@robot.edu', 'robot2@robot.edu']) + self.assertEqual(_split_input_list(u'robot@robot.edu, robot2@robot.edu'), + [u'robot@robot.edu', 'robot2@robot.edu']) scary_unistuff = unichr(40960) + u'abcd' + unichr(1972) self.assertEqual(_split_input_list(scary_unistuff), [scary_unistuff]) @@ -2383,7 +2572,7 @@ class TestDueDateExtensions(ModuleStoreTestCase, LoginEnrollmentTestCase): u'Username': self.user1.username}], u'header': [u'Username', u'Full Name', u'Extended Due Date'], u'title': u'Users with due date extensions for %s' % - self.week1.display_name}) + self.week1.display_name}) def test_show_student_extensions(self): self.test_change_due_date() @@ -2396,7 +2585,7 @@ class TestDueDateExtensions(ModuleStoreTestCase, LoginEnrollmentTestCase): u'Unit': self.week1.display_name}], u'header': [u'Unit', u'Extended Due Date'], u'title': u'Due date extensions for %s (%s)' % ( - self.user1.profile.name, self.user1.username)}) + self.user1.profile.name, self.user1.username)}) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @@ -2405,6 +2594,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): """ Test data dumps for E-commerce Course Registration Codes. """ + def setUp(self): """ Fixtures. @@ -2413,14 +2603,19 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): self.instructor = InstructorFactory(course_key=self.course.id) self.client.login(username=self.instructor.username, password='test') - # Active Registration Codes - for i in range(12): - course_registration_code = CourseRegistrationCode( - code='MyCode0{}'.format(i), course_id=self.course.id.to_deprecated_string(), - transaction_group_name='Test Group', created_by=self.instructor - ) - course_registration_code.save() + url = reverse('generate_registration_codes', + kwargs={'course_id': self.course.id.to_deprecated_string()}) + data = { + 'total_registration_codes': 12, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com', + 'company_contact_email': 'Test@company.com', 'sale_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) for i in range(5): order = Order(user=self.instructor, status='purchased') order.save() @@ -2433,6 +2628,48 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ) registration_code_redemption.save() + def test_user_invoice_copy_preference(self): + """ + Test to remember user invoice copy preference + """ + url_reg_code = reverse('generate_registration_codes', + kwargs={'course_id': self.course.id.to_deprecated_string()}) + + data = { + 'total_registration_codes': 5, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', + 'company_contact_email': 'Test@company.com', 'sale_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': self.course.id.to_deprecated_string()}) + + response = self.client.post(url_user_invoice_preference, data) + result = json.loads(response.content) + 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': self.course.id.to_deprecated_string()}) + + response = self.client.post(url_user_invoice_preference, data) + result = json.loads(response.content) + 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 @@ -2440,16 +2677,23 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): url = reverse('generate_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()}) - data = {'course_registration_code_number': 15.0, 'transaction_group_name': 'Test Group'} + data = { + 'total_registration_codes': 15, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', + 'company_contact_email': 'Test@company.com', 'sale_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) + 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.replace('\r', '') - self.assertTrue(body.startswith('"code","course_id","transaction_group_name","created_by","redeemed_by"')) + self.assertTrue(body.startswith(EXPECTED_CSV_HEADER)) self.assertEqual(len(body.split('\n')), 17) - @patch.object(instructor.views.api, 'random_code_generator', Mock(side_effect=['first', 'second', 'third', 'fourth'])) + @patch.object(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 @@ -2459,16 +2703,23 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): coupon = Coupon(code='first', course_id=self.course.id.to_deprecated_string(), created_by=self.instructor) coupon.save() - data = {'course_registration_code_number': 3, 'transaction_group_name': 'Test Group'} + data = { + 'total_registration_codes': 3, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', + 'company_contact_email': 'Test@company.com', 'sale_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) + 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.replace('\r', '') - self.assertTrue(body.startswith('"code","course_id","transaction_group_name","created_by","redeemed_by"')) + 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.object(instructor.views.api, 'random_code_generator', Mock(side_effect=['first', 'first', 'second', 'third'])) + @patch.object(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 @@ -2476,13 +2727,19 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): url = reverse('generate_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()}) - data = {'course_registration_code_number': 2, 'transaction_group_name': 'Test Group'} + data = { + 'total_registration_codes': 2, 'company_name': 'Test Group', 'company_contact_name': 'Test@company.com', + 'company_contact_email': 'Test@company.com', 'sale_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) + 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.replace('\r', '') - self.assertTrue(body.startswith('"code","course_id","transaction_group_name","created_by","redeemed_by"')) + self.assertTrue(body.startswith(EXPECTED_CSV_HEADER)) self.assertEqual(len(body.split('\n')), 4) def test_spent_course_registration_codes_csv(self): @@ -2492,20 +2749,30 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): url = reverse('spent_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()}) - data = {'spent_transaction_group_name': ''} + 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.replace('\r', '') - self.assertTrue(body.startswith('"code","course_id","transaction_group_name","created_by","redeemed_by"')) + + self.assertTrue(body.startswith(EXPECTED_CSV_HEADER)) + self.assertEqual(len(body.split('\n')), 7) - for i in range(9): - course_registration_code = CourseRegistrationCode( - code='TestCode{}'.format(i), course_id=self.course.id.to_deprecated_string(), - transaction_group_name='Group Alpha', created_by=self.instructor - ) - course_registration_code.save() + generate_code_url = reverse( + 'generate_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()} + ) + + data = { + 'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', + 'sale_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') @@ -2519,12 +2786,12 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ) registration_code_redemption.save() - data = {'spent_transaction_group_name': 'Group Alpha'} + 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.replace('\r', '') - self.assertTrue(body.startswith('"code","course_id","transaction_group_name","created_by","redeemed_by"')) + self.assertTrue(body.startswith(EXPECTED_CSV_HEADER)) self.assertEqual(len(body.split('\n')), 11) def test_active_course_registration_codes_csv(self): @@ -2534,55 +2801,120 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): url = reverse('active_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()}) - data = {'active_transaction_group_name': ''} + 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.replace('\r', '') - self.assertTrue(body.startswith('"code","course_id","transaction_group_name","created_by","redeemed_by"')) + self.assertTrue(body.startswith(EXPECTED_CSV_HEADER)) self.assertEqual(len(body.split('\n')), 9) - for i in range(9): - course_registration_code = CourseRegistrationCode( - code='TestCode{}'.format(i), course_id=self.course.id.to_deprecated_string(), - transaction_group_name='Group Alpha', created_by=self.instructor - ) - course_registration_code.save() + generate_code_url = reverse( + 'generate_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()} + ) - data = {'active_transaction_group_name': 'Group Alpha'} + data = { + 'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', + 'company_contact_email': 'Test@company.com', 'sale_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.replace('\r', '') - self.assertTrue(body.startswith('"code","course_id","transaction_group_name","created_by","redeemed_by"')) + 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': self.course.id.to_deprecated_string()}) + url = reverse( + 'get_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()} + ) - data = {'download_transaction_group_name': ''} + 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.replace('\r', '') - self.assertTrue(body.startswith('"code","course_id","transaction_group_name","created_by","redeemed_by"')) + self.assertTrue(body.startswith(EXPECTED_CSV_HEADER)) self.assertEqual(len(body.split('\n')), 14) - for i in range(9): - course_registration_code = CourseRegistrationCode( - code='TestCode{}'.format(i), course_id=self.course.id.to_deprecated_string(), - transaction_group_name='Group Alpha', created_by=self.instructor - ) - course_registration_code.save() + generate_code_url = reverse( + 'generate_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()} + ) - data = {'download_transaction_group_name': 'Group Alpha'} + data = { + 'total_registration_codes': 9, 'company_name': 'Group Alpha', 'company_contact_name': 'Test@company.com', + 'company_contact_email': 'Test@company.com', 'sale_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.replace('\r', '') - self.assertTrue(body.startswith('"code","course_id","transaction_group_name","created_by","redeemed_by"')) + 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': self.course.id.to_deprecated_string()} + ) + + data = { + 'total_registration_codes': 5.5, 'company_name': 'Group Invoice', 'company_contact_name': 'Test@company.com', + 'company_contact_email': 'Test@company.com', 'sale_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': self.course.id.to_deprecated_string()}) + 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.replace('\r', '') + self.assertTrue(body.startswith(EXPECTED_CSV_HEADER)) + + 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': self.course.id.to_deprecated_string()} + ) + 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() + + response = self.client.get(get_coupon_code_url) + self.assertEqual(response.status_code, 200, response.content) + self.assertEqual(response['Content-Type'], 'text/csv') + body = response.content.replace('\r', '') + self.assertTrue(body.startswith(EXPECTED_COUPON_CSV_HEADER)) diff --git a/lms/djangoapps/instructor/tests/test_ecommerce.py b/lms/djangoapps/instructor/tests/test_ecommerce.py index 52602dc795..d51e62e926 100644 --- a/lms/djangoapps/instructor/tests/test_ecommerce.py +++ b/lms/djangoapps/instructor/tests/test_ecommerce.py @@ -175,8 +175,7 @@ class TestECommerceDashboardViews(ModuleStoreTestCase): self.assertTrue('Please Enter the Integer Value for Coupon Discount' in response.content) course_registration = CourseRegistrationCode( - code='Vs23Ws4j', course_id=self.course.id.to_deprecated_string(), - transaction_group_name='Test Group', created_by=self.instructor + code='Vs23Ws4j', course_id=self.course.id.to_deprecated_string(), created_by=self.instructor ) course_registration.save() @@ -254,7 +253,7 @@ class TestECommerceDashboardViews(ModuleStoreTestCase): response = self.client.post(self.url) self.assertTrue('AS452' in response.content) data = { - 'coupon_id': coupon.id, 'code': 'update_code', 'discount': '12', + 'coupon_id': coupon.id, 'code': 'AS452', 'discount': '10', 'description': 'updated_description', # pylint: disable=E1101 'course_id': coupon.course_id.to_deprecated_string() } # URL for update_coupon @@ -263,43 +262,12 @@ class TestECommerceDashboardViews(ModuleStoreTestCase): self.assertTrue('coupon with the coupon id ({coupon_id}) updated Successfully'.format(coupon_id=coupon.id)in response.content) response = self.client.post(self.url) - self.assertTrue('update_code' in response.content) - self.assertTrue('12' in response.content) + self.assertTrue('updated_description' in response.content) data['coupon_id'] = 1000 # Coupon Not Exist with this ID response = self.client.post(update_coupon_url, data=data) self.assertTrue('coupon with the coupon id ({coupon_id}) DoesNotExist'.format(coupon_id=1000) in response.content) - data['coupon_id'] = coupon.id - data['discount'] = 123 - response = self.client.post(update_coupon_url, data=data) - self.assertTrue('Please Enter the Coupon Discount Value Less than or Equal to 100' in response.content) - - data['discount'] = '25%' - response = self.client.post(update_coupon_url, data=data) - self.assertTrue('Please Enter the Integer Value for Coupon Discount' in response.content) - data['coupon_id'] = '' # Coupon id is not provided response = self.client.post(update_coupon_url, data=data) self.assertTrue('coupon id not found' in response.content) - - coupon1 = Coupon( - code='11111', description='coupon', course_id=self.course.id.to_deprecated_string(), - percentage_discount=20, created_by=self.instructor - ) - coupon1.save() - data = {'coupon_id': coupon.id, 'code': '11111', 'discount': '12'} # pylint: disable=E1101 - response = self.client.post(update_coupon_url, data=data) - self.assertTrue('coupon with the coupon id ({coupon_id}) already exist'.format(coupon_id=coupon.id) in response.content) # pylint: disable=E1101 - - course_registration = CourseRegistrationCode( - code='Vs23Ws4j', course_id=self.course.id.to_deprecated_string(), - transaction_group_name='Test Group', created_by=self.instructor - ) - course_registration.save() - - data = {'coupon_id': coupon.id, 'code': 'Vs23Ws4j', # pylint: disable=E1101 - 'discount': '6', 'course_id': coupon.course_id.to_deprecated_string()} # pylint: disable=E1101 - response = self.client.post(update_coupon_url, data=data) - self.assertTrue("The code ({code}) that you have tried to define is already in use as a registration code". - format(code=data['code']) in response.content) diff --git a/lms/djangoapps/instructor/views/__init__.py b/lms/djangoapps/instructor/views/__init__.py index e69de29bb2..2b14fd387e 100644 --- a/lms/djangoapps/instructor/views/__init__.py +++ b/lms/djangoapps/instructor/views/__init__.py @@ -0,0 +1,4 @@ +""" +This is the UserPreference key for the user's recipient invoice copy +""" +INVOICE_KEY = 'pref-invoice-copy' diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 57ca7763b6..f39de72ca2 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -5,27 +5,30 @@ JSON views which the instructor dashboard requests. Many of these GETs may become PUTs in the future. """ -from django.views.decorators.http import require_POST - +import StringIO import json import logging import re import requests from django.conf import settings from django_future.csrf import ensure_csrf_cookie +from django.views.decorators.http import require_POST from django.views.decorators.cache import cache_control from django.core.exceptions import ValidationError +from django.core.mail.message import EmailMessage from django.db import IntegrityError from django.core.urlresolvers import reverse from django.core.validators import validate_email from django.utils.translation import ugettext as _ -from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden +from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound from django.utils.html import strip_tags import string # pylint: disable=W0402 import random from util.json_request import JsonResponse from instructor.views.instructor_task_helpers import extract_email_features, extract_task_features +from microsite_configuration import microsite + from courseware.access import has_access from courseware.courses import get_course_with_access, get_course_by_id from django.contrib.auth.models import User @@ -36,9 +39,9 @@ from django_comment_common.models import ( FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, ) -from edxmako.shortcuts import render_to_response +from edxmako.shortcuts import render_to_response, render_to_string from courseware.models import StudentModule -from shoppingcart.models import Coupon, CourseRegistrationCode, RegistrationCodeRedemption +from shoppingcart.models import Coupon, CourseRegistrationCode, RegistrationCodeRedemption, Invoice, CourseMode from student.models import CourseEnrollment, unique_id_for_user, anonymous_id_for_user import instructor_task.api from instructor_task.api_helper import AlreadyRunningError @@ -56,6 +59,8 @@ import instructor_analytics.basic import instructor_analytics.distributions import instructor_analytics.csvs import csv +from user_api.models import UserPreference +from instructor.views import INVOICE_KEY from submissions import api as sub_api # installed from the edx-submissions repository @@ -552,6 +557,97 @@ def get_grading_config(request, course_id): return JsonResponse(response_payload) +@ensure_csrf_cookie +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +@require_level('staff') +def get_sale_records(request, course_id, csv=False): # pylint: disable=W0613, W0621 + """ + return the summary of all sales records for a particular course + """ + course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) + query_features = [ + 'company_name', 'company_contact_name', 'company_contact_email', 'total_codes', 'total_used_codes', + 'total_amount', 'created_at', 'customer_reference_number', 'recipient_name', 'recipient_email', 'created_by', + 'internal_reference', 'invoice_number', 'codes', 'course_id' + ] + + sale_data = instructor_analytics.basic.sale_record_features(course_id, query_features) + + if not csv: + for item in sale_data: + item['created_by'] = item['created_by'].username + + response_payload = { + 'course_id': course_id.to_deprecated_string(), + 'sale': sale_data, + 'queried_features': query_features + } + return JsonResponse(response_payload) + else: + header, datarows = instructor_analytics.csvs.format_dictlist(sale_data, query_features) + return instructor_analytics.csvs.create_csv_response("e-commerce_sale_records.csv", header, datarows) + + +@require_level('staff') +@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( + "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 = SlashSeparatedCourseKey.from_deprecated_string(course_id) + try: + obj_invoice = Invoice.objects.select_related('is_valid').get(id=invoice_number, course_id=course_id) + except Invoice.DoesNotExist: + return HttpResponseNotFound(_("Invoice number '{0}' does not exist.".format(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 = _('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 = _('The registration codes for invoice {0} have been re-activated.').format(obj_invoice.id) + return JsonResponse({'message': message}) + + @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -635,7 +731,24 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=W06 return instructor_analytics.csvs.create_csv_response("enrolled_profiles.csv", header, datarows) -def save_registration_codes(request, course_id, generated_codes_list, group_name): +@ensure_csrf_cookie +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +@require_level('staff') +def get_coupon_codes(request, course_id): # pylint: disable=W0613 + """ + Respond with csv which contains a summary of all Active Coupons. + """ + course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) + active_coupons = Coupon.objects.filter(course_id=course_id, is_active=True) + query_features = [ + 'course_id', 'percentage_discount', 'code_redeemed_count', 'description' + ] + coupons_list = instructor_analytics.basic.coupon_codes_features(query_features, active_coupons) + header, data_rows = instructor_analytics.csvs.format_dictlist(coupons_list, query_features) + return instructor_analytics.csvs.create_csv_response('Coupons.csv', header, data_rows) + + +def save_registration_codes(request, course_id, generated_codes_list, invoice): """ recursive function that generate a new code every time and saves in the Course Registration Table if validation check passes @@ -645,17 +758,16 @@ def save_registration_codes(request, course_id, generated_codes_list, group_name # 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_codes(request, course_id, generated_codes_list, group_name) + return save_registration_codes(request, course_id, generated_codes_list, invoice) course_registration = CourseRegistrationCode( - code=code, course_id=course_id.to_deprecated_string(), - transaction_group_name=group_name, created_by=request.user + code=code, course_id=course_id.to_deprecated_string(), created_by=request.user, invoice=invoice ) try: course_registration.save() generated_codes_list.append(course_registration) except IntegrityError: - return save_registration_codes(request, course_id, generated_codes_list, group_name) + return save_registration_codes(request, course_id, generated_codes_list, invoice) def registration_codes_csv(file_name, codes_list, csv_type=None): @@ -667,7 +779,10 @@ def registration_codes_csv(file_name, codes_list, csv_type=None): :param csv_type: """ # csv headers - query_features = ['code', 'course_id', 'transaction_group_name', 'created_by', 'redeemed_by'] + query_features = [ + 'code', 'course_id', 'company_name', 'created_by', + 'redeemed_by', 'invoice_id', 'purchaser', 'customer_reference_number', 'internal_reference' + ] 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) @@ -679,7 +794,11 @@ def random_code_generator(): generate a random alphanumeric code of length defined in REGISTRATION_CODE_LENGTH settings """ - chars = string.ascii_uppercase + string.digits + string.ascii_lowercase + chars = '' + for char in string.ascii_uppercase + string.digits + string.ascii_lowercase: + # removing vowel words and specific characters + chars += char.strip('aAeEiIoOuU1l') + code_length = getattr(settings, 'REGISTRATION_CODE_LENGTH', 8) return string.join((random.choice(chars) for _ in range(code_length)), '') @@ -695,11 +814,11 @@ def get_registration_codes(request, course_id): # pylint: disable=W0613 course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) #filter all the course registration codes - registration_codes = CourseRegistrationCode.objects.filter(course_id=course_id).order_by('transaction_group_name') + registration_codes = CourseRegistrationCode.objects.filter(course_id=course_id).order_by('invoice__company_name') - group_name = request.POST['download_transaction_group_name'] - if group_name: - registration_codes = registration_codes.filter(transaction_group_name=group_name) + company_name = request.POST['download_company_name'] + if company_name: + registration_codes = registration_codes.filter(invoice__company_name=company_name) csv_type = 'download' return registration_codes_csv("Registration_Codes.csv", registration_codes, csv_type) @@ -715,17 +834,86 @@ def generate_registration_codes(request, course_id): """ course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_registration_codes = [] + invoice_copy = False # covert the course registration code number into integer try: - course_code_number = int(request.POST['course_registration_code_number']) + course_code_number = int(request.POST['total_registration_codes']) except ValueError: - course_code_number = int(float(request.POST['course_registration_code_number'])) + course_code_number = int(float(request.POST['total_registration_codes'])) - group_name = request.POST['transaction_group_name'] + company_name = request.POST['company_name'] + company_contact_name = request.POST['company_contact_name'] + company_contact_email = request.POST['company_contact_email'] + sale_price = request.POST['sale_price'] + 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 + UserPreference.set_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 + ) for _ in range(course_code_number): # pylint: disable=W0621 - save_registration_codes(request, course_id, course_registration_codes, group_name) + save_registration_codes(request, course_id, course_registration_codes, sale_invoice) + + site_name = microsite.get_value('SITE_NAME', 'localhost') + course = get_course_by_id(course_id, depth=None) + course_honor_mode = CourseMode.mode_for_course(course_id, 'honor') + course_price = course_honor_mode.min_price + quantity = course_code_number + discount_price = (float(quantity * course_price) - float(sale_price)) + course_url = '{base_url}{course_about}'.format( + base_url=request.META['HTTP_HOST'], + course_about=reverse('about_course', kwargs={'course_id': course_id.to_deprecated_string()}) + ) + context = { + 'invoice': sale_invoice, + 'site_name': site_name, + 'course': course, + 'course_price': course_price, + 'discount_price': discount_price, + 'sale_price': sale_price, + 'quantity': quantity, + 'registration_codes': course_registration_codes, + 'course_url': course_url, + } + # composes registration codes invoice email + subject = u'Invoice for {course_name}'.format(course_name=course.display_name) + message = render_to_string('emails/registration_codes_sale_invoice.txt', context) + from_address = microsite.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL) + + #send_mail(subject, message, from_address, recipient_list, fail_silently=False) + csv_file = StringIO.StringIO() + csv_writer = csv.writer(csv_file) + for registration_code in course_registration_codes: + csv_writer.writerow([registration_code.code]) + + # 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.send() return registration_codes_csv("Registration_Codes.csv", course_registration_codes) @@ -741,11 +929,11 @@ def active_registration_codes(request, course_id): # pylint: disable=W0613 course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) # find all the registration codes in this course - registration_codes_list = CourseRegistrationCode.objects.filter(course_id=course_id).order_by('transaction_group_name') + registration_codes_list = CourseRegistrationCode.objects.filter(course_id=course_id).order_by('invoice__company_name') - group_name = request.POST['active_transaction_group_name'] - if group_name: - registration_codes_list = registration_codes_list.filter(transaction_group_name=group_name) + company_name = request.POST['active_company_name'] + if company_name: + registration_codes_list = registration_codes_list.filter(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').filter(registration_code__course_id=course_id) if code_redemption_set.exists(): @@ -768,17 +956,21 @@ def spent_registration_codes(request, course_id): # pylint: disable=W0613 course_id = SlashSeparatedCourseKey.from_deprecated_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) + 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('transaction_group_name') + spent_codes_list = CourseRegistrationCode.objects.filter( + course_id=course_id, code__in=redeemed_registration_codes + ).order_by('invoice__company_name') - group_name = request.POST['spent_transaction_group_name'] - if group_name: - spent_codes_list = spent_codes_list.filter(transaction_group_name=group_name) # pylint: disable=E1103 + company_name = request.POST['spent_company_name'] + if company_name: + spent_codes_list = spent_codes_list.filter(invoice__company_name=company_name) # pylint: disable=E1103 csv_type = 'spent' return registration_codes_csv("Spent_Registration_Codes.csv", spent_codes_list, csv_type) @@ -1371,6 +1563,20 @@ def proxy_legacy_analytics(request, course_id): ) +@require_POST +def get_user_invoice_preference(request, course_id): # pylint: disable=W0613 + """ + Gets invoice copy user's preferences. + """ + invoice_copy_preference = True + if UserPreference.get_preference(request.user, INVOICE_KEY) is not None: + invoice_copy_preference = UserPreference.get_preference(request.user, INVOICE_KEY) == 'True' + + return JsonResponse({ + 'invoice_copy': invoice_copy_preference + }) + + def _display_unit(unit): """ Gets string for displaying unit to user. diff --git a/lms/djangoapps/instructor/views/api_urls.py b/lms/djangoapps/instructor/views/api_urls.py index 0f3b5d0d11..3bf9ba651f 100644 --- a/lms/djangoapps/instructor/views/api_urls.py +++ b/lms/djangoapps/instructor/views/api_urls.py @@ -19,6 +19,12 @@ urlpatterns = patterns('', # nopep8 'instructor.views.api.get_students_features', name="get_students_features"), url(r'^get_purchase_transaction(?P/csv)?$', 'instructor.views.api.get_purchase_transaction', name="get_purchase_transaction"), + url(r'^get_user_invoice_preference$', + 'instructor.views.api.get_user_invoice_preference', name="get_user_invoice_preference"), + url(r'^get_sale_records(?P/csv)?$', + 'instructor.views.api.get_sale_records', name="get_sale_records"), + url(r'^sale_validation_url$', + 'instructor.views.api.sale_validation', name="sale_validation"), url(r'^get_anon_ids$', 'instructor.views.api.get_anon_ids', name="get_anon_ids"), url(r'^get_distribution$', @@ -68,6 +74,10 @@ urlpatterns = patterns('', # nopep8 url(r'spent_registration_codes$', 'instructor.views.api.spent_registration_codes', name="spent_registration_codes"), + # Coupon Codes.. + url(r'get_coupon_codes', + 'instructor.views.api.get_coupon_codes', name="get_coupon_codes"), + # spoc gradebook url(r'^gradebook$', 'instructor.views.api.spoc_gradebook', name='spoc_gradebook'), diff --git a/lms/djangoapps/instructor/views/coupons.py b/lms/djangoapps/instructor/views/coupons.py index b6ee401732..b98eb9ba15 100644 --- a/lms/djangoapps/instructor/views/coupons.py +++ b/lms/djangoapps/instructor/views/coupons.py @@ -97,31 +97,8 @@ def update_coupon(request, course_id): # pylint: disable=W0613 except ObjectDoesNotExist: return HttpResponseNotFound(_("coupon with the coupon id ({coupon_id}) DoesNotExist").format(coupon_id=coupon_id)) - code = request.POST.get('code') - filtered_coupons = Coupon.objects.filter(~Q(id=coupon_id), code=code, is_active=True) - - if filtered_coupons: - return HttpResponseNotFound(_("coupon with the coupon id ({coupon_id}) already exists").format(coupon_id=coupon_id)) - - # check if the coupon code is in the CourseRegistrationCode Table - course_registration_code = CourseRegistrationCode.objects.filter(code=code) - if course_registration_code: - return HttpResponseNotFound(_( - "The code ({code}) that you have tried to define is already in use as a registration code").format(code=code) - ) - description = request.POST.get('description') - course_id = request.POST.get('course_id') - try: - discount = int(request.POST.get('discount')) - except ValueError: - return HttpResponseNotFound(_("Please Enter the Integer Value for Coupon Discount")) - if discount > 100: - return HttpResponseNotFound(_("Please Enter the Coupon Discount Value Less than or Equal to 100")) - coupon.code = code coupon.description = description - coupon.course_id = course_id - coupon.percentage_discount = discount coupon.save() return HttpResponse(_("coupon with the coupon id ({coupon_id}) updated Successfully").format(coupon_id=coupon_id)) diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 9daa11722b..db9f1a4d79 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -156,15 +156,19 @@ def _section_e_commerce(course_key, access): 'course_id': course_key.to_deprecated_string(), 'ajax_remove_coupon_url': reverse('remove_coupon', kwargs={'course_id': course_key.to_deprecated_string()}), 'ajax_get_coupon_info': reverse('get_coupon_info', kwargs={'course_id': course_key.to_deprecated_string()}), + 'get_user_invoice_preference_url': reverse('get_user_invoice_preference', kwargs={'course_id': course_key.to_deprecated_string()}), + 'sale_validation_url': reverse('sale_validation', kwargs={'course_id': course_key.to_deprecated_string()}), 'ajax_update_coupon': reverse('update_coupon', kwargs={'course_id': course_key.to_deprecated_string()}), 'ajax_add_coupon': reverse('add_coupon', kwargs={'course_id': course_key.to_deprecated_string()}), 'get_purchase_transaction_url': reverse('get_purchase_transaction', kwargs={'course_id': course_key.to_deprecated_string()}), + 'get_sale_records_url': reverse('get_sale_records', kwargs={'course_id': course_key.to_deprecated_string()}), 'instructor_url': reverse('instructor_dashboard', kwargs={'course_id': course_key.to_deprecated_string()}), 'get_registration_code_csv_url': reverse('get_registration_codes', kwargs={'course_id': course_key.to_deprecated_string()}), 'generate_registration_code_csv_url': reverse('generate_registration_codes', kwargs={'course_id': course_key.to_deprecated_string()}), 'active_registration_code_csv_url': reverse('active_registration_codes', kwargs={'course_id': course_key.to_deprecated_string()}), 'spent_registration_code_csv_url': reverse('spent_registration_codes', kwargs={'course_id': course_key.to_deprecated_string()}), 'set_course_mode_url': reverse('set_course_mode_price', kwargs={'course_id': course_key.to_deprecated_string()}), + 'download_coupon_codes_url': reverse('get_coupon_codes', kwargs={'course_id': course_key.to_deprecated_string()}), 'coupons': coupons, 'total_amount': total_amount, 'course_price': course_price diff --git a/lms/djangoapps/instructor_analytics/basic.py b/lms/djangoapps/instructor_analytics/basic.py index ad29473355..d5bc599663 100644 --- a/lms/djangoapps/instructor_analytics/basic.py +++ b/lms/djangoapps/instructor_analytics/basic.py @@ -3,7 +3,7 @@ Student and course analytics. Serve miscellaneous course and student data """ -from shoppingcart.models import PaidCourseRegistration, CouponRedemption +from shoppingcart.models import PaidCourseRegistration, CouponRedemption, Invoice, RegistrationCodeRedemption from django.contrib.auth.models import User import xmodule.graders as xmgraders from django.core.exceptions import ObjectDoesNotExist @@ -15,8 +15,58 @@ PROFILE_FEATURES = ('name', 'language', 'location', 'year_of_birth', 'gender', ORDER_ITEM_FEATURES = ('list_price', 'unit_cost', 'order_id') ORDER_FEATURES = ('purchase_time',) +SALE_FEATURES = ('total_amount', 'company_name', 'company_contact_name', 'company_contact_email', 'recipient_name', + 'recipient_email', 'customer_reference_number', 'internal_reference') + AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES -COURSE_REGISTRATION_FEATURES = ('code', 'course_id', 'transaction_group_name', 'created_by') +COURSE_REGISTRATION_FEATURES = ('code', 'course_id', 'created_by', 'created_at') +COUPON_FEATURES = ('course_id', 'percentage_discount', 'description') + + +def sale_record_features(course_id, features): + """ + Return list of sales features as dictionaries. + + sales_records(course_id, ['company_name, total_codes', total_amount]) + would return [ + {'company_name': 'group_A', 'total_codes': '1', total_amount:'total_amount1 in decimal'.} + {'company_name': 'group_B', 'total_codes': '2', total_amount:'total_amount2 in decimal'.} + {'company_name': 'group_C', 'total_codes': '3', total_amount:'total_amount3 in decimal'.} + ] + """ + sales = Invoice.objects.filter(course_id=course_id) + + def sale_records_info(sale, features): + """ convert sales records to dictionary """ + + sale_features = [x for x in SALE_FEATURES if x in features] + course_reg_features = [x for x in COURSE_REGISTRATION_FEATURES if x in features] + + # Extracting sale information + sale_dict = dict((feature, getattr(sale, feature)) + for feature in sale_features) + + total_used_codes = RegistrationCodeRedemption.objects.filter(registration_code__in=sale.courseregistrationcode_set.all()).count() + sale_dict.update({"invoice_number": getattr(sale, 'id')}) + sale_dict.update({"total_codes": sale.courseregistrationcode_set.all().count()}) + sale_dict.update({'total_used_codes': total_used_codes}) + + codes = list() + for reg_code in sale.courseregistrationcode_set.all(): + codes.append(reg_code.code) + + # Extracting registration code information + obj_course_reg_code = sale.courseregistrationcode_set.all()[:1].get() + course_reg_dict = dict((feature, getattr(obj_course_reg_code, feature)) + for feature in course_reg_features) + + course_reg_dict['course_id'] = course_id.to_deprecated_string() + course_reg_dict.update({'codes': ", ".join(codes)}) + sale_dict.update(dict(course_reg_dict.items())) + + return sale_dict + + return [sale_records_info(sale, features) for sale in sales] def purchase_transactions(course_id, features): @@ -100,6 +150,36 @@ def enrolled_students_features(course_id, features): return [extract_student(student, features) for student in students] +def coupon_codes_features(features, coupons_list): + """ + Return list of Coupon Codes as dictionaries. + + coupon_codes_features + would return [ + {'course_id': 'edX/Open_DemoX/edx_demo_course,, 'discount': '213' ..... } + {'course_id': 'edX/Open_DemoX/edx_demo_course,, 'discount': '234' ..... } + ] + """ + + def extract_coupon(coupon, features): + """ convert coupon_codes to dictionary + :param coupon_codes: + :param features: + """ + coupon_features = [x for x in COUPON_FEATURES if x in features] + + coupon_dict = dict((feature, getattr(coupon, feature)) for feature in coupon_features) + coupon_dict['code_redeemed_count'] = coupon.couponredemption_set.all().count() + + # we have to capture the redeemed_by value in the case of the downloading and spent registration + # codes csv. In the case of active and generated registration codes the redeemed_by value will be None. + # They have not been redeemed yet + + coupon_dict['course_id'] = coupon_dict['course_id'].to_deprecated_string() + return coupon_dict + return [extract_coupon(coupon, features) for coupon in coupons_list] + + def course_registration_features(features, registration_codes, csv_type): """ Return list of Course Registration Codes as dictionaries. @@ -120,14 +200,24 @@ def course_registration_features(features, registration_codes, csv_type): registration_features = [x for x in COURSE_REGISTRATION_FEATURES if x in features] course_registration_dict = dict((feature, getattr(registration_code, feature)) for feature in registration_features) + course_registration_dict['company_name'] = None + if registration_code.invoice: + course_registration_dict['company_name'] = getattr(registration_code.invoice, 'company_name') course_registration_dict['redeemed_by'] = None + if registration_code.invoice: + sale_invoice = Invoice.objects.get(id=registration_code.invoice_id) + course_registration_dict['invoice_id'] = sale_invoice.id + course_registration_dict['purchaser'] = sale_invoice.recipient_name + course_registration_dict['customer_reference_number'] = sale_invoice.customer_reference_number + course_registration_dict['internal_reference'] = sale_invoice.internal_reference # we have to capture the redeemed_by value in the case of the downloading and spent registration # codes csv. In the case of active and generated registration codes the redeemed_by value will be None. # They have not been redeemed yet if csv_type is not None: try: - course_registration_dict['redeemed_by'] = getattr(registration_code.registrationcoderedemption_set.get(registration_code=registration_code), 'redeemed_by') + redeemed_by = getattr(registration_code.registrationcoderedemption_set.get(registration_code=registration_code), 'redeemed_by') + course_registration_dict['redeemed_by'] = getattr(redeemed_by, 'email') except ObjectDoesNotExist: pass diff --git a/lms/djangoapps/instructor_analytics/tests/test_basic.py b/lms/djangoapps/instructor_analytics/tests/test_basic.py index 41e50f0ef4..591abc1d77 100644 --- a/lms/djangoapps/instructor_analytics/tests/test_basic.py +++ b/lms/djangoapps/instructor_analytics/tests/test_basic.py @@ -4,11 +4,17 @@ Tests for instructor.basic from django.test import TestCase from student.models import CourseEnrollment +from django.core.urlresolvers import reverse from student.tests.factories import UserFactory from opaque_keys.edx.locations import SlashSeparatedCourseKey -from shoppingcart.models import CourseRegistrationCode, RegistrationCodeRedemption, Order +from shoppingcart.models import CourseRegistrationCode, RegistrationCodeRedemption, Order, Invoice, Coupon -from instructor_analytics.basic import enrolled_students_features, course_registration_features, AVAILABLE_FEATURES, STUDENT_FEATURES, PROFILE_FEATURES +from instructor_analytics.basic import ( + sale_record_features, enrolled_students_features, course_registration_features, coupon_codes_features, + AVAILABLE_FEATURES, STUDENT_FEATURES, PROFILE_FEATURES +) +from courseware.tests.factories import InstructorFactory +from xmodule.modulestore.tests.factories import CourseFactory class TestAnalyticsBasic(TestCase): @@ -19,6 +25,7 @@ class TestAnalyticsBasic(TestCase): self.users = tuple(UserFactory() for _ in xrange(30)) self.ces = tuple(CourseEnrollment.enroll(user, self.course_key) for user in self.users) + self.instructor = InstructorFactory(course_key=self.course_key) def test_enrolled_students_features_username(self): self.assertIn('username', AVAILABLE_FEATURES) @@ -44,20 +51,89 @@ class TestAnalyticsBasic(TestCase): self.assertEqual(len(AVAILABLE_FEATURES), len(STUDENT_FEATURES + PROFILE_FEATURES)) self.assertEqual(set(AVAILABLE_FEATURES), set(STUDENT_FEATURES + PROFILE_FEATURES)) - def test_course_registration_features(self): - query_features = ['code', 'course_id', 'transaction_group_name', 'created_by', 'redeemed_by'] + +class TestCourseSaleRecordsAnalyticsBasic(TestCase): + """ Test basic course sale records analytics functions. """ + def setUp(self): + """ + Fixtures. + """ + self.course = CourseFactory.create() + 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_at', '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 + ) for i in range(5): course_code = CourseRegistrationCode( - code="test_code{}".format(i), course_id=self.course_key.to_deprecated_string(), - transaction_group_name='TestName', created_by=self.users[0] + code="test_code{}".format(i), course_id=self.course.id.to_deprecated_string(), + created_by=self.instructor, invoice=sale_invoice ) course_code.save() - order = Order(user=self.users[0], status='purchased') + 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) + + +class TestCourseRegistrationCodeAnalyticsBasic(TestCase): + """ Test basic course registration codes analytics functions. """ + def setUp(self): + """ + Fixtures. + """ + self.course = CourseFactory.create() + self.instructor = InstructorFactory(course_key=self.course.id) + self.client.login(username=self.instructor.username, password='test') + + url = reverse('generate_registration_codes', + kwargs={'course_id': self.course.id.to_deprecated_string()}) + + data = { + 'total_registration_codes': 12, 'company_name': 'Test Group', 'sale_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', '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( - order=order, registration_code_id=1, redeemed_by=self.users[0] + order=order, registration_code_id=1, redeemed_by=self.instructor ) registration_code_redemption.save() registration_codes = CourseRegistrationCode.objects.all() @@ -71,6 +147,32 @@ class TestAnalyticsBasic(TestCase): [registration_code.course_id.to_deprecated_string() for registration_code in registration_codes] ) self.assertIn( - course_registration['transaction_group_name'], - [registration_code.transaction_group_name for registration_code in registration_codes] + course_registration['company_name'], + [getattr(registration_code.invoice, 'company_name') for registration_code in registration_codes] + ) + self.assertIn( + course_registration['invoice_id'], + [registration_code.invoice_id for registration_code in registration_codes] + ) + + def test_coupon_codes_features(self): + query_features = [ + 'course_id', 'percentage_discount', 'code_redeemed_count', 'description' + ] + 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() + active_coupons = Coupon.objects.filter(course_id=self.course.id, is_active=True) + active_coupons_list = coupon_codes_features(query_features, active_coupons) + 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]) + self.assertIn( + active_coupon['course_id'], + [coupon.course_id.to_deprecated_string() for coupon in active_coupons] ) diff --git a/lms/djangoapps/shoppingcart/migrations/0011_auto__add_invoice__add_field_courseregistrationcode_invoice.py b/lms/djangoapps/shoppingcart/migrations/0011_auto__add_invoice__add_field_courseregistrationcode_invoice.py new file mode 100644 index 0000000000..d67d48e426 --- /dev/null +++ b/lms/djangoapps/shoppingcart/migrations/0011_auto__add_invoice__add_field_courseregistrationcode_invoice.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Invoice' + db.create_table('shoppingcart_invoice', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('total_amount', self.gf('django.db.models.fields.FloatField')()), + ('purchaser_name', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('purchaser_contact', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('purchaser_email', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('tax_id', self.gf('django.db.models.fields.CharField')(max_length=64, null=True)), + ('reference', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)), + )) + db.send_create_signal('shoppingcart', ['Invoice']) + + # Adding field 'CourseRegistrationCode.invoice' + db.add_column('shoppingcart_courseregistrationcode', 'invoice', + self.gf('django.db.models.fields.related.ForeignKey')(to=orm['shoppingcart.Invoice'], null=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting model 'Invoice' + db.delete_table('shoppingcart_invoice') + + # Deleting field 'CourseRegistrationCode.invoice' + db.delete_column('shoppingcart_courseregistrationcode', 'invoice_id') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'shoppingcart.certificateitem': { + 'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.coupon': { + 'Meta': {'object_name': 'Coupon'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 6, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'shoppingcart.couponredemption': { + 'Meta': {'object_name': 'CouponRedemption'}, + 'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.courseregistrationcode': { + 'Meta': {'object_name': 'CourseRegistrationCode'}, + 'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 6, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}), + 'transaction_group_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + 'shoppingcart.invoice': { + 'Meta': {'object_name': 'Invoice'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'purchaser_contact': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'purchaser_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'purchaser_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'tax_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}), + 'total_amount': ('django.db.models.fields.FloatField', [], {}) + }, + 'shoppingcart.order': { + 'Meta': {'object_name': 'Order'}, + 'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.orderitem': { + 'Meta': {'object_name': 'OrderItem'}, + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}), + 'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}), + 'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.paidcourseregistration': { + 'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.paidcourseregistrationannotation': { + 'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.registrationcoderedemption': { + 'Meta': {'object_name': 'RegistrationCodeRedemption'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 6, 0, 0)', 'null': 'True'}), + 'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"}) + }, + 'student.courseenrollment': { + 'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['shoppingcart'] \ No newline at end of file diff --git a/lms/djangoapps/shoppingcart/migrations/0012_auto__del_field_courseregistrationcode_transaction_group_name__del_fie.py b/lms/djangoapps/shoppingcart/migrations/0012_auto__del_field_courseregistrationcode_transaction_group_name__del_fie.py new file mode 100644 index 0000000000..77c7635538 --- /dev/null +++ b/lms/djangoapps/shoppingcart/migrations/0012_auto__del_field_courseregistrationcode_transaction_group_name__del_fie.py @@ -0,0 +1,252 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Deleting field 'CourseRegistrationCode.transaction_group_name' + db.delete_column('shoppingcart_courseregistrationcode', 'transaction_group_name') + + # Deleting field 'Invoice.purchaser_contact' + db.delete_column('shoppingcart_invoice', 'purchaser_contact') + + # Deleting field 'Invoice.purchaser_email' + db.delete_column('shoppingcart_invoice', 'purchaser_email') + + # Deleting field 'Invoice.reference' + db.delete_column('shoppingcart_invoice', 'reference') + + # Deleting field 'Invoice.purchaser_name' + db.delete_column('shoppingcart_invoice', 'purchaser_name') + + # Adding field 'Invoice.company_name' + db.add_column('shoppingcart_invoice', 'company_name', + self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True), + keep_default=False) + + # Adding field 'Invoice.course_id' + db.add_column('shoppingcart_invoice', 'course_id', + self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True), + keep_default=False) + + # Adding field 'Invoice.company_contact_name' + db.add_column('shoppingcart_invoice', 'company_contact_name', + self.gf('django.db.models.fields.CharField')(max_length=255), + keep_default=False) + + # Adding field 'Invoice.company_contact_email' + db.add_column('shoppingcart_invoice', 'company_contact_email', + self.gf('django.db.models.fields.CharField')(max_length=255), + keep_default=False) + + # Adding field 'Invoice.company_reference' + db.add_column('shoppingcart_invoice', 'company_reference', + self.gf('django.db.models.fields.CharField')(max_length=255, null=True), + keep_default=False) + + # Adding field 'Invoice.internal_reference' + db.add_column('shoppingcart_invoice', 'internal_reference', + self.gf('django.db.models.fields.CharField')(max_length=255, null=True), + keep_default=False) + + + def backwards(self, orm): + # Adding field 'CourseRegistrationCode.transaction_group_name' + db.add_column('shoppingcart_courseregistrationcode', 'transaction_group_name', + self.gf('django.db.models.fields.CharField')(blank=True, max_length=255, null=True, db_index=True), + keep_default=False) + + # Adding field 'Invoice.purchaser_contact' + db.add_column('shoppingcart_invoice', 'purchaser_contact', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Adding field 'Invoice.purchaser_email' + db.add_column('shoppingcart_invoice', 'purchaser_email', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Adding field 'Invoice.reference' + db.add_column('shoppingcart_invoice', 'reference', + self.gf('django.db.models.fields.CharField')(max_length=255, null=True), + keep_default=False) + + # Adding field 'Invoice.purchaser_name' + db.add_column('shoppingcart_invoice', 'purchaser_name', + self.gf('django.db.models.fields.CharField')(default='', max_length=255, db_index=True), + keep_default=False) + + # Deleting field 'Invoice.company_name' + db.delete_column('shoppingcart_invoice', 'company_name') + + # Deleting field 'Invoice.course_id' + db.delete_column('shoppingcart_invoice', 'course_id') + + # Deleting field 'Invoice.company_contact_name' + db.delete_column('shoppingcart_invoice', 'company_contact_name') + + # Deleting field 'Invoice.company_contact_email' + db.delete_column('shoppingcart_invoice', 'company_contact_email') + + # Deleting field 'Invoice.company_reference' + db.delete_column('shoppingcart_invoice', 'company_reference') + + # Deleting field 'Invoice.internal_reference' + db.delete_column('shoppingcart_invoice', 'internal_reference') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'shoppingcart.certificateitem': { + 'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.coupon': { + 'Meta': {'object_name': 'Coupon'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 13, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'shoppingcart.couponredemption': { + 'Meta': {'object_name': 'CouponRedemption'}, + 'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.courseregistrationcode': { + 'Meta': {'object_name': 'CourseRegistrationCode'}, + 'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 13, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}) + }, + 'shoppingcart.invoice': { + 'Meta': {'object_name': 'Invoice'}, + 'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'company_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'tax_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}), + 'total_amount': ('django.db.models.fields.FloatField', [], {}) + }, + 'shoppingcart.order': { + 'Meta': {'object_name': 'Order'}, + 'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.orderitem': { + 'Meta': {'object_name': 'OrderItem'}, + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}), + 'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}), + 'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.paidcourseregistration': { + 'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.paidcourseregistrationannotation': { + 'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.registrationcoderedemption': { + 'Meta': {'object_name': 'RegistrationCodeRedemption'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 13, 0, 0)', 'null': 'True'}), + 'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"}) + }, + 'student.courseenrollment': { + 'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['shoppingcart'] \ No newline at end of file diff --git a/lms/djangoapps/shoppingcart/migrations/0013_auto__add_field_invoice_is_valid.py b/lms/djangoapps/shoppingcart/migrations/0013_auto__add_field_invoice_is_valid.py new file mode 100644 index 0000000000..2520a8b182 --- /dev/null +++ b/lms/djangoapps/shoppingcart/migrations/0013_auto__add_field_invoice_is_valid.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Invoice.is_valid' + db.add_column('shoppingcart_invoice', 'is_valid', + self.gf('django.db.models.fields.BooleanField')(default=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Invoice.is_valid' + db.delete_column('shoppingcart_invoice', 'is_valid') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'shoppingcart.certificateitem': { + 'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.coupon': { + 'Meta': {'object_name': 'Coupon'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 20, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'shoppingcart.couponredemption': { + 'Meta': {'object_name': 'CouponRedemption'}, + 'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.courseregistrationcode': { + 'Meta': {'object_name': 'CourseRegistrationCode'}, + 'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 20, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}) + }, + 'shoppingcart.invoice': { + 'Meta': {'object_name': 'Invoice'}, + 'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'company_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'tax_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}), + 'total_amount': ('django.db.models.fields.FloatField', [], {}) + }, + 'shoppingcart.order': { + 'Meta': {'object_name': 'Order'}, + 'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.orderitem': { + 'Meta': {'object_name': 'OrderItem'}, + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}), + 'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}), + 'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.paidcourseregistration': { + 'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.paidcourseregistrationannotation': { + 'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.registrationcoderedemption': { + 'Meta': {'object_name': 'RegistrationCodeRedemption'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 20, 0, 0)', 'null': 'True'}), + 'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"}) + }, + 'student.courseenrollment': { + 'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['shoppingcart'] \ No newline at end of file diff --git a/lms/djangoapps/shoppingcart/migrations/0014_auto__del_field_invoice_tax_id__add_field_invoice_address_line_1__add_.py b/lms/djangoapps/shoppingcart/migrations/0014_auto__del_field_invoice_tax_id__add_field_invoice_address_line_1__add_.py new file mode 100644 index 0000000000..fe727666cb --- /dev/null +++ b/lms/djangoapps/shoppingcart/migrations/0014_auto__del_field_invoice_tax_id__add_field_invoice_address_line_1__add_.py @@ -0,0 +1,244 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Deleting field 'Invoice.tax_id' + db.delete_column('shoppingcart_invoice', 'tax_id') + + # Adding field 'Invoice.address_line_1' + db.add_column('shoppingcart_invoice', 'address_line_1', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Adding field 'Invoice.address_line_2' + db.add_column('shoppingcart_invoice', 'address_line_2', + self.gf('django.db.models.fields.CharField')(max_length=255, null=True), + keep_default=False) + + # Adding field 'Invoice.address_line_3' + db.add_column('shoppingcart_invoice', 'address_line_3', + self.gf('django.db.models.fields.CharField')(max_length=255, null=True), + keep_default=False) + + # Adding field 'Invoice.city' + db.add_column('shoppingcart_invoice', 'city', + self.gf('django.db.models.fields.CharField')(max_length=255, null=True), + keep_default=False) + + # Adding field 'Invoice.state' + db.add_column('shoppingcart_invoice', 'state', + self.gf('django.db.models.fields.CharField')(max_length=255, null=True), + keep_default=False) + + # Adding field 'Invoice.zip' + db.add_column('shoppingcart_invoice', 'zip', + self.gf('django.db.models.fields.CharField')(max_length=15, null=True), + keep_default=False) + + # Adding field 'Invoice.country' + db.add_column('shoppingcart_invoice', 'country', + self.gf('django.db.models.fields.CharField')(max_length=64, null=True), + keep_default=False) + + # Adding field 'Invoice.purchase_order_number' + db.add_column('shoppingcart_invoice', 'purchase_order_number', + self.gf('django.db.models.fields.CharField')(max_length=63, null=True), + keep_default=False) + + + def backwards(self, orm): + # Adding field 'Invoice.tax_id' + db.add_column('shoppingcart_invoice', 'tax_id', + self.gf('django.db.models.fields.CharField')(max_length=64, null=True), + keep_default=False) + + # Deleting field 'Invoice.address_line_1' + db.delete_column('shoppingcart_invoice', 'address_line_1') + + # Deleting field 'Invoice.address_line_2' + db.delete_column('shoppingcart_invoice', 'address_line_2') + + # Deleting field 'Invoice.address_line_3' + db.delete_column('shoppingcart_invoice', 'address_line_3') + + # Deleting field 'Invoice.city' + db.delete_column('shoppingcart_invoice', 'city') + + # Deleting field 'Invoice.state' + db.delete_column('shoppingcart_invoice', 'state') + + # Deleting field 'Invoice.zip' + db.delete_column('shoppingcart_invoice', 'zip') + + # Deleting field 'Invoice.country' + db.delete_column('shoppingcart_invoice', 'country') + + # Deleting field 'Invoice.purchase_order_number' + db.delete_column('shoppingcart_invoice', 'purchase_order_number') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'shoppingcart.certificateitem': { + 'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.coupon': { + 'Meta': {'object_name': 'Coupon'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 27, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'shoppingcart.couponredemption': { + 'Meta': {'object_name': 'CouponRedemption'}, + 'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.courseregistrationcode': { + 'Meta': {'object_name': 'CourseRegistrationCode'}, + 'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 27, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}) + }, + 'shoppingcart.invoice': { + 'Meta': {'object_name': 'Invoice'}, + 'address_line_1': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'address_line_2': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'address_line_3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'company_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'purchase_order_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True'}), + 'state': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'total_amount': ('django.db.models.fields.FloatField', [], {}), + 'zip': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}) + }, + 'shoppingcart.order': { + 'Meta': {'object_name': 'Order'}, + 'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.orderitem': { + 'Meta': {'object_name': 'OrderItem'}, + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}), + 'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}), + 'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.paidcourseregistration': { + 'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.paidcourseregistrationannotation': { + 'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.registrationcoderedemption': { + 'Meta': {'object_name': 'RegistrationCodeRedemption'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 27, 0, 0)', 'null': 'True'}), + 'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"}) + }, + 'student.courseenrollment': { + 'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['shoppingcart'] \ No newline at end of file diff --git a/lms/djangoapps/shoppingcart/migrations/0015_auto__del_field_invoice_purchase_order_number__del_field_invoice_compa.py b/lms/djangoapps/shoppingcart/migrations/0015_auto__del_field_invoice_purchase_order_number__del_field_invoice_compa.py new file mode 100644 index 0000000000..3d30974e15 --- /dev/null +++ b/lms/djangoapps/shoppingcart/migrations/0015_auto__del_field_invoice_purchase_order_number__del_field_invoice_compa.py @@ -0,0 +1,229 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Deleting field 'Invoice.purchase_order_number' + db.delete_column('shoppingcart_invoice', 'purchase_order_number') + + # Deleting field 'Invoice.company_contact_name' + db.delete_column('shoppingcart_invoice', 'company_contact_name') + + # Deleting field 'Invoice.company_contact_email' + db.delete_column('shoppingcart_invoice', 'company_contact_email') + + # Adding field 'Invoice.company_email' + db.add_column('shoppingcart_invoice', 'company_email', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Adding field 'Invoice.recipient_name' + db.add_column('shoppingcart_invoice', 'recipient_name', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Adding field 'Invoice.recipient_email' + db.add_column('shoppingcart_invoice', 'recipient_email', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Adding field 'Invoice.customer_reference_number' + db.add_column('shoppingcart_invoice', 'customer_reference_number', + self.gf('django.db.models.fields.CharField')(max_length=63, null=True), + keep_default=False) + + + def backwards(self, orm): + # Adding field 'Invoice.purchase_order_number' + db.add_column('shoppingcart_invoice', 'purchase_order_number', + self.gf('django.db.models.fields.CharField')(max_length=63, null=True), + keep_default=False) + + # Adding field 'Invoice.company_contact_name' + db.add_column('shoppingcart_invoice', 'company_contact_name', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Adding field 'Invoice.company_contact_email' + db.add_column('shoppingcart_invoice', 'company_contact_email', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Deleting field 'Invoice.company_email' + db.delete_column('shoppingcart_invoice', 'company_email') + + # Deleting field 'Invoice.recipient_name' + db.delete_column('shoppingcart_invoice', 'recipient_name') + + # Deleting field 'Invoice.recipient_email' + db.delete_column('shoppingcart_invoice', 'recipient_email') + + # Deleting field 'Invoice.customer_reference_number' + db.delete_column('shoppingcart_invoice', 'customer_reference_number') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'shoppingcart.certificateitem': { + 'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.coupon': { + 'Meta': {'object_name': 'Coupon'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 28, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'shoppingcart.couponredemption': { + 'Meta': {'object_name': 'CouponRedemption'}, + 'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.courseregistrationcode': { + 'Meta': {'object_name': 'CourseRegistrationCode'}, + 'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 28, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}) + }, + 'shoppingcart.invoice': { + 'Meta': {'object_name': 'Invoice'}, + 'address_line_1': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'address_line_2': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'address_line_3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'company_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'company_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'state': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'total_amount': ('django.db.models.fields.FloatField', [], {}), + 'zip': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}) + }, + 'shoppingcart.order': { + 'Meta': {'object_name': 'Order'}, + 'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.orderitem': { + 'Meta': {'object_name': 'OrderItem'}, + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}), + 'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}), + 'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.paidcourseregistration': { + 'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.paidcourseregistrationannotation': { + 'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.registrationcoderedemption': { + 'Meta': {'object_name': 'RegistrationCodeRedemption'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 28, 0, 0)', 'null': 'True'}), + 'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"}) + }, + 'student.courseenrollment': { + 'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['shoppingcart'] \ No newline at end of file diff --git a/lms/djangoapps/shoppingcart/migrations/0016_auto__del_field_invoice_company_email__del_field_invoice_company_refer.py b/lms/djangoapps/shoppingcart/migrations/0016_auto__del_field_invoice_company_email__del_field_invoice_company_refer.py new file mode 100644 index 0000000000..ae9e50d87e --- /dev/null +++ b/lms/djangoapps/shoppingcart/migrations/0016_auto__del_field_invoice_company_email__del_field_invoice_company_refer.py @@ -0,0 +1,205 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Deleting field 'Invoice.company_email' + db.delete_column('shoppingcart_invoice', 'company_email') + + # Deleting field 'Invoice.company_reference' + db.delete_column('shoppingcart_invoice', 'company_reference') + + # Adding field 'Invoice.company_contact_name' + db.add_column('shoppingcart_invoice', 'company_contact_name', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Adding field 'Invoice.company_contact_email' + db.add_column('shoppingcart_invoice', 'company_contact_email', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + + def backwards(self, orm): + # Adding field 'Invoice.company_email' + db.add_column('shoppingcart_invoice', 'company_email', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + # Adding field 'Invoice.company_reference' + db.add_column('shoppingcart_invoice', 'company_reference', + self.gf('django.db.models.fields.CharField')(max_length=255, null=True), + keep_default=False) + + # Deleting field 'Invoice.company_contact_name' + db.delete_column('shoppingcart_invoice', 'company_contact_name') + + # Deleting field 'Invoice.company_contact_email' + db.delete_column('shoppingcart_invoice', 'company_contact_email') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'shoppingcart.certificateitem': { + 'Meta': {'object_name': 'CertificateItem', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']"}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.coupon': { + 'Meta': {'object_name': 'Coupon'}, + 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 29, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'percentage_discount': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'shoppingcart.couponredemption': { + 'Meta': {'object_name': 'CouponRedemption'}, + 'coupon': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Coupon']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.courseregistrationcode': { + 'Meta': {'object_name': 'CourseRegistrationCode'}, + 'code': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 29, 0, 0)'}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_by_user'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']", 'null': 'True'}) + }, + 'shoppingcart.invoice': { + 'Meta': {'object_name': 'Invoice'}, + 'address_line_1': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'address_line_2': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'address_line_3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'city': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'state': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'total_amount': ('django.db.models.fields.FloatField', [], {}), + 'zip': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True'}) + }, + 'shoppingcart.order': { + 'Meta': {'object_name': 'Order'}, + 'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'bill_to_state': ('django.db.models.fields.CharField', [], {'max_length': '8', 'blank': 'True'}), + 'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'refunded_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.orderitem': { + 'Meta': {'object_name': 'OrderItem'}, + 'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}), + 'fulfilled_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'line_desc': ('django.db.models.fields.CharField', [], {'default': "'Misc. Item'", 'max_length': '1024'}), + 'list_price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '30', 'decimal_places': '2'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'refund_requested_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}), + 'report_comments': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'service_fee': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32', 'db_index': 'True'}), + 'unit_cost': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'shoppingcart.paidcourseregistration': { + 'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}), + 'mode': ('django.db.models.fields.SlugField', [], {'default': "'honor'", 'max_length': '50'}), + 'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'shoppingcart.paidcourseregistrationannotation': { + 'Meta': {'object_name': 'PaidCourseRegistrationAnnotation'}, + 'annotation': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'unique': 'True', 'max_length': '128', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'shoppingcart.registrationcoderedemption': { + 'Meta': {'object_name': 'RegistrationCodeRedemption'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']"}), + 'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2014, 8, 29, 0, 0)', 'null': 'True'}), + 'redeemed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'registration_code': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCode']"}) + }, + 'student.courseenrollment': { + 'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'}, + 'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['shoppingcart'] \ No newline at end of file diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index ac9704b258..ea33302de5 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -317,6 +317,31 @@ class OrderItem(models.Model): return '' +class Invoice(models.Model): + """ + This table capture all the information needed to support "invoicing" + which is when a user wants to purchase Registration Codes, + but will not do so via a Credit Card transaction. + """ + company_name = models.CharField(max_length=255, db_index=True) + company_contact_name = models.CharField(max_length=255) + company_contact_email = models.CharField(max_length=255) + recipient_name = models.CharField(max_length=255) + recipient_email = models.CharField(max_length=255) + address_line_1 = models.CharField(max_length=255) + address_line_2 = models.CharField(max_length=255, null=True) + address_line_3 = models.CharField(max_length=255, null=True) + city = models.CharField(max_length=255, null=True) + state = models.CharField(max_length=255, null=True) + zip = models.CharField(max_length=15, null=True) + country = models.CharField(max_length=64, null=True) + course_id = CourseKeyField(max_length=255, db_index=True) + total_amount = models.FloatField() + internal_reference = models.CharField(max_length=255, null=True) + customer_reference_number = models.CharField(max_length=63, null=True) + is_valid = models.BooleanField(default=True) + + class CourseRegistrationCode(models.Model): """ This table contains registration codes @@ -324,9 +349,9 @@ class CourseRegistrationCode(models.Model): """ code = models.CharField(max_length=32, db_index=True, unique=True) course_id = CourseKeyField(max_length=255, db_index=True) - transaction_group_name = models.CharField(max_length=255, db_index=True, null=True, blank=True) created_by = models.ForeignKey(User, related_name='created_by_user') created_at = models.DateTimeField(default=datetime.now(pytz.utc)) + invoice = models.ForeignKey(Invoice, null=True) @classmethod @transaction.commit_on_success @@ -574,7 +599,7 @@ class PaidCourseRegistration(OrderItem): Generates instructions when the user has purchased a PaidCourseRegistration. Basically tells the user to visit the dashboard to see their new classes """ - notification = (_('Please visit your dashboard to see your new enrollments.') + notification = (_('Please visit your dashboard to see your new course.') .format(dashboard_link=reverse('dashboard'))) return self.pk_with_subclass, set([notification]) diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py index cfb6205e71..86b3c9d414 100644 --- a/lms/djangoapps/shoppingcart/tests/test_views.py +++ b/lms/djangoapps/shoppingcart/tests/test_views.py @@ -96,8 +96,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): """ add dummy registration code into models """ - course_reg_code = CourseRegistrationCode(code=self.reg_code, course_id=course_key, - transaction_group_name='A', created_by=self.user) + course_reg_code = CourseRegistrationCode(code=self.reg_code, course_id=course_key, created_by=self.user) course_reg_code.save() def add_course_to_user_cart(self): diff --git a/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee b/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee index ddef9d8a27..2f614e6cd4 100644 --- a/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee +++ b/lms/static/coffee/src/instructor_dashboard/e-commerce.coffee @@ -10,19 +10,17 @@ class ECommerce @$section.data 'wrapper', @ # gather elements @$list_purchase_csv_btn = @$section.find("input[name='list-purchase-transaction-csv']'") - @$transaction_group_name = @$section.find("input[name='transaction_group_name']'") - @$course_registration_number = @$section.find("input[name='course_registration_code_number']'") - @$download_transaction_group_name = @$section.find("input[name='download_transaction_group_name']'") - @$active_transaction_group_name = @$section.find("input[name='active_transaction_group_name']'") - @$spent_transaction_group_name = @$section.find('input[name="spent_transaction_group_name"]') - - @$generate_registration_code_form = @$section.find("form#course_codes_number") + @$list_sale_csv_btn = @$section.find("input[name='list-sale-csv']'") + @$download_company_name = @$section.find("input[name='download_company_name']'") + @$active_company_name = @$section.find("input[name='active_company_name']'") + @$spent_company_name = @$section.find('input[name="spent_company_name"]') + @$download_coupon_codes = @$section.find('input[name="download-coupon-codes-csv"]') + @$download_registration_codes_form = @$section.find("form#download_registration_codes") @$active_registration_codes_form = @$section.find("form#active_registration_codes") @$spent_registration_codes_form = @$section.find("form#spent_registration_codes") - @$coupoon_error = @$section.find('#coupon-error') - @$course_code_error = @$section.find('#code-error') + @$error_msg = @$section.find('#error-msg') # attach click handlers # this handler binds to both the download @@ -31,47 +29,28 @@ class ECommerce url = @$list_purchase_csv_btn.data 'endpoint' url += '/csv' location.href = url + + @$list_sale_csv_btn.click (e) => + url = @$list_sale_csv_btn.data 'endpoint' + url += '/csv' + location.href = url + + @$download_coupon_codes.click (e) => + url = @$download_coupon_codes.data 'endpoint' + location.href = url @$download_registration_codes_form.submit (e) => - @$course_code_error.attr('style', 'display: none') - @$coupoon_error.attr('style', 'display: none') + @$error_msg.attr('style', 'display: none') return true @$active_registration_codes_form.submit (e) => - @$course_code_error.attr('style', 'display: none') - @$coupoon_error.attr('style', 'display: none') + @$error_msg.attr('style', 'display: none') return true @$spent_registration_codes_form.submit (e) => - @$course_code_error.attr('style', 'display: none') - @$coupoon_error.attr('style', 'display: none') + @$error_msg.attr('style', 'display: none') return true - @$generate_registration_code_form.submit (e) => - @$course_code_error.attr('style', 'display: none') - @$coupoon_error.attr('style', 'display: none') - group_name = @$transaction_group_name.val() - if group_name == '' - @$course_code_error.html('Please Enter the Transaction Group Name').show() - return false - - if ($.isNumeric(group_name)) - @$course_code_error.html('Please Enter the non-numeric value for Transaction Group Name').show() - return false; - - registration_codes = @$course_registration_number.val(); - if (isInt(registration_codes) && $.isNumeric(registration_codes)) - if (parseInt(registration_codes) > 1000 ) - @$course_code_error.html('You can only generate 1000 Registration Codes at a time').show() - return false; - if (parseInt(registration_codes) == 0 ) - @$course_code_error.html('Please Enter the Value greater than 0 for Registration Codes').show() - return false; - return true; - else - @$course_code_error.html('Please Enter the Integer Value for Registration Codes').show() - return false; - # handler for when the section title is clicked. onClickTitle: -> @clear_display() @@ -83,14 +62,10 @@ class ECommerce onExit: -> @clear_display() clear_display: -> - @$course_code_error.attr('style', 'display: none') - @$coupoon_error.attr('style', 'display: none') - @$course_registration_number.val('') - @$transaction_group_name.val('') - @$download_transaction_group_name.val('') - @$active_transaction_group_name.val('') - @$spent_transaction_group_name.val('') - + @$error_msg.attr('style', 'display: none') + @$download_company_name.val('') + @$active_company_name.val('') + @$spent_company_name.val('') isInt = (n) -> return n % 1 == 0; # Clear any generated tables, warning messages, etc. diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss index 1a266be8fc..3b37b53ba6 100644 --- a/lms/static/sass/course/instructor/_instructor_2.scss +++ b/lms/static/sass/course/instructor/_instructor_2.scss @@ -831,27 +831,32 @@ input[name="subject"] { } #e-commerce{ - .coupon-errors { + input { + margin-bottom: 1em; + line-height: 1.3em; + } + .error-msgs { background: #FFEEF5;color:#B72667;text-align: center;padding: 10px 0px; font-family: "Open Sans",Verdana,Geneva,sans-serif,sans-serif;font-size: 15px; border-bottom: 1px solid #B72667; margin-bottom: 20px; display: none; } + .success-msgs { + background: #D0F5D5;color:#008801;text-align: center;padding: 10px 0px; + font-family: "Open Sans",Verdana,Geneva,sans-serif,sans-serif;font-size: 15px; + border-bottom: 1px solid #008801; + margin-bottom: 20px; + display: none; + } .content{ padding: 0 !important; } - input[name="course_registration_code_number"] { - margin-right: 10px; - height: 34px; - width: 258px; - border-radius: 3px; - } - input[name="transaction_group_name"], input[name="download_transaction_group_name"], - input[name="active_transaction_group_name"], input[name="spent_transaction_group_name"] { + input[name="download_company_name"], + input[name="active_company_name"], input[name="spent_company_name"] { margin-right: 8px; height: 36px; - width: 300px; + width: 254px; border-radius: 3px; } .coupons-table { @@ -955,12 +960,21 @@ input[name="subject"] { } } } - + section#registration_code_generation_modal { + margin-left: -442px; + width: 930px; + } // coupon edit and add modals - #add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal{ + #add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal, #registration_code_generation_modal{ .inner-wrapper { background: #fff; } + span.tip-text { + font-size: 12px; + display: block; + margin-top: 5px; + color: #646464 + } top:-95px !important; width: 650px; margin-left: -325px; @@ -973,6 +987,10 @@ input[name="subject"] { @include button(simple, $blue); @extend .button-reset; } + input[name="generate-registration-codes-csv"]{ + @include button(simple, $blue); + @extend .button-reset; + } input[type="submit"]#set_course_button{ @include button(simple, $blue); @extend .button-reset; @@ -1014,6 +1032,61 @@ input[name="subject"] { margin-bottom: 0px !important; } } + form#generate_codes ol.list-input{ + li{ + width: 278px; + float: left; + label.required:after { + content: "*"; + margin-left: 5px; + } + min-height: 120px; + } + li.address_fields { + min-height: 45px !important; + } + li#generate-registration-modal-field-city, li#generate-registration-modal-field-state, + li#generate-registration-modal-field-zipcode{ + width: 205px; + } + li#generate-registration-modal-field-country { + width: 204px; + margin-left: 15px !important; + margin-bottom: 20px; + } + li:nth-child(even){ + margin-left: 0px !important; + } + li:nth-child(3n) { + margin-left: 15px !important; + } + li#generate-registration-modal-field-company-contact-name, + li#generate-registration-modal-field-address-line-3, + li#generate-registration-modal-field-zipcode { + margin-left: 15px !important; + } + li:last-child{ + label { + float: right; + margin-top: -5px; + right: 27px; + } + min-height: 5px; + margin-left: 0px !important; + input[type='checkbox'] { + width: auto; + height: auto; + } + } + li#generate-registration-modal-field-country ~ li#generate-registration-modal-field-total-price, + li#generate-registration-modal-field-country ~ li#generate-registration-modal-field-internal-reference { + margin-left: 0px !important; + margin-right: 15px !important; + } + li#generate-registration-modal-field-custom-reference-number { + width: auto; + } + } li#set-course-mode-modal-field-price{ width: 100%; label.required:after { @@ -1027,7 +1100,13 @@ input[name="subject"] { width: 100%; } } - #coupon-content, #course-content { + #registration-content form .field.text input { + background: #fff; + margin-bottom: 0; + height: 40px; + border-radius: 3px; + } + #coupon-content, #course-content, #registration-content { padding: 20px; header { margin: 0; @@ -1086,6 +1165,19 @@ input[name="subject"] { } } } + #registration-content form .group-form { + + } + #registration-content form { + .field { + margin: 0; + } + .group-form { + margin: 0; + padding-top: 0; + padding-bottom: 0px; + } + } } } @@ -1171,5 +1263,47 @@ input[name="subject"] { float: right; } } + span.code_tip { + background: none repeat scroll 0 0 #F8F4EC; + border-bottom: 1px solid #DDDDDD; + border-top: 1px solid #DDDDDD; + color: #3C3C3C; + display: block; + line-height: 30px; + margin-bottom: 6px; + padding: 10px 15px 10px 20px; + .add{ + @include button(simple, $blue); + @extend .button-reset; + font-size: em(13); + float: right; + } + } + span.csv_tip { + display: block; + line-height: 30px; + margin-bottom: 6px; + padding: 10px 15px 10px 1px; + .add{ + font-size: em(13); + float: right; + } + } + span.invalid_sale { + background: none repeat scroll 0 0 #F8F4EC; + color: #3C3C3C; + display: block; + line-height: 30px; + height: 37px; + margin-bottom: 6px; + padding: 10px 15px 10px 1px; + .add{ + @include button(simple, $blue); + @extend .button-reset; + font-size: em(13); + + } + } } + diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index a2a2f1a90b..9569ea13ca 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -1108,4 +1108,61 @@ @extend %t-copy; } } -} + p.course-block{ + border-style: solid; + border-color: #E3DC86; + padding: 5px; + border-width: 1px; + background: #FDFBE4; + + } + .enter-course-blocked{ + + @include box-sizing(border-box); + display: block; + float: left; + font: normal 15px/1.6rem $sans-serif; + letter-spacing: 0; + padding: 6px 32px 7px; + text-align: center; + margin-top: 16px; + opacity:0.5; + background:#808080; + border:0; + color:white; + box-shadow:none; + &.archived { + @include button(simple, $button-archive-color); + font: normal 15px/1.6rem $sans-serif; + padding: 6px 32px 7px; + + &:hover, &:focus { + text-decoration: none; + } + } + } + a.disable-look{ + color: #808080; + } + a.disable-look-unregister{ + color: #808080; + float: right; + display: block; + font-style: italic; + color: $lighter-base-font-color; + text-decoration: underline; + font-size: .8em; + margin-top: 32px; + + } + a.disable-look-settings{ + @extend a.disable-look-unregister; + margin-right: 10px; + } + + } + a.fade-cover{ + @extend .cover; + opacity: 0.5; + } + diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 8099775f95..3509fe0f6c 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -306,7 +306,8 @@ <% course_mode_info = all_course_modes.get(course.id) %> <% show_refund_option = (course.id in show_refund_option_for) %> <% is_paid_course = (course.id in enrolled_courses_either_paid) %> - <%include file='dashboard/_dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option = show_refund_option, is_paid_course = is_paid_course" /> + <% is_course_blocked = (course.id in block_courses) %> + <%include file='dashboard/_dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option = show_refund_option, is_paid_course = is_paid_course, is_course_blocked = is_course_blocked" /> % endfor @@ -521,3 +522,13 @@ + + + diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index a5aff31c18..fd4dd77005 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -1,4 +1,4 @@ -<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings, course_mode_info, show_refund_option, is_paid_course" /> +<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings, course_mode_info, show_refund_option, is_paid_course, is_course_blocked" /> <%! from django.utils.translation import ugettext as _ %> <%! @@ -31,9 +31,15 @@ %> % if show_courseware_link: - + % if not is_course_blocked: + ${_('{course_number} {course_name} Cover Image').format(course_number=course.number, course_name=course.display_name_with_default) |h} + % else: + + ${_('{course_number} {course_name} Cover Image').format(course_number=course.number, course_name=course.display_name_with_default) |h} + + % endif % else:
${_('{course_number} {course_name} Cover Image').format(course_number=course.number, course_name=course.display_name_with_default) | h} @@ -80,7 +86,11 @@

${get_course_about_section(course, 'university')}

% if show_courseware_link: + % if not is_course_blocked: ${course.display_number_with_default | h} ${course.display_name_with_default} + % else: + ${course.display_number_with_default | h} ${course.display_name_with_default} + % endif % else: ${course.display_number_with_default | h} ${course.display_name_with_default} % endif @@ -91,7 +101,7 @@ <%include file='_dashboard_certificate_information.html' args='cert_status=cert_status,course=course, enrollment=enrollment'/> % endif - % if course_mode_info['show_upsell']: + % if course_mode_info['show_upsell'] and not is_course_blocked: %endif + % if is_course_blocked: +

+ ${_('You can no longer access this course because payment has not yet been received. you can contact the account holder to request payment, or you can')} + ${_('unregister')} + ${_('for this course.')} +

+ %endif % if show_courseware_link: % if course.has_ended(): + % if not is_course_blocked: ${_('View Archived Course')} % else: + ${_('View Archived Course')} + % endif + % else: + % if not is_course_blocked: ${_('View Course')} + % else: + ${_('View Course')} + % endif % endif % endif % if is_paid_course and show_refund_option: ## Translators: The course's name will be added to the end of this sentence. + % if not is_course_blocked: ${_('Unregister')} + % else: + + ${_('Unregister')} + + % endif % elif is_paid_course and not show_refund_option: ## Translators: The course's name will be added to the end of this sentence. + % if not is_course_blocked: ${_('Unregister')} + % else: + + ${_('Unregister')} + + % endif % elif enrollment.mode != "verified": ## Translators: The course's name will be added to the end of this sentence. + % if not is_course_blocked: ${_('Unregister')} + % else: + + ${_('Unregister')} + + % endif % elif show_refund_option: ## Translators: The course's name will be added to the end of this sentence. + % if not is_course_blocked: ${_('Unregister')} + % else: + + ${_('Unregister')} + + % endif % else: ## Translators: The course's name will be added to the end of this sentence. + % if not is_course_blocked: ${_('Unregister')} + % else: + + ${_('Unregister')} + + % endif % endif % if show_email_settings: + % if not is_course_blocked: + % else: + ${_('Email Settings')} + % endif % endif + + \ No newline at end of file diff --git a/lms/templates/emails/registration_codes_sale_invoice.txt b/lms/templates/emails/registration_codes_sale_invoice.txt new file mode 100644 index 0000000000..f0b91b24a3 --- /dev/null +++ b/lms/templates/emails/registration_codes_sale_invoice.txt @@ -0,0 +1,49 @@ +<%! from django.utils.translation import ugettext as _ %> + +${_("Professional Education Invoicing Center")} + +${_("Portland Street, ")} +${_("9th floor Cambridge, MA,")} +${_("02139 finance@edx.org")} + +${_("---------------------------------")} + +${_("Bill To:")} + +${_("{company_name}").format(company_name=invoice.company_name)} + +${_("{purchaser_name}").format(purchaser_name=invoice.company_contact_name)} + +% if invoice.customer_reference_number: +${_("Customer Reference Number: {customer_reference_number}").format(customer_reference_number=invoice.customer_reference_number)} +%endif + +${_("---------------------------------")} + +${_("Registration codes:")} + +${_("Please distribute one of the following registration codes to each student who is going to enroll in the class.")} +${_("A student can redeem one of these codes by registering an account on ({site_name}.edx.org), starting the purchase").format(site_name=site_name)} +${_("process on this course by adding it to their shopping cart from this page ({course_url}), and").format(course_url=course_url)} +${_("entering their registration code while in the shopping cart.")} + +%for registration_code in registration_codes: +${registration_code.code} +%endfor + +${_("---------------------------------")} + +${_("Thank you for your purchase! Failure to pay this invoice will result the invalidation of student enrollments")} +${_("that use these codes. All purchases are final. Please refer to the cancellation policy")} +${_("on {site_name} for more information.").format(site_name=site_name)} + +${_("---------------------------------")} + +${_("Course: {course_name} {start_date} - {end_date}").format(course_name=course.display_name, start_date=course.start_date_text, end_date=course.end_date_text)} +${_("Price: ${price}").format(price=course_price)} +${_("Quantity: ${quantity}").format(quantity=quantity)} +${_("Sub-Total: ${sub_total}").format(sub_total=quantity*course_price)} +${_("Discount: ${discount}").format(discount=discount_price)} + +${_("--------- ------")} +${_("Total Due: ${total_price}").format(total_price=sale_price)} diff --git a/lms/templates/instructor/instructor_dashboard_2/e-commerce.html b/lms/templates/instructor/instructor_dashboard_2/e-commerce.html index 4b9ed2ecfe..055256f68b 100644 --- a/lms/templates/instructor/instructor_dashboard_2/e-commerce.html +++ b/lms/templates/instructor/instructor_dashboard_2/e-commerce.html @@ -3,26 +3,21 @@ <%include file="add_coupon_modal.html" args="section_data=section_data" /> <%include file="edit_coupon_modal.html" args="section_data=section_data" /> <%include file="set_course_mode_price_modal.html" args="section_data=section_data" /> +<%include file="generate_registarion_codes_modal.html" args="section_data=section_data" />
-

+

Registration Codes

-

Enter the transaction group name and number of registration codes that you want to generate. Click to generate a CSV :

-

-

- - - - -
-

+ Click to generate Registration Codes + Generate Registration Codes +

Click to generate a CSV file of all Course Registrations Codes:

- +

@@ -30,7 +25,7 @@

- +

@@ -38,7 +33,7 @@

- +

@@ -63,16 +58,37 @@ %if section_data['total_amount'] is not None: ${_("Total Amount: ")}$${section_data['total_amount']} %endif -

${_("Click to generate a CSV file for all purchase transactions in this course")}

-

+ + ${_("Click to generate a CSV file for all purchase transactions in this course")} + +
-
- +
+
+

${_("Sales")}

+
+ ${_("Click to generate a CSV file for all sales records in this course")} +

+
+
+

${_("Enter the invoice number to invalidate or re-validate sale")}

+ + + + + + +
+
%endif

${_("Coupons List")}

-

+ + ${_("Click to generate a CSV file of all Coupon Codes:")} + + + ${_("Coupons Information")} ${_("+ Add Coupon")}
@@ -110,6 +126,7 @@ + %endif
@@ -119,8 +136,8 @@
diff --git a/lms/templates/instructor/instructor_dashboard_2/edit_coupon_modal.html b/lms/templates/instructor/instructor_dashboard_2/edit_coupon_modal.html index 11bd44a2a1..d8ba96d99a 100644 --- a/lms/templates/instructor/instructor_dashboard_2/edit_coupon_modal.html +++ b/lms/templates/instructor/instructor_dashboard_2/edit_coupon_modal.html @@ -29,13 +29,13 @@
  1. - +
  2. - +
  3. diff --git a/lms/templates/instructor/instructor_dashboard_2/generate_registarion_codes_modal.html b/lms/templates/instructor/instructor_dashboard_2/generate_registarion_codes_modal.html new file mode 100644 index 0000000000..2e0662cd7e --- /dev/null +++ b/lms/templates/instructor/instructor_dashboard_2/generate_registarion_codes_modal.html @@ -0,0 +1,152 @@ +<%! from django.utils.translation import ugettext as _ %> +<%! from django.core.urlresolvers import reverse %> +<%page args="section_data"/> +