Commit bfe75276 by chrisndodge

Merge pull request #8292 from edx/muhhshoaib/SOL-916-invalidate-revalidate-registration-codes

SOL 916 invalidate revalidate registration codes
parents f78be26a 4dff8ecf
......@@ -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,
......
"""
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')
......
......@@ -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 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):
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);
\ No newline at end of file
})(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;
......
......@@ -8,16 +8,20 @@ import pytz
<%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>
......@@ -466,6 +470,9 @@ import pytz
$('#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();
});
......@@ -581,6 +588,8 @@ import pytz
$("#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');
......@@ -604,10 +613,11 @@ import pytz
$("#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) {
......@@ -615,14 +625,20 @@ import pytz
$("#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();
......@@ -631,6 +647,7 @@ import pytz
$('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('');
......
<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>
......@@ -77,7 +77,7 @@ from django.core.urlresolvers import reverse
## 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>
......
<%! 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 microsite_configuration import microsite
<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
......
......@@ -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
......
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