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:
+
+ % else:
+
+
+
+ % endif
% else:
@@ -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:
${_('Email Settings')}
+ % 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")}
+
+
-
-
+
+
%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 @@
${_("Code")}
-
+
${_("Percentage Discount")}
-
+
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"/>
+
+
+
+
+
+ ## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
+ ${_('Close')}
+
+
+
+
+
+ ${_("Generate Registration Codes")}
+
+
+
+
+ ${_("Please enter the details below")}
+
+
+
+
+
+