SOL-916 Add ability to query, enable, disable, unredeem Enrollment Codes
This commit is contained in:
committed by
Chris Dodge
parent
0db8bac7c4
commit
4dff8ecf09
291
lms/djangoapps/instructor/tests/test_registration_codes.py
Normal file
291
lms/djangoapps/instructor/tests/test_registration_codes.py
Normal file
@@ -0,0 +1,291 @@
|
||||
"""
|
||||
Test for the registration code status information.
|
||||
"""
|
||||
from courseware.tests.factories import InstructorFactory
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from django.utils.translation import ugettext as _
|
||||
from shoppingcart.models import (
|
||||
Invoice, CourseRegistrationCodeInvoiceItem, CourseRegistrationCode,
|
||||
CourseRegCodeItem, Order, RegistrationCodeRedemption
|
||||
)
|
||||
from student.models import CourseEnrollment
|
||||
from student.roles import CourseSalesAdminRole
|
||||
from nose.plugins.attrib import attr
|
||||
import json
|
||||
from student.tests.factories import UserFactory, CourseModeFactory
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test.utils import override_settings
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
@override_settings(REGISTRATION_CODE_LENGTH=8)
|
||||
class TestCourseRegistrationCodeStatus(ModuleStoreTestCase):
|
||||
"""
|
||||
Test registration code status.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestCourseRegistrationCodeStatus, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
CourseModeFactory.create(course_id=self.course.id, min_price=50)
|
||||
self.instructor = InstructorFactory(course_key=self.course.id)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
CourseSalesAdminRole(self.course.id).add_users(self.instructor)
|
||||
|
||||
# create testing invoice
|
||||
self.sale_invoice = Invoice.objects.create(
|
||||
total_amount=1234.32, company_name='Test1', company_contact_name='TestName',
|
||||
company_contact_email='Test@company.com', recipient_name='Testw', recipient_email='test1@test.com',
|
||||
customer_reference_number='2Fwe23S', internal_reference="A", course_id=self.course.id, is_valid=True
|
||||
)
|
||||
self.invoice_item = CourseRegistrationCodeInvoiceItem.objects.create(
|
||||
invoice=self.sale_invoice,
|
||||
qty=1,
|
||||
unit_price=1234.32,
|
||||
course_id=self.course.id
|
||||
)
|
||||
self.lookup_code_url = reverse('look_up_registration_code',
|
||||
kwargs={'course_id': unicode(self.course.id)})
|
||||
|
||||
self.registration_code_detail_url = reverse('registration_code_details',
|
||||
kwargs={'course_id': unicode(self.course.id)})
|
||||
|
||||
url = reverse('generate_registration_codes',
|
||||
kwargs={'course_id': self.course.id.to_deprecated_string()})
|
||||
|
||||
data = {
|
||||
'total_registration_codes': 12,
|
||||
'company_name': 'Test Group',
|
||||
'company_contact_name': 'Test@company.com',
|
||||
'company_contact_email': 'Test@company.com',
|
||||
'unit_price': 122.45,
|
||||
'recipient_name': 'Test123',
|
||||
'recipient_email': 'test@123.com',
|
||||
'address_line_1': 'Portland Street',
|
||||
'address_line_2': '',
|
||||
'address_line_3': '',
|
||||
'city': '',
|
||||
'state': '',
|
||||
'zip': '',
|
||||
'country': '',
|
||||
'customer_reference_number': '123A23F',
|
||||
'internal_reference': '',
|
||||
'invoice': ''
|
||||
}
|
||||
|
||||
response = self.client.post(url, data)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
|
||||
def test_look_up_invalid_registration_code(self):
|
||||
"""
|
||||
Verify the view returns HTTP status 400 if an invalid registration code is passed.
|
||||
Also, verify the data returned includes a message indicating the error,
|
||||
and the is_registration_code_valid is set to False.
|
||||
"""
|
||||
data = {
|
||||
'registration_code': 'invalid_reg_code'
|
||||
}
|
||||
response = self.client.get(self.lookup_code_url, data)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
json_dict = json.loads(response.content)
|
||||
message = _('The enrollment code ({code}) was not found for the {course_name} course.').format(
|
||||
course_name=self.course.display_name, code=data['registration_code']
|
||||
)
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
self.assertFalse(json_dict['is_registration_code_valid'])
|
||||
self.assertFalse(json_dict['is_registration_code_redeemed'])
|
||||
|
||||
def test_look_up_valid_registration_code(self):
|
||||
"""
|
||||
test lookup for the valid registration code
|
||||
and that registration code has been redeemed by user
|
||||
and then mark the registration code as in_valid
|
||||
when marking as invalidate, it also lookup for
|
||||
registration redemption entry and also delete
|
||||
that redemption entry and un_enroll the student
|
||||
who used that registration code for their enrollment.
|
||||
"""
|
||||
for i in range(2):
|
||||
CourseRegistrationCode.objects.create(
|
||||
code='reg_code{}'.format(i),
|
||||
course_id=unicode(self.course.id),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor'
|
||||
)
|
||||
|
||||
reg_code = CourseRegistrationCode.objects.all()[0]
|
||||
student = UserFactory()
|
||||
enrollment = CourseEnrollment.enroll(student, self.course.id)
|
||||
|
||||
RegistrationCodeRedemption.objects.create(
|
||||
registration_code=reg_code,
|
||||
redeemed_by=student,
|
||||
course_enrollment=enrollment
|
||||
)
|
||||
|
||||
data = {
|
||||
'registration_code': reg_code.code
|
||||
}
|
||||
response = self.client.get(self.lookup_code_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
json_dict = json.loads(response.content)
|
||||
self.assertTrue(json_dict['is_registration_code_valid'])
|
||||
self.assertTrue(json_dict['is_registration_code_redeemed'])
|
||||
|
||||
# now mark that registration code as invalid
|
||||
data = {
|
||||
'registration_code': reg_code.code,
|
||||
'action_type': 'invalidate_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
json_dict = json.loads(response.content)
|
||||
message = _('This enrollment code has been canceled. It can no longer be used.')
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
|
||||
# now check that the registration code should be marked as invalid in the db.
|
||||
reg_code = CourseRegistrationCode.objects.get(code=reg_code.code)
|
||||
self.assertEqual(reg_code.is_valid, False)
|
||||
|
||||
redemption = RegistrationCodeRedemption.get_registration_code_redemption(reg_code.code, self.course.id)
|
||||
self.assertIsNone(redemption)
|
||||
|
||||
# now the student course enrollment should be false.
|
||||
enrollment = CourseEnrollment.get_enrollment(student, self.course.id)
|
||||
self.assertEqual(enrollment.is_active, False)
|
||||
|
||||
def test_lookup_valid_redeemed_registration_code(self):
|
||||
"""
|
||||
test to lookup for the valid and redeemed registration code
|
||||
and then mark that registration code as un_redeemed
|
||||
which will unenroll the user and delete the redemption
|
||||
entry from the database.
|
||||
"""
|
||||
student = UserFactory()
|
||||
self.client.login(username=student.username, password='test')
|
||||
cart = Order.get_cart_for_user(student)
|
||||
cart.order_type = 'business'
|
||||
cart.save()
|
||||
CourseRegCodeItem.add_to_order(cart, self.course.id, 2)
|
||||
cart.purchase()
|
||||
|
||||
reg_code = CourseRegistrationCode.objects.filter(order=cart)[0]
|
||||
|
||||
enrollment = CourseEnrollment.enroll(student, self.course.id)
|
||||
|
||||
RegistrationCodeRedemption.objects.create(
|
||||
registration_code=reg_code,
|
||||
redeemed_by=student,
|
||||
course_enrollment=enrollment
|
||||
)
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
data = {
|
||||
'registration_code': reg_code.code
|
||||
}
|
||||
response = self.client.get(self.lookup_code_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
json_dict = json.loads(response.content)
|
||||
self.assertTrue(json_dict['is_registration_code_valid'])
|
||||
self.assertTrue(json_dict['is_registration_code_redeemed'])
|
||||
|
||||
# now mark the registration code as unredeemed
|
||||
# this will unenroll the user and removed the redemption entry from
|
||||
# the database.
|
||||
|
||||
data = {
|
||||
'registration_code': reg_code.code,
|
||||
'action_type': 'unredeem_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
json_dict = json.loads(response.content)
|
||||
message = _('This enrollment code has been marked as unused.')
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
|
||||
redemption = RegistrationCodeRedemption.get_registration_code_redemption(reg_code.code, self.course.id)
|
||||
self.assertIsNone(redemption)
|
||||
|
||||
# now the student course enrollment should be false.
|
||||
enrollment = CourseEnrollment.get_enrollment(student, self.course.id)
|
||||
self.assertEqual(enrollment.is_active, False)
|
||||
|
||||
def test_apply_invalid_reg_code_when_updating_code_information(self):
|
||||
"""
|
||||
test to apply an invalid registration code
|
||||
when updating the registration code information.
|
||||
"""
|
||||
data = {
|
||||
'registration_code': 'invalid_registration_code',
|
||||
'action_type': 'unredeem_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
json_dict = json.loads(response.content)
|
||||
message = _('The enrollment code ({code}) was not found for the {course_name} course.').format(
|
||||
course_name=self.course.display_name, code=data['registration_code']
|
||||
)
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
|
||||
def test_mark_registration_code_as_valid(self):
|
||||
"""
|
||||
test to mark the invalid registration code
|
||||
as valid
|
||||
"""
|
||||
for i in range(2):
|
||||
CourseRegistrationCode.objects.create(
|
||||
code='reg_code{}'.format(i),
|
||||
course_id=self.course.id.to_deprecated_string(),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor',
|
||||
is_valid=False
|
||||
)
|
||||
|
||||
reg_code = CourseRegistrationCode.objects.all()[0]
|
||||
data = {
|
||||
'registration_code': reg_code.code,
|
||||
'action_type': 'validate_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
json_dict = json.loads(response.content)
|
||||
message = _('The enrollment code has been restored.')
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
|
||||
# now check that the registration code should be marked as valid in the db.
|
||||
reg_code = CourseRegistrationCode.objects.get(code=reg_code.code)
|
||||
self.assertEqual(reg_code.is_valid, True)
|
||||
|
||||
def test_returns_error_when_unredeeming_already_unredeemed_registration_code_redemption(self):
|
||||
"""
|
||||
test to mark the already unredeemed registration code as unredeemed.
|
||||
"""
|
||||
for i in range(2):
|
||||
CourseRegistrationCode.objects.create(
|
||||
code='reg_code{}'.format(i),
|
||||
course_id=self.course.id.to_deprecated_string(),
|
||||
created_by=self.instructor,
|
||||
invoice=self.sale_invoice,
|
||||
invoice_item=self.invoice_item,
|
||||
mode_slug='honor'
|
||||
)
|
||||
|
||||
reg_code = CourseRegistrationCode.objects.all()[0]
|
||||
data = {
|
||||
'registration_code': reg_code.code,
|
||||
'action_type': 'unredeem_registration_code'
|
||||
}
|
||||
response = self.client.post(self.registration_code_detail_url, data)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
json_dict = json.loads(response.content)
|
||||
message = _('The redemption does not exist against enrollment code ({code}).').format(code=reg_code.code)
|
||||
self.assertEqual(message, json_dict['message'])
|
||||
@@ -1253,7 +1253,7 @@ def registration_codes_csv(file_name, codes_list, csv_type=None):
|
||||
# csv headers
|
||||
query_features = [
|
||||
'code', 'redeem_code_url', 'course_id', 'company_name', 'created_by',
|
||||
'redeemed_by', 'invoice_id', 'purchaser', 'customer_reference_number', 'internal_reference'
|
||||
'redeemed_by', 'invoice_id', 'purchaser', 'customer_reference_number', 'internal_reference', 'is_valid'
|
||||
]
|
||||
|
||||
registration_codes = instructor_analytics.basic.course_registration_features(query_features, codes_list, csv_type)
|
||||
|
||||
@@ -205,6 +205,7 @@ def _section_e_commerce(course, access, paid_mode, coupons_enabled, reports_enab
|
||||
'list_financial_report_downloads_url': reverse('list_financial_report_downloads',
|
||||
kwargs={'course_id': unicode(course_key)}),
|
||||
'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}),
|
||||
'look_up_registration_code': reverse('look_up_registration_code', kwargs={'course_id': unicode(course_key)}),
|
||||
'coupons': coupons,
|
||||
'sales_admin': access['sales_admin'],
|
||||
'coupons_enabled': coupons_enabled,
|
||||
|
||||
126
lms/djangoapps/instructor/views/registration_codes.py
Normal file
126
lms/djangoapps/instructor/views/registration_codes.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""
|
||||
E-commerce Tab Instructor Dashboard Query Registration Code Status.
|
||||
"""
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.views.decorators.http import require_GET, require_POST
|
||||
from instructor.enrollment import get_email_params, send_mail_to_student
|
||||
from django.utils.translation import ugettext as _
|
||||
from courseware.courses import get_course_by_id
|
||||
from instructor.views.api import require_level
|
||||
from student.models import CourseEnrollment
|
||||
from util.json_request import JsonResponse
|
||||
from shoppingcart.models import CourseRegistrationCode, RegistrationCodeRedemption
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from django.views.decorators.cache import cache_control
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_GET
|
||||
def look_up_registration_code(request, course_id): # pylint: disable=unused-argument
|
||||
"""
|
||||
Look for the registration_code in the database.
|
||||
and check if it is still valid, allowed to redeem or not.
|
||||
"""
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
code = request.GET.get('registration_code')
|
||||
course = get_course_by_id(course_key, depth=0)
|
||||
try:
|
||||
registration_code = CourseRegistrationCode.objects.get(code=code)
|
||||
except CourseRegistrationCode.DoesNotExist:
|
||||
return JsonResponse({
|
||||
'is_registration_code_exists': False,
|
||||
'is_registration_code_valid': False,
|
||||
'is_registration_code_redeemed': False,
|
||||
'message': _('The enrollment code ({code}) was not found for the {course_name} course.').format(
|
||||
code=code, course_name=course.display_name
|
||||
)
|
||||
}, status=400) # status code 200: OK by default
|
||||
|
||||
reg_code_already_redeemed = RegistrationCodeRedemption.is_registration_code_redeemed(code)
|
||||
|
||||
registration_code_detail_url = reverse('registration_code_details', kwargs={'course_id': unicode(course_id)})
|
||||
|
||||
return JsonResponse({
|
||||
'is_registration_code_exists': True,
|
||||
'is_registration_code_valid': registration_code.is_valid,
|
||||
'is_registration_code_redeemed': reg_code_already_redeemed,
|
||||
'registration_code_detail_url': registration_code_detail_url
|
||||
}) # status code 200: OK by default
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_POST
|
||||
def registration_code_details(request, course_id):
|
||||
"""
|
||||
Post handler to mark the registration code as
|
||||
1) valid
|
||||
2) invalid
|
||||
3) Unredeem.
|
||||
|
||||
"""
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
code = request.POST.get('registration_code')
|
||||
action_type = request.POST.get('action_type')
|
||||
course = get_course_by_id(course_key, depth=0)
|
||||
action_type_messages = {
|
||||
'invalidate_registration_code': _('This enrollment code has been canceled. It can no longer be used.'),
|
||||
'unredeem_registration_code': _('This enrollment code has been marked as unused.'),
|
||||
'validate_registration_code': _('The enrollment code has been restored.')
|
||||
}
|
||||
try:
|
||||
registration_code = CourseRegistrationCode.objects.get(code=code)
|
||||
except CourseRegistrationCode.DoesNotExist:
|
||||
return JsonResponse({
|
||||
'message': _('The enrollment code ({code}) was not found for the {course_name} course.').format(
|
||||
code=code, course_name=course.display_name
|
||||
)}, status=400)
|
||||
|
||||
if action_type == 'invalidate_registration_code':
|
||||
registration_code.is_valid = False
|
||||
registration_code.save()
|
||||
if RegistrationCodeRedemption.is_registration_code_redeemed(code):
|
||||
code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(code, course_key)
|
||||
delete_redemption_entry(request, code_redemption, course_key)
|
||||
|
||||
if action_type == 'validate_registration_code':
|
||||
registration_code.is_valid = True
|
||||
registration_code.save()
|
||||
|
||||
if action_type == 'unredeem_registration_code':
|
||||
code_redemption = RegistrationCodeRedemption.get_registration_code_redemption(code, course_key)
|
||||
if code_redemption is None:
|
||||
return JsonResponse({
|
||||
'message': _('The redemption does not exist against enrollment code ({code}).').format(
|
||||
code=code)}, status=400)
|
||||
|
||||
delete_redemption_entry(request, code_redemption, course_key)
|
||||
|
||||
return JsonResponse({'message': action_type_messages[action_type]})
|
||||
|
||||
|
||||
def delete_redemption_entry(request, code_redemption, course_key):
|
||||
"""
|
||||
delete the redemption entry from the table and
|
||||
unenroll the user who used the registration code
|
||||
for the enrollment and send him/her the unenrollment email.
|
||||
"""
|
||||
user = code_redemption.redeemed_by
|
||||
email_address = code_redemption.redeemed_by.email
|
||||
full_name = code_redemption.redeemed_by.profile.name
|
||||
CourseEnrollment.unenroll(user, course_key, skip_refund=True)
|
||||
|
||||
course = get_course_by_id(course_key, depth=0)
|
||||
email_params = get_email_params(course, True, secure=request.is_secure())
|
||||
email_params['message'] = 'enrolled_unenroll'
|
||||
email_params['email_address'] = email_address
|
||||
email_params['full_name'] = full_name
|
||||
send_mail_to_student(email_address, email_params)
|
||||
|
||||
# remove the redemption entry from the database.
|
||||
log.info('deleting redemption entry (%s) from the database.', code_redemption.id)
|
||||
code_redemption.delete()
|
||||
@@ -32,7 +32,7 @@ SALE_ORDER_FEATURES = ('id', 'company_name', 'company_contact_name', 'company_co
|
||||
'bill_to_country', 'order_type',)
|
||||
|
||||
AVAILABLE_FEATURES = STUDENT_FEATURES + PROFILE_FEATURES
|
||||
COURSE_REGISTRATION_FEATURES = ('code', 'course_id', 'created_by', 'created_at')
|
||||
COURSE_REGISTRATION_FEATURES = ('code', 'course_id', 'created_by', 'created_at', 'is_valid')
|
||||
COUPON_FEATURES = ('code', 'course_id', 'percentage_discount', 'description', 'expiration_date', 'is_active')
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as 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 'CourseRegistrationCode.is_valid'
|
||||
db.add_column('shoppingcart_courseregistrationcode', 'is_valid',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=True),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'CourseRegistrationCode.is_valid'
|
||||
db.delete_column('shoppingcart_courseregistrationcode', '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(2015, 5, 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'}),
|
||||
'expiration_date': ('django.db.models.fields.DateTimeField', [], {'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.courseregcodeitem': {
|
||||
'Meta': {'object_name': 'CourseRegCodeItem', '_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.courseregcodeitemannotation': {
|
||||
'Meta': {'object_name': 'CourseRegCodeItemAnnotation'},
|
||||
'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.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(2015, 5, 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'}),
|
||||
'invoice_item': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.CourseRegistrationCodeInvoiceItem']", 'null': 'True'}),
|
||||
'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'mode_slug': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'purchase_order'", 'null': 'True', 'to': "orm['shoppingcart.Order']"})
|
||||
},
|
||||
'shoppingcart.courseregistrationcodeinvoiceitem': {
|
||||
'Meta': {'object_name': 'CourseRegistrationCodeInvoiceItem', '_ormbases': ['shoppingcart.InvoiceItem']},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'invoiceitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.InvoiceItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.donation': {
|
||||
'Meta': {'object_name': 'Donation', '_ormbases': ['shoppingcart.OrderItem']},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'donation_type': ('django.db.models.fields.CharField', [], {'default': "'general'", 'max_length': '32'}),
|
||||
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
|
||||
},
|
||||
'shoppingcart.donationconfiguration': {
|
||||
'Meta': {'object_name': 'DonationConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': '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', 'blank': 'True'}),
|
||||
'address_line_3': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': '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'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'internal_reference': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'is_valid': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'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.invoicehistory': {
|
||||
'Meta': {'object_name': 'InvoiceHistory'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']"}),
|
||||
'snapshot': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'})
|
||||
},
|
||||
'shoppingcart.invoiceitem': {
|
||||
'Meta': {'object_name': 'InvoiceItem'},
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']"}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'qty': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
|
||||
'unit_price': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'})
|
||||
},
|
||||
'shoppingcart.invoicetransaction': {
|
||||
'Meta': {'object_name': 'InvoiceTransaction'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': '0.0', 'max_digits': '30', 'decimal_places': '2'}),
|
||||
'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'invoice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Invoice']"}),
|
||||
'last_modified_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'last_modified_by_user'", 'to': "orm['auth.User']"}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'started'", 'max_length': '32'})
|
||||
},
|
||||
'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'}),
|
||||
'company_contact_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'company_contact_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'company_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'usd'", 'max_length': '8'}),
|
||||
'customer_reference_number': ('django.db.models.fields.CharField', [], {'max_length': '63', 'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order_type': ('django.db.models.fields.CharField', [], {'default': "'personal'", 'max_length': '32'}),
|
||||
'processor_reply_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'purchase_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'recipient_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'recipient_name': ('django.db.models.fields.CharField', [], {'max_length': '255', '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'},
|
||||
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'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'}),
|
||||
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
|
||||
'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_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}),
|
||||
'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'},
|
||||
'course_enrollment': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['student.CourseEnrollment']", 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'order': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['shoppingcart.Order']", 'null': 'True'}),
|
||||
'redeemed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2015, 5, 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']
|
||||
@@ -1161,6 +1161,7 @@ class CourseRegistrationCode(models.Model):
|
||||
created_at = models.DateTimeField(default=datetime.now(pytz.utc))
|
||||
order = models.ForeignKey(Order, db_index=True, null=True, related_name="purchase_order")
|
||||
mode_slug = models.CharField(max_length=100, null=True)
|
||||
is_valid = models.BooleanField(default=True)
|
||||
|
||||
# For backwards compatibility, we maintain the FK to "invoice"
|
||||
# In the future, we will remove this in favor of the FK
|
||||
@@ -1196,10 +1197,21 @@ class RegistrationCodeRedemption(models.Model):
|
||||
Checks the existence of the registration code
|
||||
in the RegistrationCodeRedemption
|
||||
"""
|
||||
return cls.objects.filter(registration_code=course_reg_code).exists()
|
||||
return cls.objects.filter(registration_code__code=course_reg_code).exists()
|
||||
|
||||
@classmethod
|
||||
def create_invoice_generated_registration_redemption(cls, course_reg_code, user):
|
||||
def get_registration_code_redemption(cls, code, course_id):
|
||||
"""
|
||||
Returns the registration code redemption object if found else returns None.
|
||||
"""
|
||||
try:
|
||||
code_redemption = cls.objects.get(registration_code__code=code, registration_code__course_id=course_id)
|
||||
except cls.DoesNotExist:
|
||||
code_redemption = None
|
||||
return code_redemption
|
||||
|
||||
@classmethod
|
||||
def create_invoice_generated_registration_redemption(cls, course_reg_code, user): # pylint: disable=invalid-name
|
||||
"""
|
||||
This function creates a RegistrationCodeRedemption entry in case the registration codes were invoice generated
|
||||
and thus the order_id is missing.
|
||||
|
||||
@@ -121,12 +121,14 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
|
||||
percentage_discount=self.percentage_discount, created_by=self.user, is_active=is_active)
|
||||
coupon.save()
|
||||
|
||||
def add_reg_code(self, course_key, mode_slug='honor'):
|
||||
def add_reg_code(self, course_key, mode_slug='honor', is_valid=True):
|
||||
"""
|
||||
add dummy registration code into models
|
||||
"""
|
||||
course_reg_code = CourseRegistrationCode(
|
||||
code=self.reg_code, course_id=course_key, created_by=self.user, mode_slug=mode_slug
|
||||
code=self.reg_code, course_id=course_key,
|
||||
created_by=self.user, mode_slug=mode_slug,
|
||||
is_valid=is_valid
|
||||
)
|
||||
course_reg_code.save()
|
||||
|
||||
@@ -387,6 +389,23 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
self.assertIn("Discount does not exist against code '{0}'.".format(self.coupon_code), resp.content)
|
||||
|
||||
def test_inactive_registration_code_returns_error(self):
|
||||
"""
|
||||
test to redeem inactive registration code and
|
||||
it returns an error.
|
||||
"""
|
||||
course_key = self.course_key.to_deprecated_string()
|
||||
self.add_reg_code(course_key, is_valid=False)
|
||||
self.add_course_to_user_cart(self.course_key)
|
||||
|
||||
# now apply the inactive registration code
|
||||
# it will raise an exception
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertIn(
|
||||
"This enrollment code ({enrollment_code}) is no longer valid.".format(
|
||||
enrollment_code=self.reg_code), resp.content)
|
||||
|
||||
def test_course_does_not_exist_in_cart_against_valid_reg_code(self):
|
||||
course_key = self.course_key.to_deprecated_string() + 'testing'
|
||||
self.add_reg_code(course_key)
|
||||
@@ -525,7 +544,6 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
|
||||
self.assertEqual(coupon.is_active, False)
|
||||
|
||||
def test_course_free_discount_for_valid_active_reg_code(self):
|
||||
|
||||
self.add_reg_code(self.course_key)
|
||||
self.add_course_to_user_cart(self.course_key)
|
||||
|
||||
@@ -546,7 +564,9 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
|
||||
# the item has been removed when using the registration code for the first time
|
||||
resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.reg_code})
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertIn("Oops! The code '{0}' you entered is either invalid or expired".format(self.reg_code), resp.content)
|
||||
self.assertIn("This enrollment code ({enrollment_code}) is not valid.".format(
|
||||
enrollment_code=self.reg_code
|
||||
), resp.content)
|
||||
|
||||
def test_upgrade_from_valid_reg_code(self):
|
||||
"""Use a valid registration code to upgrade from honor to verified mode. """
|
||||
|
||||
@@ -287,17 +287,14 @@ def get_reg_code_validity(registration_code, request, limiter):
|
||||
except CourseRegistrationCode.DoesNotExist:
|
||||
reg_code_is_valid = False
|
||||
else:
|
||||
reg_code_is_valid = True
|
||||
try:
|
||||
RegistrationCodeRedemption.objects.get(registration_code__code=registration_code)
|
||||
except RegistrationCodeRedemption.DoesNotExist:
|
||||
reg_code_already_redeemed = False
|
||||
if course_registration.is_valid:
|
||||
reg_code_is_valid = True
|
||||
else:
|
||||
reg_code_already_redeemed = True
|
||||
|
||||
reg_code_is_valid = False
|
||||
reg_code_already_redeemed = RegistrationCodeRedemption.is_registration_code_redeemed(registration_code)
|
||||
if not reg_code_is_valid:
|
||||
# tick the rate limiter counter
|
||||
AUDIT_LOG.info("Redemption of a non existing RegistrationCode {code}".format(code=registration_code))
|
||||
AUDIT_LOG.info("Redemption of a invalid RegistrationCode %s", registration_code)
|
||||
limiter.tick_bad_request_counter(request)
|
||||
raise Http404()
|
||||
|
||||
@@ -430,15 +427,24 @@ def _is_enrollment_code_an_update(course, user, redemption_code):
|
||||
def use_registration_code(course_reg, user):
|
||||
"""
|
||||
This method utilize course registration code.
|
||||
If the registration code is invalid, it returns an error.
|
||||
If the registration code is already redeemed, it returns an error.
|
||||
Else, it identifies and removes the applicable OrderItem from the Order
|
||||
and redirects the user to the Registration code redemption page.
|
||||
"""
|
||||
if RegistrationCodeRedemption.is_registration_code_redeemed(course_reg):
|
||||
log.warning(u"Registration code '%s' already used", course_reg.code)
|
||||
if not course_reg.is_valid:
|
||||
log.warning(u"The enrollment code (%s) is no longer valid.", course_reg.code)
|
||||
return HttpResponseBadRequest(
|
||||
_("Oops! The code '{registration_code}' you entered is either invalid or expired").format(
|
||||
registration_code=course_reg.code
|
||||
_("This enrollment code ({enrollment_code}) is no longer valid.").format(
|
||||
enrollment_code=course_reg.code
|
||||
)
|
||||
)
|
||||
|
||||
if RegistrationCodeRedemption.is_registration_code_redeemed(course_reg.code):
|
||||
log.warning(u"This enrollment code ({%s}) has already been used.", course_reg.code)
|
||||
return HttpResponseBadRequest(
|
||||
_("This enrollment code ({enrollment_code}) is not valid.").format(
|
||||
enrollment_code=course_reg.code
|
||||
)
|
||||
)
|
||||
try:
|
||||
@@ -893,6 +899,7 @@ def _show_receipt_html(request, order):
|
||||
'course_name': course.display_name,
|
||||
'redemption_url': reverse('register_code_redemption', args=[course_registration_code.code]),
|
||||
'code': course_registration_code.code,
|
||||
'is_valid': course_registration_code.is_valid,
|
||||
'is_redeemed': RegistrationCodeRedemption.objects.filter(
|
||||
registration_code=course_registration_code).exists(),
|
||||
})
|
||||
|
||||
@@ -27,6 +27,11 @@ var edx = edx || {};
|
||||
});
|
||||
|
||||
$(function() {
|
||||
var $registration_code_status_form = $("form#set_regcode_status_form"),
|
||||
$lookup_button = $('#lookup_regcode', $registration_code_status_form),
|
||||
$registration_code_status_form_error = $('#regcode_status_form_error', $registration_code_status_form),
|
||||
$registration_code_status_form_success = $('#regcode_status_form_success', $registration_code_status_form);
|
||||
|
||||
$( "#coupon_expiration_date" ).datepicker({
|
||||
minDate: 0
|
||||
});
|
||||
@@ -43,7 +48,7 @@ var edx = edx || {};
|
||||
return $(".reports .msg-confirm").css({
|
||||
"display": "block"
|
||||
});
|
||||
},
|
||||
},
|
||||
error: function(std_ajax_err) {
|
||||
request_response_error.text(gettext('Error generating grades. Please try again.'));
|
||||
return $(".reports .msg-error").css({
|
||||
@@ -52,5 +57,126 @@ var edx = edx || {};
|
||||
}
|
||||
});
|
||||
});
|
||||
$lookup_button.click(function () {
|
||||
$registration_code_status_form_error.hide();
|
||||
$lookup_button.attr('disabled', true);
|
||||
var url = $(this).data('endpoint');
|
||||
var lookup_registration_code = $('#set_regcode_status_form input[name="regcode_code"]').val();
|
||||
if (lookup_registration_code == '') {
|
||||
$registration_code_status_form_error.show();
|
||||
$registration_code_status_form_error.text(gettext('Enter the enrollment code.'));
|
||||
$lookup_button.removeAttr('disabled');
|
||||
return false;
|
||||
}
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
data: {
|
||||
"registration_code" : lookup_registration_code
|
||||
},
|
||||
url: url,
|
||||
success: function (data) {
|
||||
var is_registration_code_valid = data.is_registration_code_valid,
|
||||
is_registration_code_redeemed = data.is_registration_code_redeemed,
|
||||
is_registration_code_exists = data.is_registration_code_exists;
|
||||
|
||||
$lookup_button.removeAttr('disabled');
|
||||
if (is_registration_code_exists == 'false') {
|
||||
$registration_code_status_form_error.hide();
|
||||
$registration_code_status_form_error.show();
|
||||
$registration_code_status_form_error.text(gettext(data.message));
|
||||
}
|
||||
else {
|
||||
var actions_links = '';
|
||||
var actions = [];
|
||||
if (is_registration_code_valid == true) {
|
||||
actions.push(
|
||||
{
|
||||
'action_url': data.registration_code_detail_url,
|
||||
'action_name': gettext('Cancel enrollment code'),
|
||||
'registration_code': lookup_registration_code,
|
||||
'action_type': 'invalidate_registration_code'
|
||||
}
|
||||
);
|
||||
}
|
||||
else {
|
||||
actions.push(
|
||||
{
|
||||
'action_url': data.registration_code_detail_url,
|
||||
'action_name': gettext('Restore enrollment code'),
|
||||
'registration_code': lookup_registration_code,
|
||||
'action_type': 'validate_registration_code'
|
||||
}
|
||||
);
|
||||
}
|
||||
if (is_registration_code_redeemed == true) {
|
||||
actions.push(
|
||||
{
|
||||
'action_url': data.registration_code_detail_url,
|
||||
'action_name': gettext('Mark enrollment code as unused'),
|
||||
'registration_code': lookup_registration_code,
|
||||
'action_type': 'unredeem_registration_code'
|
||||
}
|
||||
);
|
||||
}
|
||||
is_registration_code_redeemed = is_registration_code_redeemed ? 'Yes' : 'No';
|
||||
is_registration_code_valid = is_registration_code_valid ? 'Yes' : 'No';
|
||||
// load the underscore template.
|
||||
var template_data = _.template($('#enrollment-code-lookup-links-tpl').text());
|
||||
var registration_code_lookup_actions = template_data(
|
||||
{
|
||||
lookup_registration_code: lookup_registration_code,
|
||||
is_registration_code_redeemed: is_registration_code_redeemed,
|
||||
is_registration_code_valid: is_registration_code_valid,
|
||||
actions: actions
|
||||
}
|
||||
);
|
||||
|
||||
// before insertAfter do this.
|
||||
// remove the first element after the registration_code_status_form
|
||||
// so it doesn't duplicate the registration_code_lookup_actions in the UI.
|
||||
$registration_code_status_form.next().remove();
|
||||
$(registration_code_lookup_actions).insertAfter($registration_code_status_form);
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
var data = $.parseJSON(jqXHR.responseText);
|
||||
$lookup_button.removeAttr('disabled');
|
||||
$registration_code_status_form_error.text(gettext(data.message));
|
||||
$registration_code_status_form_error.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
$("section#invalidate_registration_code_modal").on('click', 'a.registration_code_action_link', function(event) {
|
||||
event.preventDefault();
|
||||
$registration_code_status_form_error.attr('style', 'display: none');
|
||||
$lookup_button.attr('disabled', true);
|
||||
var url = $(this).data('endpoint');
|
||||
var action_type = $(this).data('action-type');
|
||||
var registration_code = $(this).data('registration-code');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
data: {
|
||||
"registration_code": registration_code,
|
||||
"action_type": action_type
|
||||
},
|
||||
url: url,
|
||||
success: function (data) {
|
||||
$('#set_regcode_status_form input[name="regcode_code"]').val('');
|
||||
$registration_code_status_form.next().remove();
|
||||
$registration_code_status_form_error.hide();
|
||||
$lookup_button.removeAttr('disabled');
|
||||
$registration_code_status_form_success.text(gettext(data.message));
|
||||
$registration_code_status_form_success.show();
|
||||
$registration_code_status_form_success.fadeOut(3000);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
var data = $.parseJSON(jqXHR.responseText);
|
||||
$registration_code_status_form_error.hide();
|
||||
$lookup_button.removeAttr('disabled');
|
||||
$registration_code_status_form_error.show();
|
||||
$registration_code_status_form_error.text(gettext(data.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})(Backbone, $, _, gettext);
|
||||
})(Backbone, $, _, gettext);
|
||||
|
||||
@@ -1624,7 +1624,8 @@ input[name="subject"] {
|
||||
width: 930px;
|
||||
}
|
||||
// coupon edit and add modals
|
||||
#add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal, #registration_code_generation_modal{
|
||||
#add-coupon-modal, #invalidate_registration_code_modal, #edit-coupon-modal,
|
||||
#set-course-mode-price-modal, #registration_code_generation_modal{
|
||||
.inner-wrapper {
|
||||
background: $white;
|
||||
}
|
||||
@@ -1639,7 +1640,7 @@ input[name="subject"] {
|
||||
@include margin-left(-325px);
|
||||
border-radius: 2px;
|
||||
input[type="button"]#update_coupon_button, input[type="button"]#add_coupon_button,
|
||||
input[type="button"]#set_course_button {
|
||||
input[type="button"]#set_course_button, input[type="button"]#lookup_regcode {
|
||||
@include button(simple, $blue);
|
||||
@extend .button-reset;
|
||||
display: block;
|
||||
@@ -1689,6 +1690,25 @@ input[name="subject"] {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
}
|
||||
table.tb_registration_code_status{
|
||||
margin-top: $baseline;
|
||||
color: #555;
|
||||
thead {
|
||||
font-size: 14px;
|
||||
tr th:last-child {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
font-size: 14px;
|
||||
tr td:last-child {
|
||||
text-align: center;
|
||||
a:first-child{
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
form#generate_codes ol.list-input{
|
||||
li{
|
||||
width: 278px;
|
||||
@@ -1763,7 +1783,7 @@ input[name="subject"] {
|
||||
height: 40px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
#coupon-content, #course-content, #registration-content {
|
||||
#coupon-content, #course-content, #registration-content, #regcode-content {
|
||||
padding: $baseline;
|
||||
header {
|
||||
margin: 0;
|
||||
|
||||
@@ -6,16 +6,20 @@
|
||||
<%include file="edit_coupon_modal.html" args="section_data=section_data" />
|
||||
<%include file="set_course_mode_price_modal.html" args="section_data=section_data" />
|
||||
<%include file="generate_registarion_codes_modal.html" args="section_data=section_data" />
|
||||
<%include file="invalidate_registration_code_modal.html" args="section_data=section_data" />
|
||||
<div class="ecommerce-wrapper">
|
||||
<h3 class="error-msgs" id="error-msg"></h3>
|
||||
<div id = "accordion">
|
||||
<div class="wrap">
|
||||
<h2>${_('Registration Codes')}</h2>
|
||||
<h2>${_('Enrollment Codes')}</h2>
|
||||
<div>
|
||||
%if section_data['sales_admin']:
|
||||
<span class="code_tip">
|
||||
<p>${_('Create one or more pre-paid course enrollment codes. Students can use these codes to enroll in the course.')}</p>
|
||||
<a id="registration_code_generation_link" href="#reg_code_generation_modal" class="add blue-button">${_('Create Enrollment Codes')}</a>
|
||||
<p></p>
|
||||
<p>${_('Cancel, restore, or mark an enrollment code as unused.')}</p>
|
||||
<a id="query_registration_code_link" href="#invalidate_registration_code_modal" rel="leanModal" class="add blue-button">${_('Change Enrollment Code Status')}</a>
|
||||
</span>
|
||||
%endif
|
||||
<p>${_('Download a .csv file of all enrollment codes for this course')}</p>
|
||||
@@ -464,6 +468,9 @@
|
||||
$('#course_price_link').click(function () {
|
||||
reset_input_fields();
|
||||
});
|
||||
$('#query_registration_code_link').click(function () {
|
||||
reset_input_fields();
|
||||
});
|
||||
$('#add_coupon_link').click(function () {
|
||||
reset_input_fields();
|
||||
});
|
||||
@@ -579,6 +586,8 @@
|
||||
$("#edit-coupon-modal").attr("aria-hidden", "true");
|
||||
$(".edit-right").focus();
|
||||
$("#set-course-mode-price-modal").attr("aria-hidden", "true");
|
||||
$("#invalidate_registration_code_modal").attr("aria-hidden", "true");
|
||||
|
||||
$("#registration_code_generation_modal").attr("aria-hidden", "true");
|
||||
$("#add_coupon_button").removeAttr('disabled');
|
||||
$("#set_course_button").removeAttr('disabled');
|
||||
@@ -602,10 +611,11 @@
|
||||
$("#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);
|
||||
$("#invalidate_registration_code_modal .close-modal").click(reset_input_fields);
|
||||
|
||||
|
||||
// Hitting the ESC key will exit the modal
|
||||
$("#add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal, #registration_code_generation_modal").on("keydown", function (e) {
|
||||
$("#add-coupon-modal, #edit-coupon-modal, #set-course-mode-price-modal, #invalidate_registration_code_modal, #registration_code_generation_modal").on("keydown", function (e) {
|
||||
var keyCode = e.keyCode || e.which;
|
||||
// 27 is the ESC key
|
||||
if (keyCode === 27) {
|
||||
@@ -613,14 +623,20 @@
|
||||
$("#add-coupon-modal .close-modal").click();
|
||||
$("#set-course-mode-price-modal .close-modal").click();
|
||||
$("#edit-coupon-modal .close-modal").click();
|
||||
$("#invalidate_registration_code_modal .close-modal").click();
|
||||
|
||||
$('#registration_code_generation_modal .close-modal').click();
|
||||
}
|
||||
});
|
||||
});
|
||||
var reset_input_fields = function () {
|
||||
$('#error-msg').val('');
|
||||
$('#error-msg').hide()
|
||||
$('#error-msg').hide();
|
||||
$('#add_coupon_form #coupon_form_error').attr('style', 'display: none');
|
||||
$("form#set_regcode_status_form").next().remove();
|
||||
$('#set_regcode_status_form #regcode_status_form_error').attr('style', 'display: none');
|
||||
$('#set_regcode_status_form #regcode_status_form_success').attr('style', 'display: none');
|
||||
$('#set_regcode_status_form input#lookup_regcode').removeAttr('disabled');
|
||||
$('#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();
|
||||
@@ -629,6 +645,7 @@
|
||||
$('input#coupon_discount').val('');
|
||||
$('textarea#coupon_description').val('');
|
||||
$('input[name="company_name"]').val('');
|
||||
$('input[name="regcode_code"]').val('');
|
||||
$('input[name="total_registration_codes"]').val('');
|
||||
$('input[name="address_line_1"]').val('');
|
||||
$('input[name="address_line_2"]').val('');
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<table width="100%" class="tb_registration_code_status">
|
||||
<thead>
|
||||
<th width="15%"> <%- gettext("Code") %> </th>
|
||||
<th width="20%"> <%- gettext("Used") %> </th>
|
||||
<th width="14%"> <%- gettext("Valid") %> </th>
|
||||
<th> <%- gettext("Actions") %> </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> <%- lookup_registration_code %> </td>
|
||||
<td> <%- is_registration_code_redeemed %> </td>
|
||||
<td> <%- is_registration_code_valid %> </td>
|
||||
<td>
|
||||
<% _.each(actions, function(action){ %>
|
||||
<a class="registration_code_action_link" data-registration-code="<%= action.registration_code %>" data-action-type="<%= action.action_type %>" href="#" data-endpoint="<%= action.action_url %>">
|
||||
<%- action.action_name %>
|
||||
</a>
|
||||
<% }); %>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
## Include Underscore templates
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["cohorts", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification", "cohort-state", "cohort-discussions-inline", "cohort-discussions-course-wide", "cohort-discussions-category","cohort-discussions-subcategory"]:
|
||||
% for template_name in ["cohorts", "enrollment-code-lookup-links", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification", "cohort-state", "cohort-discussions-inline", "cohort-discussions-course-wide", "cohort-discussions-category","cohort-discussions-subcategory"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" />
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%page args="section_data"/>
|
||||
<section id="invalidate_registration_code_modal" class="modal" role="dialog" tabindex="-1" aria-label="${_('Enrollment Code Status')}">
|
||||
<div class="inner-wrapper">
|
||||
<button class="close-modal">
|
||||
<i class="icon fa fa-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="regcode-content">
|
||||
<header>
|
||||
<h2>${_("Enrollment Code Status")}</h2>
|
||||
</header>
|
||||
|
||||
<div class="instructions">
|
||||
<p>
|
||||
${_("Change the status of an enrollment code.")}</p>
|
||||
</div>
|
||||
<form id="set_regcode_status_form">
|
||||
<div id="regcode_status_form_error" class="modal-form-error"></div>
|
||||
<div class="success-msgs" id="regcode_status_form_success"></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="set-course-mode-modal-field-price">
|
||||
<label for="regcode_code">${_("Enrollment Code")}</label>
|
||||
<input class="field" id="regcode_code" type="text" name="regcode_code" placeholder="Enter an Enrollment Code" aria-required="true">
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
<div class="submit">
|
||||
<input name="submit" type="button" id="lookup_regcode" data-endpoint="${section_data['look_up_registration_code']}" value="${_('Find Enrollment Code')}"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -84,6 +84,8 @@ from courseware.courses import course_image_url, get_course_about_section, get_c
|
||||
<td>
|
||||
% if reg_code_info['is_redeemed']:
|
||||
<span class="red"></M>${_("Used")}</span>
|
||||
% elif not reg_code_info['is_valid']:
|
||||
<span class="red"></M>${_("Invalid")}</span>
|
||||
% else:
|
||||
<span class="green"></M>${_("Available")}</span>
|
||||
% endif
|
||||
|
||||
14
lms/urls.py
14
lms/urls.py
@@ -237,6 +237,19 @@ if settings.WIKI_ENABLED:
|
||||
)
|
||||
|
||||
if settings.COURSEWARE_ENABLED:
|
||||
COURSE_URLS = patterns(
|
||||
'',
|
||||
url(
|
||||
r'^look_up_registration_code$',
|
||||
'instructor.views.registration_codes.look_up_registration_code',
|
||||
name='look_up_registration_code'
|
||||
),
|
||||
url(
|
||||
r'^registration_code_details$',
|
||||
'instructor.views.registration_codes.registration_code_details',
|
||||
name='registration_code_details'
|
||||
)
|
||||
)
|
||||
urlpatterns += (
|
||||
url(r'^courses/{}/jump_to/(?P<location>.*)$'.format(settings.COURSE_ID_PATTERN),
|
||||
'courseware.views.jump_to', name="jump_to"),
|
||||
@@ -357,6 +370,7 @@ if settings.COURSEWARE_ENABLED:
|
||||
url(r'^courses/{}/get_coupon_info$'.format(settings.COURSE_ID_PATTERN),
|
||||
'instructor.views.coupons.get_coupon_info', name="get_coupon_info"),
|
||||
|
||||
url(r'^courses/{}/'.format(settings.COURSE_ID_PATTERN), include(COURSE_URLS)),
|
||||
# see ENABLE_INSTRUCTOR_LEGACY_DASHBOARD section for legacy dash urls
|
||||
|
||||
# Open Ended grading views
|
||||
|
||||
Reference in New Issue
Block a user