Commit c5fc9b2f by chrisndodge

Merge pull request #4771 from edx/cdodge/ecommerce-invoicing

Cdodge/ecommerce invoicing
parents 1e2d5ffe 2cc04338
...@@ -15,6 +15,8 @@ from xmodule.error_module import ErrorDescriptor ...@@ -15,6 +15,8 @@ from xmodule.error_module import ErrorDescriptor
from django.test.client import Client from django.test.client import Client
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.views import get_course_enrollment_pairs from student.views import get_course_enrollment_pairs
import unittest
from django.conf import settings
class TestCourseListing(ModuleStoreTestCase): class TestCourseListing(ModuleStoreTestCase):
...@@ -54,6 +56,7 @@ class TestCourseListing(ModuleStoreTestCase): ...@@ -54,6 +56,7 @@ class TestCourseListing(ModuleStoreTestCase):
self.client.logout() self.client.logout()
super(TestCourseListing, self).tearDown() super(TestCourseListing, self).tearDown()
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_get_course_list(self): def test_get_course_list(self):
""" """
Test getting courses Test getting courses
......
...@@ -32,6 +32,7 @@ from student.tests.factories import UserFactory, CourseModeFactory ...@@ -32,6 +32,7 @@ from student.tests.factories import UserFactory, CourseModeFactory
from certificates.models import CertificateStatuses from certificates.models import CertificateStatuses
from certificates.tests.factories import GeneratedCertificateFactory from certificates.tests.factories import GeneratedCertificateFactory
import shoppingcart import shoppingcart
from bulk_email.models import Optout
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -266,6 +267,50 @@ class DashboardTest(TestCase): ...@@ -266,6 +267,50 @@ class DashboardTest(TestCase):
self.assertFalse(enrollment.refundable()) self.assertFalse(enrollment.refundable())
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @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): def test_refundable_of_purchased_course(self):
self.client.login(username="jack", password="test") self.client.login(username="jack", password="test")
...@@ -315,6 +360,7 @@ class EnrollInCourseTest(TestCase): ...@@ -315,6 +360,7 @@ class EnrollInCourseTest(TestCase):
self.mock_tracker = patcher.start() self.mock_tracker = patcher.start()
self.addCleanup(patcher.stop) self.addCleanup(patcher.stop)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_enrollment(self): def test_enrollment(self):
user = User.objects.create_user("joe", "joe@joe.com", "password") user = User.objects.create_user("joe", "joe@joe.com", "password")
course_id = SlashSeparatedCourseKey("edX", "Test101", "2013") course_id = SlashSeparatedCourseKey("edX", "Test101", "2013")
...@@ -419,6 +465,7 @@ class EnrollInCourseTest(TestCase): ...@@ -419,6 +465,7 @@ class EnrollInCourseTest(TestCase):
self.assertTrue(CourseEnrollment.is_enrolled(user, course_id)) self.assertTrue(CourseEnrollment.is_enrolled(user, course_id))
self.assert_enrollment_event_was_emitted(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): def test_enrollment_by_email(self):
user = User.objects.create(username="jack", email="jack@fake.edx.org") user = User.objects.create(username="jack", email="jack@fake.edx.org")
course_id = SlashSeparatedCourseKey("edX", "Test101", "2013") course_id = SlashSeparatedCourseKey("edX", "Test101", "2013")
...@@ -456,6 +503,7 @@ class EnrollInCourseTest(TestCase): ...@@ -456,6 +503,7 @@ class EnrollInCourseTest(TestCase):
CourseEnrollment.unenroll_by_email("not_jack@fake.edx.org", course_id) CourseEnrollment.unenroll_by_email("not_jack@fake.edx.org", course_id)
self.assert_no_events_were_emitted() self.assert_no_events_were_emitted()
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_enrollment_multiple_classes(self): def test_enrollment_multiple_classes(self):
user = User(username="rusty", email="rusty@fake.edx.org") user = User(username="rusty", email="rusty@fake.edx.org")
course_id1 = SlashSeparatedCourseKey("edX", "Test101", "2013") course_id1 = SlashSeparatedCourseKey("edX", "Test101", "2013")
...@@ -478,6 +526,7 @@ class EnrollInCourseTest(TestCase): ...@@ -478,6 +526,7 @@ class EnrollInCourseTest(TestCase):
self.assertFalse(CourseEnrollment.is_enrolled(user, course_id1)) self.assertFalse(CourseEnrollment.is_enrolled(user, course_id1))
self.assertFalse(CourseEnrollment.is_enrolled(user, course_id2)) self.assertFalse(CourseEnrollment.is_enrolled(user, course_id2))
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_activation(self): def test_activation(self):
user = User.objects.create(username="jack", email="jack@fake.edx.org") user = User.objects.create(username="jack", email="jack@fake.edx.org")
course_id = SlashSeparatedCourseKey("edX", "Test101", "2013") course_id = SlashSeparatedCourseKey("edX", "Test101", "2013")
......
...@@ -92,6 +92,7 @@ from util.password_policy_validators import ( ...@@ -92,6 +92,7 @@ from util.password_policy_validators import (
from third_party_auth import pipeline, provider from third_party_auth import pipeline, provider
from xmodule.error_module import ErrorDescriptor from xmodule.error_module import ErrorDescriptor
from shoppingcart.models import CourseRegistrationCode
import analytics import analytics
from eventtracking import tracker from eventtracking import tracker
...@@ -431,6 +432,20 @@ def complete_course_mode_info(course_id, enrollment): ...@@ -431,6 +432,20 @@ def complete_course_mode_info(course_id, enrollment):
return mode_info 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 @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
def dashboard(request): def dashboard(request):
...@@ -493,6 +508,10 @@ def dashboard(request): ...@@ -493,6 +508,10 @@ def dashboard(request):
show_refund_option_for = frozenset(course.id for course, _enrollment in course_enrollment_pairs show_refund_option_for = frozenset(course.id for course, _enrollment in course_enrollment_pairs
if _enrollment.refundable()) 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 enrolled_courses_either_paid = frozenset(course.id for course, _enrollment in course_enrollment_pairs
if _enrollment.is_paid_course()) if _enrollment.is_paid_course())
# get info w.r.t ExternalAuthMap # get info w.r.t ExternalAuthMap
...@@ -544,6 +563,7 @@ def dashboard(request): ...@@ -544,6 +563,7 @@ def dashboard(request):
'verification_status': verification_status, 'verification_status': verification_status,
'verification_msg': verification_msg, 'verification_msg': verification_msg,
'show_refund_option_for': show_refund_option_for, 'show_refund_option_for': show_refund_option_for,
'block_courses': block_courses,
'denied_banner': denied_banner, 'denied_banner': denied_banner,
'billing_email': settings.PAYMENT_SUPPORT_EMAIL, 'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
'language_options': language_options, 'language_options': language_options,
......
...@@ -46,6 +46,7 @@ from xmodule.modulestore.search import path_to_location ...@@ -46,6 +46,7 @@ from xmodule.modulestore.search import path_to_location
from xmodule.tabs import CourseTabList, StaffGradingTab, PeerGradingTab, OpenEndedGradingTab from xmodule.tabs import CourseTabList, StaffGradingTab, PeerGradingTab, OpenEndedGradingTab
from xmodule.x_module import STUDENT_VIEW from xmodule.x_module import STUDENT_VIEW
import shoppingcart import shoppingcart
from shoppingcart.models import CourseRegistrationCode
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from microsite_configuration import microsite from microsite_configuration import microsite
...@@ -291,6 +292,14 @@ def index(request, course_id, chapter=None, section=None, ...@@ -291,6 +292,14 @@ def index(request, course_id, chapter=None, section=None,
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
user = User.objects.prefetch_related("groups").get(id=request.user.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 request.user = user # keep just one instance of User
course = get_course_with_access(user, 'load', course_key, depth=2) course = get_course_with_access(user, 'load', course_key, depth=2)
staff_access = has_access(user, 'staff', course) staff_access = has_access(user, 'staff', course)
......
...@@ -44,12 +44,17 @@ import instructor.views.api ...@@ -44,12 +44,17 @@ import instructor.views.api
from instructor.views.api import _split_input_list, common_exceptions_400 from instructor.views.api import _split_input_list, common_exceptions_400
from instructor_task.api_helper import AlreadyRunningError from instructor_task.api_helper import AlreadyRunningError
from opaque_keys.edx.locations import SlashSeparatedCourseKey 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 course_modes.models import CourseMode
from .test_tools import msk_from_problem_urlname from .test_tools import msk_from_problem_urlname
from ..views.tools import get_extended_due 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 @common_exceptions_400
def view_success(request): # pylint: disable=W0613 def view_success(request): # pylint: disable=W0613
...@@ -73,6 +78,7 @@ class TestCommonExceptions400(unittest.TestCase): ...@@ -73,6 +78,7 @@ class TestCommonExceptions400(unittest.TestCase):
""" """
Testing the common_exceptions_400 decorator. Testing the common_exceptions_400 decorator.
""" """
def setUp(self): def setUp(self):
self.request = Mock(spec=HttpRequest) self.request = Mock(spec=HttpRequest)
self.request.META = {} self.request.META = {}
...@@ -114,6 +120,7 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -114,6 +120,7 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
Ensure that users cannot access endpoints they shouldn't be able to. Ensure that users cannot access endpoints they shouldn't be able to.
""" """
def setUp(self): def setUp(self):
self.course = CourseFactory.create() self.course = CourseFactory.create()
self.user = UserFactory.create() self.user = UserFactory.create()
...@@ -138,8 +145,10 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -138,8 +145,10 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
('get_students_features', {}), ('get_students_features', {}),
('get_distribution', {}), ('get_distribution', {}),
('get_student_progress_url', {'unique_student_identifier': self.user.username}), ('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}), ('reset_student_attempts',
('update_forum_role_membership', {'unique_student_identifier': self.user.email, 'rolename': 'Moderator', 'action': 'allow'}), {'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}), ('list_forum_members', {'rolename': FORUM_ROLE_COMMUNITY_TA}),
('proxy_legacy_analytics', {'aname': 'ProblemGradeDistribution'}), ('proxy_legacy_analytics', {'aname': 'ProblemGradeDistribution'}),
('send_email', {'send_to': 'staff', 'subject': 'test', 'message': 'asdf'}), ('send_email', {'send_to': 'staff', 'subject': 'test', 'message': 'asdf'}),
...@@ -153,7 +162,8 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -153,7 +162,8 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
('bulk_beta_modify_access', {'identifiers': 'foo@example.org', 'action': 'add'}), ('bulk_beta_modify_access', {'identifiers': 'foo@example.org', 'action': 'add'}),
('modify_access', {'unique_student_identifier': self.user.email, 'rolename': 'beta', 'action': 'allow'}), ('modify_access', {'unique_student_identifier': self.user.email, 'rolename': 'beta', 'action': 'allow'}),
('list_course_role_members', {'rolename': 'beta'}), ('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): def _access_endpoint(self, endpoint, args, status_code, msg):
...@@ -267,6 +277,7 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -267,6 +277,7 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
This test does NOT exhaustively test state changes, that is the This test does NOT exhaustively test state changes, that is the
job of test_enrollment. This tests the response and action switch. job of test_enrollment. This tests the response and action switch.
""" """
def setUp(self): def setUp(self):
self.request = RequestFactory().request() self.request = RequestFactory().request()
self.course = CourseFactory.create() self.course = CourseFactory.create()
...@@ -278,7 +289,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -278,7 +289,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.enrolled_student, self.enrolled_student,
self.course.id 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 # Create invited, but not registered, user
cea = CourseEnrollmentAllowed(email='robot-allowed@robot.org', course_id=self.course.id) cea = CourseEnrollmentAllowed(email='robot-allowed@robot.org', course_id=self.course.id)
...@@ -537,7 +549,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -537,7 +549,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
@ddt.data('http', 'https') @ddt.data('http', 'https')
def test_enroll_with_email_not_registered_autoenroll(self, protocol): def test_enroll_with_email_not_registered_autoenroll(self, protocol):
url = reverse('students_update_enrollment', kwargs={'course_id': self.course.id.to_deprecated_string()}) 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} environ = {'wsgi.url_scheme': protocol}
response = self.client.post(url, params, **environ) response = self.client.post(url, params, **environ)
print "type(self.notregistered_email): {}".format(type(self.notregistered_email)) print "type(self.notregistered_email): {}".format(type(self.notregistered_email))
...@@ -746,7 +759,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -746,7 +759,8 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
mock_uses_shib.return_value = True mock_uses_shib.return_value = True
url = reverse('students_update_enrollment', kwargs={'course_id': self.course.id.to_deprecated_string()}) 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} environ = {'wsgi.url_scheme': protocol}
response = self.client.post(url, params, **environ) response = self.client.post(url, params, **environ)
print "type(self.notregistered_email): {}".format(type(self.notregistered_email)) print "type(self.notregistered_email): {}".format(type(self.notregistered_email))
...@@ -775,6 +789,7 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe ...@@ -775,6 +789,7 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe
""" """
Test bulk beta modify access endpoint. Test bulk beta modify access endpoint.
""" """
def setUp(self): def setUp(self):
self.course = CourseFactory.create() self.course = CourseFactory.create()
self.instructor = InstructorFactory(course_key=self.course.id) self.instructor = InstructorFactory(course_key=self.course.id)
...@@ -923,7 +938,8 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe ...@@ -923,7 +938,8 @@ class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTe
@ddt.data('http', 'https') @ddt.data('http', 'https')
def test_add_notenrolled_with_email_autoenroll(self, protocol): def test_add_notenrolled_with_email_autoenroll(self, protocol):
url = reverse('bulk_beta_modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) 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} environ = {'wsgi.url_scheme': protocol}
response = self.client.post(url, params, **environ) response = self.client.post(url, params, **environ)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -1089,6 +1105,7 @@ class TestInstructorAPILevelsAccess(ModuleStoreTestCase, LoginEnrollmentTestCase ...@@ -1089,6 +1105,7 @@ class TestInstructorAPILevelsAccess(ModuleStoreTestCase, LoginEnrollmentTestCase
Actually, modify_access does not have a very meaningful Actually, modify_access does not have a very meaningful
response yet, so only the status code is tested. response yet, so only the status code is tested.
""" """
def setUp(self): def setUp(self):
self.course = CourseFactory.create() self.course = CourseFactory.create()
self.instructor = InstructorFactory(course_key=self.course.id) self.instructor = InstructorFactory(course_key=self.course.id)
...@@ -1331,6 +1348,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa ...@@ -1331,6 +1348,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
""" """
Test endpoints that show data without side effects. Test endpoints that show data without side effects.
""" """
def setUp(self): def setUp(self):
self.course = CourseFactory.create() self.course = CourseFactory.create()
self.course_mode = CourseMode(course_id=self.course.id, self.course_mode = CourseMode(course_id=self.course.id,
...@@ -1346,10 +1364,61 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa ...@@ -1346,10 +1364,61 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
percentage_discount=10, created_by=self.instructor, is_active=True) percentage_discount=10, created_by=self.instructor, is_active=True)
self.coupon.save() 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)] self.students = [UserFactory() for _ in xrange(6)]
for student in self.students: for student in self.students:
CourseEnrollment.enroll(student, self.course.id) 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): def test_get_ecommerce_purchase_features_csv(self):
""" """
Test that the response from get_purchase_transaction is in csv format. Test that the response from get_purchase_transaction is in csv format.
...@@ -1360,6 +1429,115 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa ...@@ -1360,6 +1429,115 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
response = self.client.get(url + '/csv', {}) response = self.client.get(url + '/csv', {})
self.assertEqual(response['Content-Type'], 'text/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): def test_get_ecommerce_purchase_features_with_coupon_info(self):
""" """
Test that some minimum of information is formatted Test that some minimum of information is formatted
...@@ -1588,6 +1766,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -1588,6 +1766,7 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
This test does NOT test whether the actions had an effect on the This test does NOT test whether the actions had an effect on the
database, that is the job of task tests and test_enrollment. database, that is the job of task tests and test_enrollment.
""" """
def setUp(self): def setUp(self):
self.course = CourseFactory.create() self.course = CourseFactory.create()
self.instructor = InstructorFactory(course_key=self.course.id) self.instructor = InstructorFactory(course_key=self.course.id)
...@@ -1726,6 +1905,7 @@ class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -1726,6 +1905,7 @@ class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase):
these endpoints are only accessible with courses that actually exist, these endpoints are only accessible with courses that actually exist,
only with valid email messages. only with valid email messages.
""" """
def setUp(self): def setUp(self):
self.course = CourseFactory.create() self.course = CourseFactory.create()
self.instructor = InstructorFactory(course_key=self.course.id) self.instructor = InstructorFactory(course_key=self.course.id)
...@@ -1967,6 +2147,7 @@ class TestInstructorEmailContentList(ModuleStoreTestCase, LoginEnrollmentTestCas ...@@ -1967,6 +2147,7 @@ class TestInstructorEmailContentList(ModuleStoreTestCase, LoginEnrollmentTestCas
""" """
Test the instructor email content history endpoint. Test the instructor email content history endpoint.
""" """
def setUp(self): def setUp(self):
self.course = CourseFactory.create() self.course = CourseFactory.create()
self.instructor = InstructorFactory(course_key=self.course.id) self.instructor = InstructorFactory(course_key=self.course.id)
...@@ -2105,12 +2286,14 @@ class TestInstructorAPIAnalyticsProxy(ModuleStoreTestCase, LoginEnrollmentTestCa ...@@ -2105,12 +2286,14 @@ class TestInstructorAPIAnalyticsProxy(ModuleStoreTestCase, LoginEnrollmentTestCa
class FakeProxyResponse(object): class FakeProxyResponse(object):
""" Fake successful requests response object. """ """ Fake successful requests response object. """
def __init__(self): def __init__(self):
self.status_code = requests.status_codes.codes.OK self.status_code = requests.status_codes.codes.OK
self.content = '{"test_content": "robot test content"}' self.content = '{"test_content": "robot test content"}'
class FakeBadProxyResponse(object): class FakeBadProxyResponse(object):
""" Fake strange-failed requests response object. """ """ Fake strange-failed requests response object. """
def __init__(self): def __init__(self):
self.status_code = 'notok.' self.status_code = 'notok.'
self.content = '{"test_content": "robot test content"}' self.content = '{"test_content": "robot test content"}'
...@@ -2182,19 +2365,25 @@ class TestInstructorAPIAnalyticsProxy(ModuleStoreTestCase, LoginEnrollmentTestCa ...@@ -2182,19 +2365,25 @@ class TestInstructorAPIAnalyticsProxy(ModuleStoreTestCase, LoginEnrollmentTestCa
class TestInstructorAPIHelpers(TestCase): class TestInstructorAPIHelpers(TestCase):
""" Test helpers for instructor.api """ """ Test helpers for instructor.api """
def test_split_input_list(self): def test_split_input_list(self):
strings = [] strings = []
lists = [] lists = []
strings.append("Lorem@ipsum.dolor, sit@amet.consectetur\nadipiscing@elit.Aenean\r convallis@at.lacus\r, ut@lacinia.Sed") strings.append(
lists.append(['Lorem@ipsum.dolor', 'sit@amet.consectetur', 'adipiscing@elit.Aenean', 'convallis@at.lacus', 'ut@lacinia.Sed']) "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): for (stng, lst) in zip(strings, lists):
self.assertEqual(_split_input_list(stng), lst) self.assertEqual(_split_input_list(stng), lst)
def test_split_input_list_unicode(self): 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('robot@robot.edu, robot2@robot.edu'),
self.assertEqual(_split_input_list(u'robot@robot.edu, robot2@robot.edu'), ['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(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) scary_unistuff = unichr(40960) + u'abcd' + unichr(1972)
self.assertEqual(_split_input_list(scary_unistuff), [scary_unistuff]) self.assertEqual(_split_input_list(scary_unistuff), [scary_unistuff])
...@@ -2383,7 +2572,7 @@ class TestDueDateExtensions(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -2383,7 +2572,7 @@ class TestDueDateExtensions(ModuleStoreTestCase, LoginEnrollmentTestCase):
u'Username': self.user1.username}], u'Username': self.user1.username}],
u'header': [u'Username', u'Full Name', u'Extended Due Date'], u'header': [u'Username', u'Full Name', u'Extended Due Date'],
u'title': u'Users with due date extensions for %s' % u'title': u'Users with due date extensions for %s' %
self.week1.display_name}) self.week1.display_name})
def test_show_student_extensions(self): def test_show_student_extensions(self):
self.test_change_due_date() self.test_change_due_date()
...@@ -2396,7 +2585,7 @@ class TestDueDateExtensions(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -2396,7 +2585,7 @@ class TestDueDateExtensions(ModuleStoreTestCase, LoginEnrollmentTestCase):
u'Unit': self.week1.display_name}], u'Unit': self.week1.display_name}],
u'header': [u'Unit', u'Extended Due Date'], u'header': [u'Unit', u'Extended Due Date'],
u'title': u'Due date extensions for %s (%s)' % ( 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) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
...@@ -2405,6 +2594,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ...@@ -2405,6 +2594,7 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
""" """
Test data dumps for E-commerce Course Registration Codes. Test data dumps for E-commerce Course Registration Codes.
""" """
def setUp(self): def setUp(self):
""" """
Fixtures. Fixtures.
...@@ -2413,14 +2603,19 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ...@@ -2413,14 +2603,19 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
self.instructor = InstructorFactory(course_key=self.course.id) self.instructor = InstructorFactory(course_key=self.course.id)
self.client.login(username=self.instructor.username, password='test') self.client.login(username=self.instructor.username, password='test')
# Active Registration Codes url = reverse('generate_registration_codes',
for i in range(12): kwargs={'course_id': self.course.id.to_deprecated_string()})
course_registration_code = CourseRegistrationCode(
code='MyCode0{}'.format(i), course_id=self.course.id.to_deprecated_string(), data = {
transaction_group_name='Test Group', created_by=self.instructor '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',
course_registration_code.save() '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): for i in range(5):
order = Order(user=self.instructor, status='purchased') order = Order(user=self.instructor, status='purchased')
order.save() order.save()
...@@ -2433,6 +2628,48 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ...@@ -2433,6 +2628,48 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
) )
registration_code_redemption.save() 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): def test_generate_course_registration_codes_csv(self):
""" """
Test to generate a response of all the generated course registration codes Test to generate a response of all the generated course registration codes
...@@ -2440,16 +2677,23 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ...@@ -2440,16 +2677,23 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
url = reverse('generate_registration_codes', url = reverse('generate_registration_codes',
kwargs={'course_id': self.course.id.to_deprecated_string()}) 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.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Type'], 'text/csv')
body = response.content.replace('\r', '') 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) 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): def test_generate_course_registration_codes_matching_existing_coupon_code(self):
""" """
Test the generated course registration code is already in the Coupon Table Test the generated course registration code is already in the Coupon Table
...@@ -2459,16 +2703,23 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ...@@ -2459,16 +2703,23 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
coupon = Coupon(code='first', course_id=self.course.id.to_deprecated_string(), created_by=self.instructor) coupon = Coupon(code='first', course_id=self.course.id.to_deprecated_string(), created_by=self.instructor)
coupon.save() 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.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Type'], 'text/csv')
body = response.content.replace('\r', '') 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 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): def test_generate_course_registration_codes_integrity_error(self):
""" """
Test for the Integrity error against the generated code Test for the Integrity error against the generated code
...@@ -2476,13 +2727,19 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ...@@ -2476,13 +2727,19 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
url = reverse('generate_registration_codes', url = reverse('generate_registration_codes',
kwargs={'course_id': self.course.id.to_deprecated_string()}) 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.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Type'], 'text/csv')
body = response.content.replace('\r', '') 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) self.assertEqual(len(body.split('\n')), 4)
def test_spent_course_registration_codes_csv(self): def test_spent_course_registration_codes_csv(self):
...@@ -2492,20 +2749,30 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ...@@ -2492,20 +2749,30 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
url = reverse('spent_registration_codes', url = reverse('spent_registration_codes',
kwargs={'course_id': self.course.id.to_deprecated_string()}) kwargs={'course_id': self.course.id.to_deprecated_string()})
data = {'spent_transaction_group_name': ''} data = {'spent_company_name': ''}
response = self.client.post(url, data) response = self.client.post(url, data)
self.assertEqual(response.status_code, 200, response.content) self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Type'], 'text/csv')
body = response.content.replace('\r', '') 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) self.assertEqual(len(body.split('\n')), 7)
for i in range(9): generate_code_url = reverse(
course_registration_code = CourseRegistrationCode( 'generate_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()}
code='TestCode{}'.format(i), course_id=self.course.id.to_deprecated_string(), )
transaction_group_name='Group Alpha', created_by=self.instructor
) data = {
course_registration_code.save() '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): for i in range(9):
order = Order(user=self.instructor, status='purchased') order = Order(user=self.instructor, status='purchased')
...@@ -2519,12 +2786,12 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ...@@ -2519,12 +2786,12 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
) )
registration_code_redemption.save() registration_code_redemption.save()
data = {'spent_transaction_group_name': 'Group Alpha'} data = {'spent_company_name': 'Group Alpha'}
response = self.client.post(url, data) response = self.client.post(url, data)
self.assertEqual(response.status_code, 200, response.content) self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Type'], 'text/csv')
body = response.content.replace('\r', '') 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) self.assertEqual(len(body.split('\n')), 11)
def test_active_course_registration_codes_csv(self): def test_active_course_registration_codes_csv(self):
...@@ -2534,55 +2801,120 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase): ...@@ -2534,55 +2801,120 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
url = reverse('active_registration_codes', url = reverse('active_registration_codes',
kwargs={'course_id': self.course.id.to_deprecated_string()}) kwargs={'course_id': self.course.id.to_deprecated_string()})
data = {'active_transaction_group_name': ''} data = {'active_company_name': ''}
response = self.client.post(url, data) response = self.client.post(url, data)
self.assertEqual(response.status_code, 200, response.content) self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Type'], 'text/csv')
body = response.content.replace('\r', '') 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) self.assertEqual(len(body.split('\n')), 9)
for i in range(9): generate_code_url = reverse(
course_registration_code = CourseRegistrationCode( 'generate_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()}
code='TestCode{}'.format(i), course_id=self.course.id.to_deprecated_string(), )
transaction_group_name='Group Alpha', created_by=self.instructor
) data = {
course_registration_code.save() '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': ''
}
data = {'active_transaction_group_name': 'Group Alpha'} 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) response = self.client.post(url, data)
self.assertEqual(response.status_code, 200, response.content) self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Type'], 'text/csv')
body = response.content.replace('\r', '') 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) self.assertEqual(len(body.split('\n')), 11)
def test_get_all_course_registration_codes_csv(self): def test_get_all_course_registration_codes_csv(self):
""" """
Test to generate a response of all the course registration codes Test to generate a response of all the course registration codes
""" """
url = reverse('get_registration_codes', url = reverse(
kwargs={'course_id': self.course.id.to_deprecated_string()}) '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) response = self.client.post(url, data)
self.assertEqual(response.status_code, 200, response.content) self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Type'], 'text/csv')
body = response.content.replace('\r', '') 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) self.assertEqual(len(body.split('\n')), 14)
for i in range(9): generate_code_url = reverse(
course_registration_code = CourseRegistrationCode( 'generate_registration_codes', kwargs={'course_id': self.course.id.to_deprecated_string()}
code='TestCode{}'.format(i), course_id=self.course.id.to_deprecated_string(), )
transaction_group_name='Group Alpha', created_by=self.instructor
) data = {
course_registration_code.save() '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_transaction_group_name': 'Group Alpha'} data = {'download_company_name': 'Group Alpha'}
response = self.client.post(url, data) response = self.client.post(url, data)
self.assertEqual(response.status_code, 200, response.content) self.assertEqual(response.status_code, 200, response.content)
self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Type'], 'text/csv')
body = response.content.replace('\r', '') 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) 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))
...@@ -175,8 +175,7 @@ class TestECommerceDashboardViews(ModuleStoreTestCase): ...@@ -175,8 +175,7 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
self.assertTrue('Please Enter the Integer Value for Coupon Discount' in response.content) self.assertTrue('Please Enter the Integer Value for Coupon Discount' in response.content)
course_registration = CourseRegistrationCode( course_registration = CourseRegistrationCode(
code='Vs23Ws4j', course_id=self.course.id.to_deprecated_string(), code='Vs23Ws4j', course_id=self.course.id.to_deprecated_string(), created_by=self.instructor
transaction_group_name='Test Group', created_by=self.instructor
) )
course_registration.save() course_registration.save()
...@@ -254,7 +253,7 @@ class TestECommerceDashboardViews(ModuleStoreTestCase): ...@@ -254,7 +253,7 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
response = self.client.post(self.url) response = self.client.post(self.url)
self.assertTrue('<td>AS452</td>' in response.content) self.assertTrue('<td>AS452</td>' in response.content)
data = { 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() 'course_id': coupon.course_id.to_deprecated_string()
} }
# URL for update_coupon # URL for update_coupon
...@@ -263,43 +262,12 @@ class TestECommerceDashboardViews(ModuleStoreTestCase): ...@@ -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) 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) response = self.client.post(self.url)
self.assertTrue('<td>update_code</td>' in response.content) self.assertTrue('<td>updated_description</td>' in response.content)
self.assertTrue('<td>12</td>' in response.content)
data['coupon_id'] = 1000 # Coupon Not Exist with this ID data['coupon_id'] = 1000 # Coupon Not Exist with this ID
response = self.client.post(update_coupon_url, data=data) 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) 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 data['coupon_id'] = '' # Coupon id is not provided
response = self.client.post(update_coupon_url, data=data) response = self.client.post(update_coupon_url, data=data)
self.assertTrue('coupon id not found' in response.content) 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)
"""
This is the UserPreference key for the user's recipient invoice copy
"""
INVOICE_KEY = 'pref-invoice-copy'
...@@ -5,27 +5,30 @@ JSON views which the instructor dashboard requests. ...@@ -5,27 +5,30 @@ JSON views which the instructor dashboard requests.
Many of these GETs may become PUTs in the future. Many of these GETs may become PUTs in the future.
""" """
from django.views.decorators.http import require_POST import StringIO
import json import json
import logging import logging
import re import re
import requests import requests
from django.conf import settings from django.conf import settings
from django_future.csrf import ensure_csrf_cookie 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.views.decorators.cache import cache_control
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.mail.message import EmailMessage
from django.db import IntegrityError from django.db import IntegrityError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.validators import validate_email from django.core.validators import validate_email
from django.utils.translation import ugettext as _ 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 from django.utils.html import strip_tags
import string # pylint: disable=W0402 import string # pylint: disable=W0402
import random import random
from util.json_request import JsonResponse from util.json_request import JsonResponse
from instructor.views.instructor_task_helpers import extract_email_features, extract_task_features 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.access import has_access
from courseware.courses import get_course_with_access, get_course_by_id from courseware.courses import get_course_with_access, get_course_by_id
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -36,9 +39,9 @@ from django_comment_common.models import ( ...@@ -36,9 +39,9 @@ from django_comment_common.models import (
FORUM_ROLE_MODERATOR, FORUM_ROLE_MODERATOR,
FORUM_ROLE_COMMUNITY_TA, 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 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 from student.models import CourseEnrollment, unique_id_for_user, anonymous_id_for_user
import instructor_task.api import instructor_task.api
from instructor_task.api_helper import AlreadyRunningError from instructor_task.api_helper import AlreadyRunningError
...@@ -56,6 +59,8 @@ import instructor_analytics.basic ...@@ -56,6 +59,8 @@ import instructor_analytics.basic
import instructor_analytics.distributions import instructor_analytics.distributions
import instructor_analytics.csvs import instructor_analytics.csvs
import csv 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 from submissions import api as sub_api # installed from the edx-submissions repository
...@@ -555,6 +560,97 @@ def get_grading_config(request, course_id): ...@@ -555,6 +560,97 @@ def get_grading_config(request, course_id):
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff') @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')
def get_purchase_transaction(request, course_id, csv=False): # pylint: disable=W0613, W0621 def get_purchase_transaction(request, course_id, csv=False): # pylint: disable=W0613, W0621
""" """
return the summary of all purchased transactions for a particular course return the summary of all purchased transactions for a particular course
...@@ -635,7 +731,24 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=W06 ...@@ -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) 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 recursive function that generate a new code every time and saves in the Course Registration Table
if validation check passes if validation check passes
...@@ -645,17 +758,16 @@ def save_registration_codes(request, course_id, generated_codes_list, group_name ...@@ -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 # check if the generated code is in the Coupon Table
matching_coupons = Coupon.objects.filter(code=code, is_active=True) matching_coupons = Coupon.objects.filter(code=code, is_active=True)
if matching_coupons: 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( course_registration = CourseRegistrationCode(
code=code, course_id=course_id.to_deprecated_string(), code=code, course_id=course_id.to_deprecated_string(), created_by=request.user, invoice=invoice
transaction_group_name=group_name, created_by=request.user
) )
try: try:
course_registration.save() course_registration.save()
generated_codes_list.append(course_registration) generated_codes_list.append(course_registration)
except IntegrityError: 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): 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): ...@@ -667,7 +779,10 @@ def registration_codes_csv(file_name, codes_list, csv_type=None):
:param csv_type: :param csv_type:
""" """
# csv headers # 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) 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) header, data_rows = instructor_analytics.csvs.format_dictlist(registration_codes, query_features)
...@@ -679,7 +794,11 @@ def random_code_generator(): ...@@ -679,7 +794,11 @@ def random_code_generator():
generate a random alphanumeric code of length defined in generate a random alphanumeric code of length defined in
REGISTRATION_CODE_LENGTH settings 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) code_length = getattr(settings, 'REGISTRATION_CODE_LENGTH', 8)
return string.join((random.choice(chars) for _ in range(code_length)), '') 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 ...@@ -695,11 +814,11 @@ def get_registration_codes(request, course_id): # pylint: disable=W0613
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
#filter all the course registration codes #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'] company_name = request.POST['download_company_name']
if group_name: if company_name:
registration_codes = registration_codes.filter(transaction_group_name=group_name) registration_codes = registration_codes.filter(invoice__company_name=company_name)
csv_type = 'download' csv_type = 'download'
return registration_codes_csv("Registration_Codes.csv", registration_codes, csv_type) return registration_codes_csv("Registration_Codes.csv", registration_codes, csv_type)
...@@ -715,17 +834,86 @@ def generate_registration_codes(request, course_id): ...@@ -715,17 +834,86 @@ def generate_registration_codes(request, course_id):
""" """
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course_registration_codes = [] course_registration_codes = []
invoice_copy = False
# covert the course registration code number into integer # covert the course registration code number into integer
try: try:
course_code_number = int(request.POST['course_registration_code_number']) course_code_number = int(request.POST['total_registration_codes'])
except ValueError: 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 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) return registration_codes_csv("Registration_Codes.csv", course_registration_codes)
...@@ -741,11 +929,11 @@ def active_registration_codes(request, course_id): # pylint: disable=W0613 ...@@ -741,11 +929,11 @@ def active_registration_codes(request, course_id): # pylint: disable=W0613
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
# find all the registration codes in this course # 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'] company_name = request.POST['active_company_name']
if group_name: if company_name:
registration_codes_list = registration_codes_list.filter(transaction_group_name=group_name) registration_codes_list = registration_codes_list.filter(invoice__company_name=company_name)
# find the redeemed registration codes if any exist in the db # 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)
if code_redemption_set.exists(): if code_redemption_set.exists():
...@@ -768,17 +956,21 @@ def spent_registration_codes(request, course_id): # pylint: disable=W0613 ...@@ -768,17 +956,21 @@ def spent_registration_codes(request, course_id): # pylint: disable=W0613
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
# find the redeemed registration codes if any exist in the db # 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 = [] spent_codes_list = []
if code_redemption_set.exists(): if code_redemption_set.exists():
redeemed_registration_codes = [code.registration_code.code for code in code_redemption_set] 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 # filter the Registration Codes by course id and the redeemed codes and
# you will get a list of all the spent(Redeemed) Registration Codes # 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'] company_name = request.POST['spent_company_name']
if group_name: if company_name:
spent_codes_list = spent_codes_list.filter(transaction_group_name=group_name) # pylint: disable=E1103 spent_codes_list = spent_codes_list.filter(invoice__company_name=company_name) # pylint: disable=E1103
csv_type = 'spent' csv_type = 'spent'
return registration_codes_csv("Spent_Registration_Codes.csv", spent_codes_list, csv_type) return registration_codes_csv("Spent_Registration_Codes.csv", spent_codes_list, csv_type)
...@@ -1371,6 +1563,20 @@ def proxy_legacy_analytics(request, course_id): ...@@ -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): def _display_unit(unit):
""" """
Gets string for displaying unit to user. Gets string for displaying unit to user.
......
...@@ -19,6 +19,12 @@ urlpatterns = patterns('', # nopep8 ...@@ -19,6 +19,12 @@ urlpatterns = patterns('', # nopep8
'instructor.views.api.get_students_features', name="get_students_features"), 'instructor.views.api.get_students_features', name="get_students_features"),
url(r'^get_purchase_transaction(?P<csv>/csv)?$', url(r'^get_purchase_transaction(?P<csv>/csv)?$',
'instructor.views.api.get_purchase_transaction', name="get_purchase_transaction"), '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>/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$', url(r'^get_anon_ids$',
'instructor.views.api.get_anon_ids', name="get_anon_ids"), 'instructor.views.api.get_anon_ids', name="get_anon_ids"),
url(r'^get_distribution$', url(r'^get_distribution$',
...@@ -68,6 +74,10 @@ urlpatterns = patterns('', # nopep8 ...@@ -68,6 +74,10 @@ urlpatterns = patterns('', # nopep8
url(r'spent_registration_codes$', url(r'spent_registration_codes$',
'instructor.views.api.spent_registration_codes', name="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 # spoc gradebook
url(r'^gradebook$', url(r'^gradebook$',
'instructor.views.api.spoc_gradebook', name='spoc_gradebook'), 'instructor.views.api.spoc_gradebook', name='spoc_gradebook'),
......
...@@ -97,31 +97,8 @@ def update_coupon(request, course_id): # pylint: disable=W0613 ...@@ -97,31 +97,8 @@ def update_coupon(request, course_id): # pylint: disable=W0613
except ObjectDoesNotExist: except ObjectDoesNotExist:
return HttpResponseNotFound(_("coupon with the coupon id ({coupon_id}) DoesNotExist").format(coupon_id=coupon_id)) 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') 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.description = description
coupon.course_id = course_id
coupon.percentage_discount = discount
coupon.save() coupon.save()
return HttpResponse(_("coupon with the coupon id ({coupon_id}) updated Successfully").format(coupon_id=coupon_id)) return HttpResponse(_("coupon with the coupon id ({coupon_id}) updated Successfully").format(coupon_id=coupon_id))
......
...@@ -156,15 +156,19 @@ def _section_e_commerce(course_key, access): ...@@ -156,15 +156,19 @@ def _section_e_commerce(course_key, access):
'course_id': course_key.to_deprecated_string(), 'course_id': course_key.to_deprecated_string(),
'ajax_remove_coupon_url': reverse('remove_coupon', kwargs={'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()}), '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_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()}), '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_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()}), '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()}), '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()}), '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()}), '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()}), '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()}), '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, 'coupons': coupons,
'total_amount': total_amount, 'total_amount': total_amount,
'course_price': course_price 'course_price': course_price
......
...@@ -3,7 +3,7 @@ Student and course analytics. ...@@ -3,7 +3,7 @@ Student and course analytics.
Serve miscellaneous course and student data 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 from django.contrib.auth.models import User
import xmodule.graders as xmgraders import xmodule.graders as xmgraders
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
...@@ -15,8 +15,58 @@ PROFILE_FEATURES = ('name', 'language', 'location', 'year_of_birth', 'gender', ...@@ -15,8 +15,58 @@ PROFILE_FEATURES = ('name', 'language', 'location', 'year_of_birth', 'gender',
ORDER_ITEM_FEATURES = ('list_price', 'unit_cost', 'order_id') ORDER_ITEM_FEATURES = ('list_price', 'unit_cost', 'order_id')
ORDER_FEATURES = ('purchase_time',) 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 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): def purchase_transactions(course_id, features):
...@@ -100,6 +150,36 @@ def enrolled_students_features(course_id, features): ...@@ -100,6 +150,36 @@ def enrolled_students_features(course_id, features):
return [extract_student(student, features) for student in students] 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): def course_registration_features(features, registration_codes, csv_type):
""" """
Return list of Course Registration Codes as dictionaries. Return list of Course Registration Codes as dictionaries.
...@@ -120,14 +200,24 @@ def course_registration_features(features, registration_codes, csv_type): ...@@ -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] 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 = 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 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 # 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. # codes csv. In the case of active and generated registration codes the redeemed_by value will be None.
# They have not been redeemed yet # They have not been redeemed yet
if csv_type is not None: if csv_type is not None:
try: 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: except ObjectDoesNotExist:
pass pass
......
...@@ -4,11 +4,17 @@ Tests for instructor.basic ...@@ -4,11 +4,17 @@ Tests for instructor.basic
from django.test import TestCase from django.test import TestCase
from student.models import CourseEnrollment from student.models import CourseEnrollment
from django.core.urlresolvers import reverse
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from opaque_keys.edx.locations import SlashSeparatedCourseKey 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): class TestAnalyticsBasic(TestCase):
...@@ -19,6 +25,7 @@ class TestAnalyticsBasic(TestCase): ...@@ -19,6 +25,7 @@ class TestAnalyticsBasic(TestCase):
self.users = tuple(UserFactory() for _ in xrange(30)) self.users = tuple(UserFactory() for _ in xrange(30))
self.ces = tuple(CourseEnrollment.enroll(user, self.course_key) self.ces = tuple(CourseEnrollment.enroll(user, self.course_key)
for user in self.users) for user in self.users)
self.instructor = InstructorFactory(course_key=self.course_key)
def test_enrolled_students_features_username(self): def test_enrolled_students_features_username(self):
self.assertIn('username', AVAILABLE_FEATURES) self.assertIn('username', AVAILABLE_FEATURES)
...@@ -44,20 +51,89 @@ class TestAnalyticsBasic(TestCase): ...@@ -44,20 +51,89 @@ class TestAnalyticsBasic(TestCase):
self.assertEqual(len(AVAILABLE_FEATURES), len(STUDENT_FEATURES + PROFILE_FEATURES)) self.assertEqual(len(AVAILABLE_FEATURES), len(STUDENT_FEATURES + PROFILE_FEATURES))
self.assertEqual(set(AVAILABLE_FEATURES), set(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): for i in range(5):
course_code = CourseRegistrationCode( course_code = CourseRegistrationCode(
code="test_code{}".format(i), course_id=self.course_key.to_deprecated_string(), code="test_code{}".format(i), course_id=self.course.id.to_deprecated_string(),
transaction_group_name='TestName', created_by=self.users[0] created_by=self.instructor, invoice=sale_invoice
) )
course_code.save() 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() order.save()
registration_code_redemption = RegistrationCodeRedemption( 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_code_redemption.save()
registration_codes = CourseRegistrationCode.objects.all() registration_codes = CourseRegistrationCode.objects.all()
...@@ -71,6 +147,32 @@ class TestAnalyticsBasic(TestCase): ...@@ -71,6 +147,32 @@ class TestAnalyticsBasic(TestCase):
[registration_code.course_id.to_deprecated_string() for registration_code in registration_codes] [registration_code.course_id.to_deprecated_string() for registration_code in registration_codes]
) )
self.assertIn( self.assertIn(
course_registration['transaction_group_name'], course_registration['company_name'],
[registration_code.transaction_group_name for registration_code in registration_codes] [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]
) )
# -*- 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
# -*- 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
# -*- 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
# -*- 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
# -*- 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
# -*- 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
...@@ -317,6 +317,31 @@ class OrderItem(models.Model): ...@@ -317,6 +317,31 @@ class OrderItem(models.Model):
return '' 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): class CourseRegistrationCode(models.Model):
""" """
This table contains registration codes This table contains registration codes
...@@ -324,9 +349,9 @@ class CourseRegistrationCode(models.Model): ...@@ -324,9 +349,9 @@ class CourseRegistrationCode(models.Model):
""" """
code = models.CharField(max_length=32, db_index=True, unique=True) code = models.CharField(max_length=32, db_index=True, unique=True)
course_id = CourseKeyField(max_length=255, db_index=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_by = models.ForeignKey(User, related_name='created_by_user')
created_at = models.DateTimeField(default=datetime.now(pytz.utc)) created_at = models.DateTimeField(default=datetime.now(pytz.utc))
invoice = models.ForeignKey(Invoice, null=True)
@classmethod @classmethod
@transaction.commit_on_success @transaction.commit_on_success
...@@ -574,7 +599,7 @@ class PaidCourseRegistration(OrderItem): ...@@ -574,7 +599,7 @@ class PaidCourseRegistration(OrderItem):
Generates instructions when the user has purchased a PaidCourseRegistration. Generates instructions when the user has purchased a PaidCourseRegistration.
Basically tells the user to visit the dashboard to see their new classes Basically tells the user to visit the dashboard to see their new classes
""" """
notification = (_('Please visit your <a href="{dashboard_link}">dashboard</a> to see your new enrollments.') notification = (_('Please visit your <a href="{dashboard_link}">dashboard</a> to see your new course.')
.format(dashboard_link=reverse('dashboard'))) .format(dashboard_link=reverse('dashboard')))
return self.pk_with_subclass, set([notification]) return self.pk_with_subclass, set([notification])
......
...@@ -96,8 +96,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): ...@@ -96,8 +96,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
""" """
add dummy registration code into models add dummy registration code into models
""" """
course_reg_code = CourseRegistrationCode(code=self.reg_code, course_id=course_key, course_reg_code = CourseRegistrationCode(code=self.reg_code, course_id=course_key, created_by=self.user)
transaction_group_name='A', created_by=self.user)
course_reg_code.save() course_reg_code.save()
def add_course_to_user_cart(self): def add_course_to_user_cart(self):
......
...@@ -10,19 +10,17 @@ class ECommerce ...@@ -10,19 +10,17 @@ class ECommerce
@$section.data 'wrapper', @ @$section.data 'wrapper', @
# gather elements # gather elements
@$list_purchase_csv_btn = @$section.find("input[name='list-purchase-transaction-csv']'") @$list_purchase_csv_btn = @$section.find("input[name='list-purchase-transaction-csv']'")
@$transaction_group_name = @$section.find("input[name='transaction_group_name']'") @$list_sale_csv_btn = @$section.find("input[name='list-sale-csv']'")
@$course_registration_number = @$section.find("input[name='course_registration_code_number']'") @$download_company_name = @$section.find("input[name='download_company_name']'")
@$download_transaction_group_name = @$section.find("input[name='download_transaction_group_name']'") @$active_company_name = @$section.find("input[name='active_company_name']'")
@$active_transaction_group_name = @$section.find("input[name='active_transaction_group_name']'") @$spent_company_name = @$section.find('input[name="spent_company_name"]')
@$spent_transaction_group_name = @$section.find('input[name="spent_transaction_group_name"]') @$download_coupon_codes = @$section.find('input[name="download-coupon-codes-csv"]')
@$generate_registration_code_form = @$section.find("form#course_codes_number")
@$download_registration_codes_form = @$section.find("form#download_registration_codes") @$download_registration_codes_form = @$section.find("form#download_registration_codes")
@$active_registration_codes_form = @$section.find("form#active_registration_codes") @$active_registration_codes_form = @$section.find("form#active_registration_codes")
@$spent_registration_codes_form = @$section.find("form#spent_registration_codes") @$spent_registration_codes_form = @$section.find("form#spent_registration_codes")
@$coupoon_error = @$section.find('#coupon-error') @$error_msg = @$section.find('#error-msg')
@$course_code_error = @$section.find('#code-error')
# attach click handlers # attach click handlers
# this handler binds to both the download # this handler binds to both the download
...@@ -31,47 +29,28 @@ class ECommerce ...@@ -31,47 +29,28 @@ class ECommerce
url = @$list_purchase_csv_btn.data 'endpoint' url = @$list_purchase_csv_btn.data 'endpoint'
url += '/csv' url += '/csv'
location.href = url 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) => @$download_registration_codes_form.submit (e) =>
@$course_code_error.attr('style', 'display: none') @$error_msg.attr('style', 'display: none')
@$coupoon_error.attr('style', 'display: none')
return true return true
@$active_registration_codes_form.submit (e) => @$active_registration_codes_form.submit (e) =>
@$course_code_error.attr('style', 'display: none') @$error_msg.attr('style', 'display: none')
@$coupoon_error.attr('style', 'display: none')
return true return true
@$spent_registration_codes_form.submit (e) => @$spent_registration_codes_form.submit (e) =>
@$course_code_error.attr('style', 'display: none') @$error_msg.attr('style', 'display: none')
@$coupoon_error.attr('style', 'display: none')
return true 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. # handler for when the section title is clicked.
onClickTitle: -> onClickTitle: ->
@clear_display() @clear_display()
...@@ -83,14 +62,10 @@ class ECommerce ...@@ -83,14 +62,10 @@ class ECommerce
onExit: -> @clear_display() onExit: -> @clear_display()
clear_display: -> clear_display: ->
@$course_code_error.attr('style', 'display: none') @$error_msg.attr('style', 'display: none')
@$coupoon_error.attr('style', 'display: none') @$download_company_name.val('')
@$course_registration_number.val('') @$active_company_name.val('')
@$transaction_group_name.val('') @$spent_company_name.val('')
@$download_transaction_group_name.val('')
@$active_transaction_group_name.val('')
@$spent_transaction_group_name.val('')
isInt = (n) -> return n % 1 == 0; isInt = (n) -> return n % 1 == 0;
# Clear any generated tables, warning messages, etc. # Clear any generated tables, warning messages, etc.
......
...@@ -831,27 +831,32 @@ input[name="subject"] { ...@@ -831,27 +831,32 @@ input[name="subject"] {
} }
#e-commerce{ #e-commerce{
.coupon-errors { input {
margin-bottom: 1em;
line-height: 1.3em;
}
.error-msgs {
background: #FFEEF5;color:#B72667;text-align: center;padding: 10px 0px; background: #FFEEF5;color:#B72667;text-align: center;padding: 10px 0px;
font-family: "Open Sans",Verdana,Geneva,sans-serif,sans-serif;font-size: 15px; font-family: "Open Sans",Verdana,Geneva,sans-serif,sans-serif;font-size: 15px;
border-bottom: 1px solid #B72667; border-bottom: 1px solid #B72667;
margin-bottom: 20px; margin-bottom: 20px;
display: none; 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{ .content{
padding: 0 !important; padding: 0 !important;
} }
input[name="course_registration_code_number"] { input[name="download_company_name"],
margin-right: 10px; input[name="active_company_name"], input[name="spent_company_name"] {
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"] {
margin-right: 8px; margin-right: 8px;
height: 36px; height: 36px;
width: 300px; width: 254px;
border-radius: 3px; border-radius: 3px;
} }
.coupons-table { .coupons-table {
...@@ -955,12 +960,21 @@ input[name="subject"] { ...@@ -955,12 +960,21 @@ input[name="subject"] {
} }
} }
} }
section#registration_code_generation_modal {
margin-left: -442px;
width: 930px;
}
// coupon edit and add modals // 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 { .inner-wrapper {
background: #fff; background: #fff;
} }
span.tip-text {
font-size: 12px;
display: block;
margin-top: 5px;
color: #646464
}
top:-95px !important; top:-95px !important;
width: 650px; width: 650px;
margin-left: -325px; margin-left: -325px;
...@@ -973,6 +987,10 @@ input[name="subject"] { ...@@ -973,6 +987,10 @@ input[name="subject"] {
@include button(simple, $blue); @include button(simple, $blue);
@extend .button-reset; @extend .button-reset;
} }
input[name="generate-registration-codes-csv"]{
@include button(simple, $blue);
@extend .button-reset;
}
input[type="submit"]#set_course_button{ input[type="submit"]#set_course_button{
@include button(simple, $blue); @include button(simple, $blue);
@extend .button-reset; @extend .button-reset;
...@@ -1014,6 +1032,61 @@ input[name="subject"] { ...@@ -1014,6 +1032,61 @@ input[name="subject"] {
margin-bottom: 0px !important; 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{ li#set-course-mode-modal-field-price{
width: 100%; width: 100%;
label.required:after { label.required:after {
...@@ -1027,7 +1100,13 @@ input[name="subject"] { ...@@ -1027,7 +1100,13 @@ input[name="subject"] {
width: 100%; 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; padding: 20px;
header { header {
margin: 0; margin: 0;
...@@ -1086,6 +1165,19 @@ input[name="subject"] { ...@@ -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"] { ...@@ -1171,5 +1263,47 @@ input[name="subject"] {
float: right; 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);
}
}
} }
...@@ -1108,4 +1108,61 @@ ...@@ -1108,4 +1108,61 @@
@extend %t-copy; @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;
}
...@@ -306,7 +306,8 @@ ...@@ -306,7 +306,8 @@
<% course_mode_info = all_course_modes.get(course.id) %> <% course_mode_info = all_course_modes.get(course.id) %>
<% show_refund_option = (course.id in show_refund_option_for) %> <% show_refund_option = (course.id in show_refund_option_for) %>
<% is_paid_course = (course.id in enrolled_courses_either_paid) %> <% 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 % endfor
</ul> </ul>
...@@ -521,3 +522,13 @@ ...@@ -521,3 +522,13 @@
</form> </form>
</div> </div>
</section> </section>
<script>
$(function() {
$("#unregister_block_course").click( function(event) {
$("#unenroll_course_id").val( $(event.target).data("course-id") );
$("#unenroll_course_number").text( $(event.target).data("course-number") );
});
});
</script>
<%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 _ %> <%! from django.utils.translation import ugettext as _ %>
<%! <%!
...@@ -31,9 +31,15 @@ ...@@ -31,9 +31,15 @@
%> %>
% if show_courseware_link: % if show_courseware_link:
<a href="${course_target}" class="cover"> % if not is_course_blocked:
<a href="${course_target}" class="cover">
<img src="${course_image_url(course)}" alt="${_('{course_number} {course_name} Cover Image').format(course_number=course.number, course_name=course.display_name_with_default) |h}" /> <img src="${course_image_url(course)}" alt="${_('{course_number} {course_name} Cover Image').format(course_number=course.number, course_name=course.display_name_with_default) |h}" />
</a> </a>
% else:
<a class="fade-cover">
<img src="${course_image_url(course)}" alt="${_('{course_number} {course_name} Cover Image').format(course_number=course.number, course_name=course.display_name_with_default) |h}" />
</a>
% endif
% else: % else:
<div class="cover"> <div class="cover">
<img src="${course_image_url(course)}" alt="${_('{course_number} {course_name} Cover Image').format(course_number=course.number, course_name=course.display_name_with_default) | h}" /> <img src="${course_image_url(course)}" alt="${_('{course_number} {course_name} Cover Image').format(course_number=course.number, course_name=course.display_name_with_default) | h}" />
...@@ -80,7 +86,11 @@ ...@@ -80,7 +86,11 @@
<h2 class="university">${get_course_about_section(course, 'university')}</h2> <h2 class="university">${get_course_about_section(course, 'university')}</h2>
<h3> <h3>
% if show_courseware_link: % if show_courseware_link:
% if not is_course_blocked:
<a href="${course_target}">${course.display_number_with_default | h} ${course.display_name_with_default}</a> <a href="${course_target}">${course.display_number_with_default | h} ${course.display_name_with_default}</a>
% else:
<a class="disable-look">${course.display_number_with_default | h} ${course.display_name_with_default}</a>
% endif
% else: % else:
<span>${course.display_number_with_default | h} ${course.display_name_with_default}</span> <span>${course.display_number_with_default | h} ${course.display_name_with_default}</span>
% endif % endif
...@@ -91,7 +101,7 @@ ...@@ -91,7 +101,7 @@
<%include file='_dashboard_certificate_information.html' args='cert_status=cert_status,course=course, enrollment=enrollment'/> <%include file='_dashboard_certificate_information.html' args='cert_status=cert_status,course=course, enrollment=enrollment'/>
% endif % endif
% if course_mode_info['show_upsell']: % if course_mode_info['show_upsell'] and not is_course_blocked:
<div class="message message-upsell has-actions is-expandable is-shown"> <div class="message message-upsell has-actions is-expandable is-shown">
<div class="wrapper-tip"> <div class="wrapper-tip">
...@@ -122,52 +132,116 @@ ...@@ -122,52 +132,116 @@
</div> </div>
</div> </div>
%endif %endif
% if is_course_blocked:
<p id="block-course-msg" class="course-block">
${_('You can no longer access this course because payment has not yet been received. you can <a href="#">contact the account holder</a> to request payment, or you can')}
<a id="unregister_block_course" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" href="#unenroll-modal" > ${_('unregister')} </a>
${_('for this course.')}
</p>
%endif
% if show_courseware_link: % if show_courseware_link:
% if course.has_ended(): % if course.has_ended():
% if not is_course_blocked:
<a href="${course_target}" class="enter-course archived">${_('View Archived Course')}</a> <a href="${course_target}" class="enter-course archived">${_('View Archived Course')}</a>
% else: % else:
<a class="enter-course-blocked archived">${_('View Archived Course')}</a>
% endif
% else:
% if not is_course_blocked:
<a href="${course_target}" class="enter-course">${_('View Course')}</a> <a href="${course_target}" class="enter-course">${_('View Course')}</a>
% else:
<a class="enter-course-blocked">${_('View Course')}</a>
% endif
% endif % endif
% endif % endif
% if is_paid_course and show_refund_option: % if is_paid_course and show_refund_option:
## Translators: The course's name will be added to the end of this sentence. ## Translators: The course's name will be added to the end of this sentence.
% if not is_course_blocked:
<a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the purchased course")}'; <a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the purchased course")}';
document.getElementById('refund-info').innerHTML=gettext('You will be refunded the amount you paid.')"> document.getElementById('refund-info').innerHTML=gettext('You will be refunded the amount you paid.')">
${_('Unregister')} ${_('Unregister')}
</a> </a>
% else:
<a class="disable-look-unregister" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the purchased course")}';
document.getElementById('refund-info').innerHTML=gettext('You will be refunded the amount you paid.')">
${_('Unregister')}
</a>
% endif
% elif is_paid_course and not show_refund_option: % elif is_paid_course and not show_refund_option:
## Translators: The course's name will be added to the end of this sentence. ## Translators: The course's name will be added to the end of this sentence.
% if not is_course_blocked:
<a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the purchased course")}'; <a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the purchased course")}';
document.getElementById('refund-info').innerHTML=gettext('You will not be refunded the amount you paid.')"> document.getElementById('refund-info').innerHTML=gettext('You will not be refunded the amount you paid.')">
${_('Unregister')} ${_('Unregister')}
</a> </a>
% else:
<a class="disable-look-unregister" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the purchased course")}';
document.getElementById('refund-info').innerHTML=gettext('You will not be refunded the amount you paid.')">
${_('Unregister')}
</a>
% endif
% elif enrollment.mode != "verified": % elif enrollment.mode != "verified":
## Translators: The course's name will be added to the end of this sentence. ## Translators: The course's name will be added to the end of this sentence.
% if not is_course_blocked:
<a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from")}'; document.getElementById('refund-info').innerHTML=''"> <a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from")}'; document.getElementById('refund-info').innerHTML=''">
${_('Unregister')} ${_('Unregister')}
</a> </a>
% else:
<a class="disable-look-unregister" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from")}'; document.getElementById('refund-info').innerHTML=''">
${_('Unregister')}
</a>
% endif
% elif show_refund_option: % elif show_refund_option:
## Translators: The course's name will be added to the end of this sentence. ## Translators: The course's name will be added to the end of this sentence.
% if not is_course_blocked:
<a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the verified {cert_name_long} track of").format(cert_name_long=cert_name_long)}'; <a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the verified {cert_name_long} track of").format(cert_name_long=cert_name_long)}';
document.getElementById('refund-info').innerHTML=gettext('You will be refunded the amount you paid.')"> document.getElementById('refund-info').innerHTML=gettext('You will be refunded the amount you paid.')">
${_('Unregister')} ${_('Unregister')}
</a> </a>
% else:
<a class="disable-look-unregister" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the verified {cert_name_long} track of").format(cert_name_long=cert_name_long)}';
document.getElementById('refund-info').innerHTML=gettext('You will be refunded the amount you paid.')">
${_('Unregister')}
</a>
% endif
% else: % else:
## Translators: The course's name will be added to the end of this sentence. ## Translators: The course's name will be added to the end of this sentence.
% if not is_course_blocked:
<a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the verified {cert_name_long} track of").format(cert_name_long=cert_name_long)}'; <a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the verified {cert_name_long} track of").format(cert_name_long=cert_name_long)}';
document.getElementById('refund-info').innerHTML=gettext('The refund deadline for this course has passed, so you will not receive a refund.')"> document.getElementById('refund-info').innerHTML=gettext('The refund deadline for this course has passed, so you will not receive a refund.')">
${_('Unregister')} ${_('Unregister')}
</a> </a>
% else:
<a class="disable-look-unregister" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" onclick="document.getElementById('track-info').innerHTML='${_("Are you sure you want to unregister from the verified {cert_name_long} track of").format(cert_name_long=cert_name_long)}';
document.getElementById('refund-info').innerHTML=gettext('The refund deadline for this course has passed, so you will not receive a refund.')">
${_('Unregister')}
</a>
% endif
% endif % endif
% if show_email_settings: % if show_email_settings:
% if not is_course_blocked:
<a href="#email-settings-modal" class="email-settings" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" data-optout="${course.id.to_deprecated_string() in course_optouts}">${_('Email Settings')}</a> <a href="#email-settings-modal" class="email-settings" rel="leanModal" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" data-optout="${course.id.to_deprecated_string() in course_optouts}">${_('Email Settings')}</a>
% else:
<a class="disable-look-settings" data-course-id="${course.id.to_deprecated_string()}" data-course-number="${course.number}" data-optout="${course.id.to_deprecated_string() in course_optouts}">${_('Email Settings')}</a>
% endif
% endif % endif
</section> </section>
</article> </article>
</article>
</li> </li>
<script>
$( document ).ready(function() {
if("${is_course_blocked}" == "True"){
$( "#unregister_block_course" ).click(function() {
$('.disable-look-unregister').click();
});
}
});
</script>
\ No newline at end of file
<%! 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)}
...@@ -3,26 +3,21 @@ ...@@ -3,26 +3,21 @@
<%include file="add_coupon_modal.html" args="section_data=section_data" /> <%include file="add_coupon_modal.html" args="section_data=section_data" />
<%include file="edit_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="set_course_mode_price_modal.html" args="section_data=section_data" />
<%include file="generate_registarion_codes_modal.html" args="section_data=section_data" />
<div class="ecommerce-wrapper"> <div class="ecommerce-wrapper">
<h3 class="coupon-errors" id="code-error"></h3> <h3 class="error-msgs" id="error-msg"></h3>
<div id = "accordion"> <div id = "accordion">
<div class="wrap"> <div class="wrap">
<h2>Registration Codes</h2> <h2>Registration Codes</h2>
<div> <div>
<p>Enter the transaction group name and number of registration codes that you want to generate. Click to generate a CSV :</p> <span class="code_tip">Click to generate Registration Codes
<p> <a id="registration_code_generation_link" href="#reg_code_generation_modal" class="add blue-button">Generate Registration Codes</a>
<form action="${ section_data['generate_registration_code_csv_url'] }" id="course_codes_number" method="post"> </span>
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
<input type="text" name="transaction_group_name" placeholder="Transaction Group Name"/>
<input type="text" name="course_registration_code_number" placeholder="Number of Registration Codes" maxlength="4"/>
<input type="submit" name="generate-registration-codes-csv" value="${_("Generate Registration Codes")}" data-csv="true">
</form>
</p>
<p>Click to generate a CSV file of all Course Registrations Codes:</p> <p>Click to generate a CSV file of all Course Registrations Codes:</p>
<p> <p>
<form action="${ section_data['get_registration_code_csv_url'] }" id="download_registration_codes" method="post"> <form action="${ section_data['get_registration_code_csv_url'] }" id="download_registration_codes" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }"> <input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
<input type="text" name="download_transaction_group_name" placeholder="Transaction Group Name (Optional)"/> <input type="text" name="download_company_name" placeholder="Company Name (Optional)"/>
<input type="submit" name="list-registration-codes-csv" value="${_("Download Registration Codes")}" data-csv="true"> <input type="submit" name="list-registration-codes-csv" value="${_("Download Registration Codes")}" data-csv="true">
</form> </form>
</p> </p>
...@@ -30,7 +25,7 @@ ...@@ -30,7 +25,7 @@
<p> <p>
<form action="${ section_data['active_registration_code_csv_url'] }" id="active_registration_codes" method="post"> <form action="${ section_data['active_registration_code_csv_url'] }" id="active_registration_codes" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }"> <input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
<input type="text" name="active_transaction_group_name" placeholder="Transaction Group Name (Optional)"/> <input type="text" name="active_company_name" placeholder="Company Name (Optional)"/>
<input type="submit" name="active-registration-codes-csv" value="${_("Active Registration Codes")}" data-csv="true"> <input type="submit" name="active-registration-codes-csv" value="${_("Active Registration Codes")}" data-csv="true">
</form> </form>
</p> </p>
...@@ -38,7 +33,7 @@ ...@@ -38,7 +33,7 @@
<p> <p>
<form action="${ section_data['spent_registration_code_csv_url'] }" id="spent_registration_codes" method="post"> <form action="${ section_data['spent_registration_code_csv_url'] }" id="spent_registration_codes" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }"> <input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
<input type="text" name="spent_transaction_group_name" placeholder="Transaction Group Name (Optional)"/> <input type="text" name="spent_company_name" placeholder="Company Name (Optional)"/>
<input type="submit" name="spent-registration-codes-csv" value="${_("Spent Registration Codes")}" data-csv="true"> <input type="submit" name="spent-registration-codes-csv" value="${_("Spent Registration Codes")}" data-csv="true">
</form> </form>
</p> </p>
...@@ -63,16 +58,37 @@ ...@@ -63,16 +58,37 @@
%if section_data['total_amount'] is not None: %if section_data['total_amount'] is not None:
<span>${_("Total Amount: ")}<span>$${section_data['total_amount']}</span></span> <span>${_("Total Amount: ")}<span>$${section_data['total_amount']}</span></span>
%endif %endif
<p>${_("Click to generate a CSV file for all purchase transactions in this course")}</p>
<p><input type="button" name="list-purchase-transaction-csv" value="${_("Download All e-Commerce Purchases")}" data-endpoint="${ section_data['get_purchase_transaction_url'] }" data-csv="true"></p> <span class="csv_tip">${_("Click to generate a CSV file for all purchase transactions in this course")}
<input class="add blue-button" type="button" name="list-purchase-transaction-csv" value="${_("Download All e-Commerce Purchases")}" data-endpoint="${ section_data['get_purchase_transaction_url'] }" data-csv="true">
</span>
</div> </div>
</div> </div><!-- end wrap -->
<!-- end wrap --> <div class="wrap">
<h2>${_("Sales")}</h2>
<div>
<span class="csv_tip">${_("Click to generate a CSV file for all sales records in this course")}
<input type="button" class="add blue-button" name="list-sale-csv" value="${_("Download All e-Commerce Sales")}" data-endpoint="${ section_data['get_sale_records_url'] }" data-csv="true"></p></td>
</span>
<hr>
<p>${_("Enter the invoice number to invalidate or re-validate sale")}</p>
<span class="invalid_sale">
<input type="number" id="invoice_number" placeholder= "${_("Enter Invoice Number")}"/>
<input type="button" class="add blue-button" id="invalidate_invoice" value="${_("Invalidate Sale")}">
<input type="button" class="add blue-button" id="re_validate_invoice" value="${_("Revalidate Sale")}">
</span>
</div>
</div><!-- end wrap -->
%endif %endif
<div class="wrap"> <div class="wrap">
<h2>${_("Coupons List")}</h2> <h2>${_("Coupons List")}</h2>
<div> <div>
<h3 class="coupon-errors" id="coupon-error"></h3>
<span class="csv_tip">${_("Click to generate a CSV file of all Coupon Codes:")}
<input class="add blue-button" type="button" name="download-coupon-codes-csv" value="${_("Download coupon codes")}" data-endpoint="${ section_data['download_coupon_codes_url'] }" data-csv="true">
</span>
<span class="tip">${_("Coupons Information")} <a id="add_coupon_link" href="#add-coupon-modal" rel="leanModal" <span class="tip">${_("Coupons Information")} <a id="add_coupon_link" href="#add-coupon-modal" rel="leanModal"
class="add blue-button">${_("+ Add Coupon")}</a></span> class="add blue-button">${_("+ Add Coupon")}</a></span>
<div class="wrapper-content wrapper"> <div class="wrapper-content wrapper">
...@@ -110,6 +126,7 @@ ...@@ -110,6 +126,7 @@
</tbody> </tbody>
</table> </table>
<a id="edit-modal-trigger" href="#edit-coupon-modal" rel="leanModal"></a> <a id="edit-modal-trigger" href="#edit-coupon-modal" rel="leanModal"></a>
<a id="registration_code_generation_link-trigger" href="#registration_code_generation_modal" rel="leanModal"></a>
%endif %endif
</section> </section>
</div> </div>
...@@ -119,8 +136,8 @@ ...@@ -119,8 +136,8 @@
</div> </div>
</div> </div>
<script> <script>
$(function () {
$(function () {
var icons = { var icons = {
header: "ui-icon-circle-arrow-e", header: "ui-icon-circle-arrow-e",
activeHeader: "ui-icon-circle-arrow-s" activeHeader: "ui-icon-circle-arrow-s"
...@@ -133,13 +150,17 @@ ...@@ -133,13 +150,17 @@
var active = jQuery("#accordion").accordion('option', 'active'); var active = jQuery("#accordion").accordion('option', 'active');
$.cookie('saved_index', null); $.cookie('saved_index', null);
$.cookie('saved_index', active); $.cookie('saved_index', active);
$('#error-msg').val('');
$("#error-msg").fadeOut(2000 , function(){
$('#error-msg').hide()
});
}, },
animate: 400, animate: 400,
header: "> div.wrap >h2", header: "> div.wrap >h2",
icons:icons, icons:icons,
active:isNaN(parseInt($.cookie('saved_index'))) ? 0 : parseInt($.cookie('saved_index')), active:isNaN(parseInt($.cookie('saved_index'))) ? 0 : parseInt($.cookie('saved_index')),
collapsible: true collapsible: true
}); });
$('a[rel*=leanModal]').leanModal(); $('a[rel*=leanModal]').leanModal();
$.each($("a.edit-right"), function () { $.each($("a.edit-right"), function () {
...@@ -152,6 +173,46 @@ ...@@ -152,6 +173,46 @@
$(this).removeAttr('href') $(this).removeAttr('href')
} }
}); });
$('#registration_code_generation_link').click(function(event){
event.preventDefault();
$.ajax({
type: "POST",
url: "${section_data['get_user_invoice_preference_url']}",
success: function (data) {
$('#invoice-copy').prop('checked', data.invoice_copy);
$('#registration_code_generation_link-trigger').click();
}
});
});
$('#invalidate_invoice, #re_validate_invoice').click(function (event) {
event.preventDefault();
var event_type = "re_validate"
if($(event.target).attr('id')=='invalidate_invoice'){
event_type = "invalidate"
}
if($('#invoice_number').val() == "") {
$('#error-msg').attr('class','error-msgs')
$('#error-msg').html("${_("Invoice number should not be empty.")}").show();
return
}
$.ajax({
type: "POST",
data: {invoice_number: $('#invoice_number').val(), event_type:event_type},
url: "${section_data['sale_validation_url']}",
success: function (data) {
$('#error-msg').attr('class','success-msgs')
$('#error-msg').html(data.message).show();
$('#invoice_number').val('');
},
error: function(jqXHR, textStatus, errorThrown) {
$('#error-msg').attr('class','error-msgs')
$('#error-msg').html(jqXHR.responseText).show();
}
});
});
$('a.edit-right').click(function (event) { $('a.edit-right').click(function (event) {
$('#edit_coupon_form #coupon_form_error').attr('style', 'display: none'); $('#edit_coupon_form #coupon_form_error').attr('style', 'display: none');
$('#edit_coupon_form #coupon_form_error').text(); $('#edit_coupon_form #coupon_form_error').text();
...@@ -167,8 +228,8 @@ ...@@ -167,8 +228,8 @@
data: {id: coupon_id}, data: {id: coupon_id},
url: "${section_data['ajax_get_coupon_info']}", url: "${section_data['ajax_get_coupon_info']}",
success: function (data) { success: function (data) {
$('#coupon-error').val(''); $('#error-msg').val('');
$('#coupon-error').attr('style', 'display: none'); $('#error-msg').hide()
$('input#edit_coupon_code').val(data.coupon_code); $('input#edit_coupon_code').val(data.coupon_code);
$('input#edit_coupon_discount').val(data.coupon_discount); $('input#edit_coupon_discount').val(data.coupon_discount);
$('textarea#edit_coupon_description').val(data.coupon_description); $('textarea#edit_coupon_description').val(data.coupon_description);
...@@ -177,7 +238,8 @@ ...@@ -177,7 +238,8 @@
}, },
error: function(jqXHR, textStatus, errorThrown) { error: function(jqXHR, textStatus, errorThrown) {
var data = $.parseJSON(jqXHR.responseText); var data = $.parseJSON(jqXHR.responseText);
$('#coupon-error').html(data.message).show(); $('#error-msg').attr('class','error-msgs')
$('#error-msg').html(data.message).show();
} }
}); });
}); });
...@@ -189,7 +251,7 @@ ...@@ -189,7 +251,7 @@
anchor.data("disabled", "disabled"); anchor.data("disabled", "disabled");
event.preventDefault(); event.preventDefault();
if ($(this).parent().parent('tr').hasClass('inactive_coupon')) { if ($(this).parent().parent('tr').hasClass('inactive_coupon')) {
return false; return false;
} }
$.ajax({ $.ajax({
type: "POST", type: "POST",
...@@ -201,42 +263,131 @@ ...@@ -201,42 +263,131 @@
}, },
error: function(jqXHR, textStatus, errorThrown) { error: function(jqXHR, textStatus, errorThrown) {
var data = $.parseJSON(jqXHR.responseText); var data = $.parseJSON(jqXHR.responseText);
$('#coupon-error').html(data.message).show(); $('#error-msg').attr('class','error-msgs')
$('#error-msg').html(data.message).show();
anchor.removeData("disabled"); anchor.removeData("disabled");
} }
}); });
}); });
var generate_registration_code_form = $("form#generate_codes");
var generate_registration_button = $('input[name="generate-registration-codes-csv"]');
var registration_code_error = $('#generate_codes #registration_code_form_error');
function validateEmail(sEmail) {
filter = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/
return filter.test(sEmail)
}
generate_registration_code_form.submit(function () {
registration_code_error.attr('style', 'display: none');
generate_registration_button.attr('disabled', true);
var total_registration_codes = $('input[name="total_registration_codes"]').val();
var recipient_name = $('input[name="recipient_name"]').val();
var recipient_email = $('input[name="recipient_email"]').val();
var sale_price = $('input[name="sale_price"]').val();
var company_name = $('input[name="company_name"]').val();
var company_contact_name = $('input[name="company_contact_name"]').val();
var company_contact_email = $('input[name="company_contact_email"]').val();
var address_line = $('input[name="address_line_1"]').val();
if (company_name == '') {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the company name');
generate_registration_button.removeAttr('disabled');
return false;
}
if (($.isNumeric(company_name))) {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the non-numeric value for company name');
generate_registration_button.removeAttr('disabled');
return false;
}
if (company_contact_name == '') {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the company contact name');
generate_registration_button.removeAttr('disabled');
return false;
}
if (($.isNumeric(company_contact_name))) {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the non-numeric value for company contact name');
generate_registration_button.removeAttr('disabled');
return false;
}
if (company_contact_email == '') {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the company contact email');
generate_registration_button.removeAttr('disabled');
return false;
}
if (!(validateEmail(company_contact_email))) {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the valid email address');
generate_registration_button.removeAttr('disabled');
return false;
}
if (recipient_name == '') {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the recipient name');
generate_registration_button.removeAttr('disabled');
return false;
}
if (($.isNumeric(recipient_name))) {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the non-numeric value for recipient name');
generate_registration_button.removeAttr('disabled');
return false;
}
if (recipient_email == '') {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the recipient email');
generate_registration_button.removeAttr('disabled');
return false;
}
if (!(validateEmail(recipient_email))) {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the valid email address');
generate_registration_button.removeAttr('disabled');
return false;
}
if (address_line == '') {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the billing address');
generate_registration_button.removeAttr('disabled');
return false;
}
if (sale_price == '') {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the sale price');
generate_registration_button.removeAttr('disabled');
return false
}
if (!($.isNumeric(sale_price))) {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the numeric value for sale price');
generate_registration_button.removeAttr('disabled');
return false
}
if (total_registration_codes == '') {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the total registration codes');
generate_registration_button.removeAttr('disabled');
return false
}
if (!($.isNumeric(total_registration_codes))) {
registration_code_error.attr('style', 'display: block !important');
registration_code_error.text('Please enter the numeric value for total registration codes');
generate_registration_button.removeAttr('disabled');
return false;
}
var modal_overLay = $('#lean_overlay')
var registration_code_modal = $('#registration_code_generation_modal');
registration_code_modal.hide();
modal_overLay.hide();
});
$('#edit_coupon_form').submit(function () { $('#edit_coupon_form').submit(function () {
$("#update_coupon_button").attr('disabled', true); $("#update_coupon_button").attr('disabled', true);
// Get the Code and Discount value and trim it
var code = $.trim($('#edit_coupon_code').val());
var coupon_discount = $.trim($('#edit_coupon_discount').val());
// Check if empty of not
if (code === '') {
$('#edit_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#edit_coupon_form #coupon_form_error').text("${_('Please Enter the Coupon Code')}");
$("#update_coupon_button").removeAttr('disabled');
return false;
}
if (coupon_discount == '0') {
$('#edit_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#edit_coupon_form #coupon_form_error').text("${_('Please Enter the Value Greater than 0')}");
$("#update_coupon_button").removeAttr('disabled');
return false;
}
if (parseInt(coupon_discount) > 100) {
$('#edit_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#edit_coupon_form #coupon_form_error').text("${_('Please Enter the Coupon Discount Value Less than or Equal to 100')}");
$("#update_coupon_button").removeAttr('disabled');
return false;
}
if (!$.isNumeric(coupon_discount)) {
$('#edit_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#edit_coupon_form #coupon_form_error').text("${_('Please Enter the Coupon Discount Value Greater than 0')}");
$("#update_coupon_button").removeAttr('disabled');
return false;
}
}); });
$('#course_price_link').click(function () { $('#course_price_link').click(function () {
reset_input_fields(); reset_input_fields();
...@@ -244,6 +395,10 @@ ...@@ -244,6 +395,10 @@
$('#add_coupon_link').click(function () { $('#add_coupon_link').click(function () {
reset_input_fields(); reset_input_fields();
}); });
$('#registration_code_generation_link').click(function () {
reset_input_fields();
$('input[name="generate-registration-codes-csv"]').removeAttr('disabled');
});
$('#set_price_form').submit(function () { $('#set_price_form').submit(function () {
$("#set_course_button").attr('disabled', true); $("#set_course_button").attr('disabled', true);
// Get the Code and Discount value and trim it // Get the Code and Discount value and trim it
...@@ -253,19 +408,19 @@ ...@@ -253,19 +408,19 @@
// Check if empty of not // Check if empty of not
if (course_price === '') { if (course_price === '') {
$('#set_price_form #course_form_error').attr('style', 'display: block !important'); $('#set_price_form #course_form_error').attr('style', 'display: block !important');
$('#set_price_form #course_form_error').text("${_('Please Enter the Course Price')}"); $('#set_price_form #course_form_error').text("${_('Please enter the course price')}");
$("#set_course_button").removeAttr('disabled'); $("#set_course_button").removeAttr('disabled');
return false; return false;
} }
if (!$.isNumeric(course_price)) { if (!$.isNumeric(course_price)) {
$("#set_course_button").removeAttr('disabled'); $("#set_course_button").removeAttr('disabled');
$('#set_price_form #course_form_error').attr('style', 'display: block !important'); $('#set_price_form #course_form_error').attr('style', 'display: block !important');
$('#set_price_form #course_form_error').text("${_('Please Enter the Numeric value for Discount')}"); $('#set_price_form #course_form_error').text("${_('Please enter the numeric value for course price')}");
return false; return false;
} }
if (currency == '') { if (currency == '') {
$('#set_price_form #course_form_error').attr('style', 'display: block !important'); $('#set_price_form #course_form_error').attr('style', 'display: block !important');
$('#set_price_form #course_form_error').text("${_('Please Select the Currency')}"); $('#set_price_form #course_form_error').text("${_('Please select the currency')}");
$("#set_course_button").removeAttr('disabled'); $("#set_course_button").removeAttr('disabled');
return false; return false;
} }
...@@ -280,25 +435,25 @@ ...@@ -280,25 +435,25 @@
if (code === '') { if (code === '') {
$("#add_coupon_button").removeAttr('disabled'); $("#add_coupon_button").removeAttr('disabled');
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important'); $('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#add_coupon_form #coupon_form_error').text("${_('Please Enter the Coupon Code')}"); $('#add_coupon_form #coupon_form_error').text("${_('Please enter the coupon code')}");
return false; return false;
} }
if (coupon_discount == '0') { if (coupon_discount == '0') {
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important'); $('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#add_coupon_form #coupon_form_error').text("${_('Please Enter the Coupon Discount Value Greater than 0')}"); $('#add_coupon_form #coupon_form_error').text("${_('Please enter the coupon discount value greater than 0')}");
$("#add_coupon_button").removeAttr('disabled'); $("#add_coupon_button").removeAttr('disabled');
return false; return false;
} }
if (parseInt(coupon_discount) > 100) { if (parseInt(coupon_discount) > 100) {
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important'); $('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#add_coupon_form #coupon_form_error').text("${_('Please Enter the Coupon Discount Value Less than or Equal to 100')}"); $('#add_coupon_form #coupon_form_error').text("${_('Please enter the coupon discount value less than or equal to 100')}");
$("#add_coupon_button").removeAttr('disabled'); $("#add_coupon_button").removeAttr('disabled');
return false; return false;
} }
if (!$.isNumeric(coupon_discount)) { if (!$.isNumeric(coupon_discount)) {
$("#add_coupon_button").removeAttr('disabled'); $("#add_coupon_button").removeAttr('disabled');
$('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important'); $('#add_coupon_form #coupon_form_error').attr('style', 'display: block !important');
$('#add_coupon_form #coupon_form_error').text("${_('Please Enter the Numeric value for Discount')}"); $('#add_coupon_form #coupon_form_error').text("${_('Please enter the numeric value for discount')}");
return false; return false;
} }
}); });
...@@ -337,6 +492,7 @@ ...@@ -337,6 +492,7 @@
$("#update_coupon_button").removeAttr('disabled'); $("#update_coupon_button").removeAttr('disabled');
$("#add_coupon_button").removeAttr('disabled'); $("#add_coupon_button").removeAttr('disabled');
$("#set_course_button").removeAttr('disabled'); $("#set_course_button").removeAttr('disabled');
$('input[name="generate-registration-codes-csv"]').removeAttr('disabled');
reset_input_fields(); reset_input_fields();
e.preventDefault(); e.preventDefault();
}); });
...@@ -347,9 +503,11 @@ ...@@ -347,9 +503,11 @@
$("#edit-coupon-modal").attr("aria-hidden", "true"); $("#edit-coupon-modal").attr("aria-hidden", "true");
$(".edit-right").focus(); $(".edit-right").focus();
$("#set-course-mode-price-modal").attr("aria-hidden", "true"); $("#set-course-mode-price-modal").attr("aria-hidden", "true");
$("#registration_code_generation_modal").attr("aria-hidden", "true");
$("#add_coupon_button").removeAttr('disabled'); $("#add_coupon_button").removeAttr('disabled');
$("#set_course_button").removeAttr('disabled'); $("#set_course_button").removeAttr('disabled');
$("#update_coupon_button").removeAttr('disabled'); $("#update_coupon_button").removeAttr('disabled');
$('input[name="generate-registration-codes-csv"]').removeAttr('disabled');
reset_input_fields(); reset_input_fields();
}; };
...@@ -366,11 +524,12 @@ ...@@ -366,11 +524,12 @@
$("#add-coupon-modal .close-modal").click(onModalClose); $("#add-coupon-modal .close-modal").click(onModalClose);
$("#edit-coupon-modal .close-modal").click(onModalClose); $("#edit-coupon-modal .close-modal").click(onModalClose);
$('#registration_code_generation_modal .close-modal').click(onModalClose);
$("#set-course-mode-price-modal .close-modal").click(reset_input_fields); $("#set-course-mode-price-modal .close-modal").click(reset_input_fields);
// Hitting the ESC key will exit the modal // Hitting the ESC key will exit the modal
$("#add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal").on("keydown", function (e) { $("#add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal, #registration_code_generation_modal").on("keydown", function (e) {
var keyCode = e.keyCode || e.which; var keyCode = e.keyCode || e.which;
// 27 is the ESC key // 27 is the ESC key
if (keyCode === 27) { if (keyCode === 27) {
...@@ -378,19 +537,38 @@ ...@@ -378,19 +537,38 @@
$("#add-coupon-modal .close-modal").click(); $("#add-coupon-modal .close-modal").click();
$("#set-course-mode-price-modal .close-modal").click(); $("#set-course-mode-price-modal .close-modal").click();
$("#edit-coupon-modal .close-modal").click(); $("#edit-coupon-modal .close-modal").click();
$('#registration_code_generation_modal .close-modal').click();
} }
}); });
}); });
var reset_input_fields = function () { var reset_input_fields = function () {
$('#coupon-error').val(''); $('#error-msg').val('');
$('#coupon-error').attr('style', 'display: none'); $('#error-msg').hide()
$('#add_coupon_form #coupon_form_error').attr('style', 'display: none'); $('#add_coupon_form #coupon_form_error').attr('style', 'display: none');
$('#set_price_form #course_form_error').attr('style', 'display: none'); $('#set_price_form #course_form_error').attr('style', 'display: none');
$('#generate_codes #registration_code_form_error').attr('style', 'display: none');
$('#add_coupon_form #coupon_form_error').text(); $('#add_coupon_form #coupon_form_error').text();
$('input#mode_price').val(''); $('input#mode_price').val('');
$('input#coupon_code').val(''); $('input#coupon_code').val('');
$('input#coupon_discount').val(''); $('input#coupon_discount').val('');
$('textarea#coupon_description').val(''); $('textarea#coupon_description').val('');
$('input[name="company_name"]').val('');
$('input[name="total_registration_codes"]').val('');
$('input[name="address_line_1"]').val('');
$('input[name="address_line_2"]').val('');
$('input[name="address_line_3"]').val('');
$('input[name="city"]').val('');
$('input[name="state"]').val('');
$('input[name="zip"]').val('');
$('input[name="country"]').val('');
$('input[name="customer_reference_number"]').val('');
$('input[name="recipient_name"]').val('');
$('input[name="sale_price"]').val('');
$('input[name="recipient_email"]').val('');
$('input[name="company_contact_name"]').val('');
$('input[name="company_contact_email"]').val('');
$('input[name="invoice"]').attr('checked', 'checked');
$('input[name="company_reference"]').val('');
$('input[name="internal_reference"]').val('');
} }
</script> </script>
...@@ -29,13 +29,13 @@ ...@@ -29,13 +29,13 @@
<ol class="list-input"> <ol class="list-input">
<li class="field required text" id="edit-coupon-modal-field-code"> <li class="field required text" id="edit-coupon-modal-field-code">
<label for="edit_coupon_code" class="required">${_("Code")}</label> <label for="edit_coupon_code" class="required">${_("Code")}</label>
<input class="field" id="edit_coupon_code" type="text" name="code" maxlength="16" value="" placeholder="example: A123DS" <input class="field readonly" id="edit_coupon_code" type="text" name="code" maxlength="16" value="" placeholder="example: A123DS"
aria-required="true"/> readonly aria-required="true"/>
</li> </li>
<li class="field required text" id="edit-coupon-modal-field-discount"> <li class="field required text" id="edit-coupon-modal-field-discount">
<label for="edit_coupon_discount" class="required">${_("Percentage Discount")}</label> <label for="edit_coupon_discount" class="required">${_("Percentage Discount")}</label>
<input class="field" id="edit_coupon_discount" type="text" name="discount" value="" maxlength="3" <input class="field readonly" id="edit_coupon_discount" type="text" name="discount" value="" maxlength="3"
aria-required="true"/> readonly aria-required="true"/>
</li> </li>
<li class="field" id="edit-coupon-modal-field-description"> <li class="field" id="edit-coupon-modal-field-description">
......
<%! from django.utils.translation import ugettext as _ %>
<%! from django.core.urlresolvers import reverse %>
<%page args="section_data"/>
<section id="registration_code_generation_modal" class="modal" role="dialog" tabindex="-1" aria-label="${_('Generate Registration Code Modal')}">
<div class="inner-wrapper">
<button class="close-modal">
<i class="icon-remove"></i>
<span class="sr">
## 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')}
</span>
</button>
<div id="registration-content">
<header>
<h2>${_("Generate Registration Codes")}</h2>
</header>
<div class="instructions">
<p>
${_("Please enter the details below")}</p>
</div>
<form id="generate_codes" action="${section_data['generate_registration_code_csv_url']}" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
<div id="registration_code_form_error" class="modal-form-error"></div>
<fieldset class="group group-form group-form-requiredinformation">
<legend class="is-hidden">${_("Required Information")}</legend>
<ol class="list-input">
<li class="field required text" id="generate-registration-modal-field-company-name">
<label for="id_company_name" class="required text">${_("Organization Name")}</label>
<input class="field required" id="id_company_name" type="text" name="company_name"
placeholder="${_('Organization Name')}" aria-required="true" />
<span class="tip-text">
${_("What is the company the seats were sold to?")}
</span>
</li>
<li class="field required text" id="generate-registration-modal-field-company-contact-name">
<label for="id_company_contact_name" class="required text">${_("Organization Contact")}</label>
<input class="field required" id="id_company_contact_name" type="text" name="company_contact_name"
placeholder="${_('Name')}" aria-required="true" />
<span class="tip-text">
${_("Who is the key contact at the company the sale was made to? ")}
</span>
</li>
<li class="field text" id="generate-registration-modal-field-company-email">
<label for="id_company_contact_email" class="text">&nbsp;</label>
<input class="field" id="id_company_contact_email" type="email" value="" name="company_contact_email"
placeholder="${_('Email')}" aria-required="true" />
</li>
<div class="clearfix"></div>
<li class="field required text" id="generate-registration-modal-field-recipient-name">
<label for="id_recipient_name" class="required text">${_("Invoice Recipient")}</label>
<input class="field required" id="id_recipient_name" type="text" name="recipient_name"
placeholder="${_('Name')}" aria-required="true"/>
<span class="tip-text">
${_("Who at the company should the invoice be sent to?")}
</span>
</li>
<li class="field text" id="generate-registration-modal-field-recipient-email">
<label for="id_recipient_email" class="text">&nbsp;</label>
<input class="field required" id="id_recipient_email" type="email" value="" placeholder="${_('Email')}"
name="recipient_email" aria-required="true"/>
</li>
<div class="clearfix"></div>
<li class="field required text address_fields" id="generate-registration-modal-field-address-line-1">
<label for="id_address_line_1" class="required text">${_("Organization Billing Address")}</label>
<input class="field required" id="id_address_line_1" type="text" name="address_line_1"
placeholder="${_('Address Line 1')}" aria-required="true" />
</li>
<li class="field text address_fields" id="generate-registration-modal-field-address-line-2">
<label for="id_address_line_2" class="text">&nbsp;</label>
<input class="field" id="id_address_line_2" type="text" name="address_line_2"
placeholder="${_('Address Line 2')}" aria-required="true" />
</li>
<li class="field text address_fields" id="generate-registration-modal-field-address-line-3">
<label for="id_address_line_3" class="text">&nbsp;</label>
<input class="field" id="id_address_line_3" type="text" name="address_line_3"
placeholder="${_('Address Line 3')}" aria-required="true" />
</li>
<li class="field text address_fields" id="generate-registration-modal-field-city">
<label for="id_city" class="text">&nbsp;</label>
<input class="field" id="id_city" type="text" name="city"
placeholder="${_('City')}" aria-required="true" />
</li>
<li class="field text address_fields" id="generate-registration-modal-field-state">
<label for="id_state" class="text">&nbsp;</label>
<input class="field" id="id_state" type="text" name="state"
placeholder="${_('State/Province')}" aria-required="true" />
</li>
<li class="field text address_fields" id="generate-registration-modal-field-zipcode">
<label for="id_zip" class="text">&nbsp;</label>
<input class="field" id="id_zip" type="text" name="zip"
placeholder="${_('Zip')}" aria-required="true" />
</li>
<li class="field text address_fields" id="generate-registration-modal-field-country">
<label for="id_country" class="text">&nbsp;</label>
<input class="field" id="id_country" type="text" name="country"
placeholder="${_('Country')}" aria-required="true" />
<span class="tip-text">
</span>
</li>
<div class="clearfix"></div>
<li class="field required text" id="generate-registration-modal-field-total-price">
<label for="id_sale_price" class="required text">${_("Price of Sale")}</label>
<input class="field required" id="id_sale_price" type="text" name="sale_price"
aria-required="true" />
<span class="tip-text">
${_("What was the total sale price for all seats sold?")}
</span>
</li>
<li class="field required text" id="generate-registration-modal-field-total-codes">
<label for="id_total_registration_codes" class="required text">${_("Number of registrations")}</label>
<input class="field required" id="id_total_registration_codes" type="text" name="total_registration_codes"
aria-required="true"/>
<span class="tip-text">
${_("Number of codes to generate for the Invoice")}
</span>
</li>
<div class="clearfix"></div>
<li class="field text" id="generate-registration-modal-field-internal-reference">
<label for="id_internal_reference" >${_("(Optional) Sale Label")}</label>
<input class="field" id="id_internal_reference" type="text" name="internal_reference"
aria-required="true" placeholder="BigCorp-IT-Department-1"/>
<span class="tip-text">
${_("Internal tag to associate with the sale")}
</span>
</li>
<li class="field text" id="generate-registration-modal-field-custom-reference-number">
<label for="id_customer_reference_number" >${_("(Optional) Customer Reference Number")}</label>
<input class="field" id="id_customer_reference_number" type="text" name="customer_reference_number"
aria-required="true" placeholder="PO #"/>
<span class="tip-text">
${_("Any reference specific to the purchasing company, e.g. PO #")}
</span>
</li>
<div class="clearfix"></div>
<li class="field text" id="generate-registration-modal-field-invoice-copy">
<label for="id_invoice" >${_("Send me a copy of the invoice")}</label>
<input class="field" id="id_invoice" type="checkbox" name="invoice"
aria-required="true"/>
</li>
</ol>
</fieldset>
<div class="submit">
<input name="generate-registration-codes-csv" type="submit" value="${_('Generate Registration Codes')}" data-csv="true"/>
</div>
</form>
</div>
</div>
</section>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment