Can check verified-ness and expiration date
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
Add and create new modes for running courses on this particular LMS
|
||||
"""
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date
|
||||
|
||||
from django.db import models
|
||||
from collections import namedtuple
|
||||
@@ -101,6 +101,17 @@ class CourseMode(models.Model):
|
||||
modes = cls.modes_for_course(course_id)
|
||||
return min(mode.min_price for mode in modes if mode.currency == currency)
|
||||
|
||||
@classmethod
|
||||
def refund_expiration_date(cls, course_id, mode_slug):
|
||||
"""
|
||||
Returns the expiration date for verified certificate refunds. After this date, refunds are
|
||||
no longer possible. Note that this is currently set to be identical to the expiration date for
|
||||
verified cert signups, but this could be changed in the future
|
||||
"""
|
||||
print "TODO fix this"
|
||||
return date(1990, 1, 1)
|
||||
#return cls.mode_for_course(course_id,mode_slug).expiration_date
|
||||
|
||||
def __unicode__(self):
|
||||
return u"{} : {}, min={}, prices={}".format(
|
||||
self.course_id, self.mode_slug, self.min_price, self.suggested_prices
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from course_modes.models import CourseMode
|
||||
from factory import DjangoModelFactory
|
||||
import datetime
|
||||
|
||||
# Factories don't have __init__ methods, and are self documenting
|
||||
# pylint: disable=W0232
|
||||
@@ -11,3 +12,4 @@ class CourseModeFactory(DjangoModelFactory):
|
||||
mode_display_name = 'audit course'
|
||||
min_price = 0
|
||||
currency = 'usd'
|
||||
expiration_date = datetime.date(1990, 1, 1)
|
||||
|
||||
@@ -5,7 +5,7 @@ when you run "manage.py test".
|
||||
Replace this with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, date, timedelta
|
||||
import pytz
|
||||
|
||||
from django.test import TestCase
|
||||
@@ -20,6 +20,7 @@ class CourseModeModelTest(TestCase):
|
||||
def setUp(self):
|
||||
self.course_id = 'TestCourse'
|
||||
CourseMode.objects.all().delete()
|
||||
#todo use different default date
|
||||
|
||||
def create_mode(self, mode_slug, mode_name, min_price=0, suggested_prices='', currency='usd'):
|
||||
"""
|
||||
@@ -31,7 +32,7 @@ class CourseModeModelTest(TestCase):
|
||||
mode_slug=mode_slug,
|
||||
min_price=min_price,
|
||||
suggested_prices=suggested_prices,
|
||||
currency=currency
|
||||
currency=currency,
|
||||
)
|
||||
|
||||
def test_modes_for_course_empty(self):
|
||||
@@ -112,3 +113,9 @@ class CourseModeModelTest(TestCase):
|
||||
|
||||
modes = CourseMode.modes_for_course('second_test_course')
|
||||
self.assertEqual([CourseMode.DEFAULT_MODE], modes)
|
||||
|
||||
def test_refund_expiration_date(self):
|
||||
self.create_mode('verified', 'Verified Certificate')
|
||||
modes = CourseMode.modes_for_course(self.course_id)
|
||||
mode = Mode(u'verified', u'Verified Certificate', 0, '', 'usd')
|
||||
self.assertEqual(CourseMode.refund_expiration_date(self.course_id, 'verified'), date(1990, 1, 1))
|
||||
|
||||
@@ -422,3 +422,28 @@ class PaidRegistrationTest(ModuleStoreTestCase):
|
||||
self.assertEqual(response.content, reverse('shoppingcart.views.show_cart'))
|
||||
self.assertTrue(shoppingcart.models.PaidCourseRegistration.contained_in_order(
|
||||
shoppingcart.models.Order.get_cart_for_user(self.user), self.course.id))
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
|
||||
class CertificateItemTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for paid certificate functionality (verified student), involves shoppingcart
|
||||
"""
|
||||
# test data
|
||||
COURSE_SLUG = "100"
|
||||
COURSE_NAME = "test_course"
|
||||
COURSE_ORG = "EDX"
|
||||
|
||||
def setUp(self):
|
||||
# Create course
|
||||
self.req_factory = RequestFactory()
|
||||
self.course = CourseFactory.create(org=self.COURSE_ORG, display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
|
||||
self.assertIsNotNone(self.course)
|
||||
self.user = User.objects.create(username="test", email="test@test.org")
|
||||
|
||||
def test_unenroll_and_refund(self):
|
||||
request = self.req_factory.post(reverse('change_enrollment'), {'course_id': self.course.id, 'enrollment_action': 'unenroll'})
|
||||
request.user = self.user
|
||||
response = change_enrollment(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# add more later; see if this even works
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Student Views
|
||||
"""
|
||||
import datetime
|
||||
from datetime import date
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
@@ -65,6 +66,7 @@ import external_auth.views
|
||||
|
||||
from bulk_email.models import Optout, CourseAuthorization
|
||||
import shoppingcart
|
||||
from shoppingcart.models import (Order, OrderItem, CertificateItem)
|
||||
|
||||
import track.views
|
||||
|
||||
@@ -300,6 +302,7 @@ def dashboard(request):
|
||||
# exist (because the course IDs have changed). Still, we don't delete those
|
||||
# enrollments, because it could have been a data push snafu.
|
||||
courses = []
|
||||
refund_status = []
|
||||
for enrollment in CourseEnrollment.enrollments_for_user(user):
|
||||
try:
|
||||
courses.append((course_from_id(enrollment.course_id), enrollment))
|
||||
@@ -335,15 +338,19 @@ def dashboard(request):
|
||||
CourseAuthorization.instructor_email_enabled(course.id)
|
||||
)
|
||||
)
|
||||
|
||||
# Verification Attempts
|
||||
verification_status, verification_msg = SoftwareSecurePhotoVerification.user_status(user)
|
||||
|
||||
show_refund_option_for = frozenset(course.id for course, _enrollment in courses
|
||||
if (has_access(request.user, course, 'refund') and (_enrollment.mode == "verified")))
|
||||
|
||||
# get info w.r.t ExternalAuthMap
|
||||
external_auth_map = None
|
||||
try:
|
||||
external_auth_map = ExternalAuthMap.objects.get(user=user)
|
||||
except ExternalAuthMap.DoesNotExist:
|
||||
pass
|
||||
|
||||
context = {'courses': courses,
|
||||
'course_optouts': course_optouts,
|
||||
'message': message,
|
||||
@@ -356,6 +363,7 @@ def dashboard(request):
|
||||
'show_email_settings_for': show_email_settings_for,
|
||||
'verification_status': verification_status,
|
||||
'verification_msg': verification_msg,
|
||||
'show_refund_option_for': show_refund_option_for,
|
||||
}
|
||||
|
||||
return render_to_response('dashboard.html', context)
|
||||
@@ -424,6 +432,8 @@ def change_enrollment(request):
|
||||
.format(user.username, course_id))
|
||||
return HttpResponseBadRequest(_("Course id is invalid"))
|
||||
|
||||
course = course_from_id(course_id)
|
||||
|
||||
if not has_access(user, course, 'enroll'):
|
||||
return HttpResponseBadRequest(_("Enrollment is closed"))
|
||||
|
||||
@@ -464,19 +474,42 @@ def change_enrollment(request):
|
||||
|
||||
elif action == "unenroll":
|
||||
try:
|
||||
CourseEnrollment.unenroll(user, course_id)
|
||||
course = course_from_id(course_id)
|
||||
except ItemNotFoundError:
|
||||
log.warning("User {0} tried to unenroll from non-existent course {1}"
|
||||
.format(user.username, course_id))
|
||||
return HttpResponseBadRequest(_("Course id is invalid"))
|
||||
|
||||
org, course_num, run = course_id.split("/")
|
||||
dog_stats_api.increment(
|
||||
"common.student.unenrollment",
|
||||
tags=["org:{0}".format(org),
|
||||
"course:{0}".format(course_num),
|
||||
"run:{0}".format(run)]
|
||||
)
|
||||
course = course_from_id(course_id)
|
||||
verified = CourseEnrollment.enrollment_mode_for_user(user, course_id)
|
||||
# did they sign up for verified certs?
|
||||
if(verified):
|
||||
|
||||
return HttpResponse()
|
||||
except CourseEnrollment.DoesNotExist:
|
||||
return HttpResponseBadRequest(_("You are not enrolled in this course"))
|
||||
# If the user is allowed a refund, do so
|
||||
if has_access(user, course, 'refund'):
|
||||
subject = _("[Refund] User-Requested Refund")
|
||||
# todo: make this reference templates/student/refund_email.html
|
||||
message = "Important info here."
|
||||
to_email = [settings.PAYMENT_SUPPORT_EMAIL]
|
||||
from_email = "support@edx.org"
|
||||
try:
|
||||
send_mail(subject, message, from_email, to_email, fail_silently=False)
|
||||
except:
|
||||
log.warning('Unable to send reimbursement request to billing', exc_info=True)
|
||||
js['value'] = _('Could not send reimbursement request.')
|
||||
return HttpResponse(json.dumps(js))
|
||||
# email has been sent, let's deal with the order now
|
||||
CertificateItem.refund_cert(user, course_id)
|
||||
CourseEnrollment.unenroll(user, course_id)
|
||||
|
||||
org, course_num, run = course_id.split("/")
|
||||
dog_stats_api.increment(
|
||||
"common.student.unenrollment",
|
||||
tags=["org:{0}".format(org),
|
||||
"course:{0}".format(course_num),
|
||||
"run:{0}".format(run)]
|
||||
)
|
||||
return HttpResponse()
|
||||
else:
|
||||
return HttpResponseBadRequest(_("Enrollment action is invalid"))
|
||||
|
||||
@@ -891,7 +924,7 @@ def create_account(request, post_override=None):
|
||||
subject = ''.join(subject.splitlines())
|
||||
message = render_to_string('emails/activation_email.txt', d)
|
||||
|
||||
# dont send email if we are doing load testing or random user generation for some reason
|
||||
# don't send email if we are doing load testing or random user generation for some reason
|
||||
if not (settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING')):
|
||||
try:
|
||||
if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Ideally, it will be the only place that needs to know about any special settings
|
||||
like DISABLE_START_DATES"""
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, date
|
||||
from functools import partial
|
||||
|
||||
from django.conf import settings
|
||||
@@ -202,11 +202,33 @@ def _has_access_course_desc(user, course, action):
|
||||
|
||||
return can_enroll() or can_load()
|
||||
|
||||
def can_refund():
|
||||
"""
|
||||
For paid/verified certificates, students may receive a refund IFF the deadline
|
||||
for refunds has not yet passed. Note that this function *only* checks whether
|
||||
or not that deadline has passed; checking whether the student actually *purchased*
|
||||
a paid/verified certificate must be done elsewhere.
|
||||
"""
|
||||
now = datetime.now(UTC())
|
||||
course_start = course.enrollment_start
|
||||
# If there *is* no start date, user can be refunded
|
||||
if course_start is None:
|
||||
return True
|
||||
# Presently, refunds are only allowed up to two weeks after the course
|
||||
# start date.
|
||||
grace_period = timedelta(days=14)
|
||||
refund_end = course_start + grace_period
|
||||
if (now.date() <= refund_end.date()):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
checkers = {
|
||||
'load': can_load,
|
||||
'load_forum': can_load_forum,
|
||||
'enroll': can_enroll,
|
||||
'see_exists': see_exists,
|
||||
'refund': can_refund,
|
||||
'staff': lambda: _has_staff_access_to_descriptor(user, course),
|
||||
'instructor': lambda: _has_instructor_access_to_descriptor(user, course),
|
||||
}
|
||||
|
||||
@@ -106,3 +106,20 @@ class AccessTestCase(TestCase):
|
||||
|
||||
# TODO:
|
||||
# Non-staff cannot enroll outside the open enrollment period if not specifically allowed
|
||||
|
||||
def test__has_access_refund(self):
|
||||
u = Mock()
|
||||
today = datetime.datetime.now(UTC())
|
||||
grace_period = datetime.timedelta(days=14)
|
||||
one_day_extra = datetime.timedelta(days=1)
|
||||
|
||||
# User is allowed to receive refund if it is within two weeks of course start date
|
||||
c = Mock(enrollment_start=(today-one_day_extra), id='edX/tests/Whenever')
|
||||
self.assertTrue(access._has_access_course_desc(u, c, 'refund'))
|
||||
|
||||
c = Mock(enrollment_start=(today-grace_period), id='edX/test/Whenever')
|
||||
self.assertTrue(access._has_access_course_desc(u, c, 'refund'))
|
||||
|
||||
# After two weeks, user may no longer receive a refund
|
||||
c = Mock(enrollment_start=(today-grace_period-one_day_extra), id='edX/test/Whenever')
|
||||
self.assertFalse(access._has_access_course_desc(u, c, 'refund'))
|
||||
|
||||
@@ -9,7 +9,7 @@ from boto.exception import BotoServerError # this is a super-class of SESError
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.exceptions import (ObjectDoesNotExist, MultipleObjectsReturned)
|
||||
from django.core.mail import send_mail
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -22,7 +22,6 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
from student.views import course_from_id
|
||||
from student.models import CourseEnrollment
|
||||
from verify_student.models import SoftwareSecurePhotoVerification
|
||||
|
||||
@@ -34,13 +33,19 @@ log = logging.getLogger("shoppingcart")
|
||||
ORDER_STATUSES = (
|
||||
('cart', 'cart'),
|
||||
('purchased', 'purchased'),
|
||||
('refunded', 'refunded'), # Not used for now
|
||||
('refunded', 'refunded'),
|
||||
)
|
||||
|
||||
# we need a tuple to represent the primary key of various OrderItem subclasses
|
||||
OrderItemSubclassPK = namedtuple('OrderItemSubclassPK', ['cls', 'pk']) # pylint: disable=C0103
|
||||
|
||||
|
||||
def course_from_id(course_id):
|
||||
"""Return the CourseDescriptor corresponding to this course_id"""
|
||||
course_loc = CourseDescriptor.id_to_location(course_id)
|
||||
return modulestore().get_instance(course_id, course_loc)
|
||||
|
||||
|
||||
class Order(models.Model):
|
||||
"""
|
||||
This is the model for an order. Before purchase, an Order and its related OrderItems are used
|
||||
@@ -398,6 +403,23 @@ class CertificateItem(OrderItem):
|
||||
course_enrollment = models.ForeignKey(CourseEnrollment)
|
||||
mode = models.SlugField()
|
||||
|
||||
@classmethod
|
||||
def refund_cert(cls, target_user, target_course_id):
|
||||
try:
|
||||
target_cert = CertificateItem.objects.get(course_id=target_course_id, user_id=target_user, status='purchased', mode='verified')
|
||||
target_cert.status = 'refunded'
|
||||
# todo return success
|
||||
return target_cert
|
||||
except MultipleObjectsReturned:
|
||||
# this seems like a thing that shouldn't happen
|
||||
log.exception("Multiple entries for single verified cert found")
|
||||
# but we can recover; select one item and refund it
|
||||
# todo
|
||||
except ObjectDoesNotExist:
|
||||
# todo log properly
|
||||
log.exception("No certificate found")
|
||||
# handle the exception
|
||||
|
||||
@classmethod
|
||||
@transaction.commit_on_success
|
||||
def add_to_order(cls, order, course_id, cost, mode, currency='usd'):
|
||||
|
||||
@@ -20,6 +20,7 @@ from student.tests.factories import UserFactory
|
||||
from student.models import CourseEnrollment
|
||||
from course_modes.models import CourseMode
|
||||
from shoppingcart.exceptions import PurchasedCallbackException
|
||||
from django.core.exceptions import (ObjectDoesNotExist, MultipleObjectsReturned)
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
@@ -360,3 +361,27 @@ class CertificateItemTest(ModuleStoreTestCase):
|
||||
cert_item = CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
|
||||
self.assertEquals(cert_item.single_item_receipt_template,
|
||||
'shoppingcart/receipt.html')
|
||||
|
||||
def test_refund_cert_single_cert(self):
|
||||
# enroll and buy; dup from test_existing_enrollment
|
||||
CourseEnrollment.enroll(self.user, self.course_id)
|
||||
cart = Order.get_cart_for_user(user=self.user)
|
||||
CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified')
|
||||
cart.purchase()
|
||||
enrollment = CourseEnrollment.objects.get(user=self.user, course_id=self.course_id)
|
||||
# now that it's there, let's try refunding it
|
||||
order = CertificateItem.refund_cert(target_user=self.user, target_course_id=self.course_id)
|
||||
self.assertEquals(order.status, 'refunded')
|
||||
|
||||
def test_refund_cert_no_cert_exists(self):
|
||||
order = CertificateItem.refund_cert(target_user=self.user, target_course_id=self.course_id)
|
||||
self.assertRaises(ObjectDoesNotExist)
|
||||
|
||||
def test_refund_cert_duplicate_certs_exist(self):
|
||||
for i in range(0, 2):
|
||||
CourseEnrollment.enroll(self.user, self.course_id)
|
||||
cart = Order.get_cart_for_user(user=self.user)
|
||||
CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified')
|
||||
cart.purchase()
|
||||
enrollment = CourseEnrollment.objects.get(user=self.user, course_id=self.course_id)
|
||||
self.assertRaises(MultipleObjectsReturned)
|
||||
|
||||
@@ -20,6 +20,7 @@ USE_I18N = True
|
||||
TEMPLATE_DEBUG = True
|
||||
|
||||
|
||||
MITX_FEATURES['ENABLE_PAYMENT_FAKE'] = True
|
||||
MITX_FEATURES['DISABLE_START_DATES'] = False
|
||||
MITX_FEATURES['ENABLE_SQL_TRACKING_LOGS'] = True
|
||||
MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False # Enable to test subdomains--otherwise, want all courses to show up
|
||||
@@ -269,7 +270,7 @@ if SEGMENT_IO_LMS_KEY:
|
||||
CC_PROCESSOR['CyberSource']['SHARED_SECRET'] = os.environ.get('CYBERSOURCE_SHARED_SECRET', '')
|
||||
CC_PROCESSOR['CyberSource']['MERCHANT_ID'] = os.environ.get('CYBERSOURCE_MERCHANT_ID', '')
|
||||
CC_PROCESSOR['CyberSource']['SERIAL_NUMBER'] = os.environ.get('CYBERSOURCE_SERIAL_NUMBER', '')
|
||||
CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = os.environ.get('CYBERSOURCE_PURCHASE_ENDPOINT', '')
|
||||
CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = '/shoppingcart/payment_fake/'
|
||||
|
||||
|
||||
########################## USER API ########################
|
||||
|
||||
@@ -190,7 +190,8 @@
|
||||
<% cert_status = cert_statuses.get(course.id) %>
|
||||
<% show_email_settings = (course.id in show_email_settings_for) %>
|
||||
<% course_mode_info = all_course_modes.get(course.id) %>
|
||||
<%include file='dashboard/_dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info" />
|
||||
<% show_refund_option = (course.id in show_refund_option_for) %>
|
||||
<%include file='dashboard/dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option = show_refund_option" />
|
||||
% endfor
|
||||
|
||||
</ul>
|
||||
@@ -244,26 +245,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="unenroll-modal" class="modal unenroll-modal" aria-hidden="true">
|
||||
<div class="inner-wrapper" role="alertdialog" aria-labelledy="unenrollment-modal-title">
|
||||
<button class="close-modal">✕ <span class="sr">${_('Close Modal')}</span></button>
|
||||
|
||||
<header>
|
||||
<h2 id="unenrollment-modal-title">${_('Are you sure you want to unregister from {course_number}?').format(course_number='<span id="unenroll_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
|
||||
<hr/>
|
||||
</header>
|
||||
|
||||
<div id="unenroll_error" class="modal-form-error"></div>
|
||||
|
||||
<form id="unenroll_form" method="post" data-remote="true" action="${reverse('change_enrollment')}">
|
||||
<input name="course_id" id="unenroll_course_id" type="hidden" />
|
||||
<input name="enrollment_action" type="hidden" value="unenroll" />
|
||||
<div class="submit">
|
||||
<input name="submit" type="submit" value="${_('Unregister')}" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="password_reset_complete" class="modal" aria-hidden="true">
|
||||
<div class="inner-wrapper" role="dialog" aria-labelledby="password-reset-email">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings, course_mode_info" />
|
||||
<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings, course_mode_info, show_refund_option" />
|
||||
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%!
|
||||
@@ -143,10 +143,41 @@
|
||||
|
||||
<a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id}" data-course-number="${course.number}">${_('Unregister')}</a>
|
||||
|
||||
|
||||
|
||||
% if show_email_settings:
|
||||
<a href="#email-settings-modal" class="email-settings" rel="leanModal" data-course-id="${course.id}" data-course-number="${course.number}" data-optout="${course.id in course_optouts}">${_('Email Settings')}</a>
|
||||
% endif
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
</article>
|
||||
</li>
|
||||
|
||||
<section id="unenroll-modal" class="modal unenroll-modal" aria-hidden="true">
|
||||
<div class="inner-wrapper" role="alertdialog" aria-labelledy="unenrollment-modal-title">
|
||||
<button class="close-modal">✕ <span class="sr">${_('Close Modal')}</span></button>
|
||||
|
||||
<header>
|
||||
% if enrollment.mode != "verified":
|
||||
<h2 id="unenrollment-modal-title">${_('Are you sure you want to unregister from {course_number}?').format(course_number='<span id="unenroll_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
|
||||
% elif show_refund_option:
|
||||
<h2 id="unenrollment-modal-title">${_('Are you sure you want to unregister from {course_number}? You will be refunded for the amount paid for the verified certificate.').format(course_number='<span id="unenroll_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
|
||||
% else:
|
||||
<h2 id="unenrollment-modal-title">${_('Are you sure you want to unregister from {course_number}? The deadline for verified certificate refunds has passed, so you will not receive any money back.').format(course_number='<span id="unenroll_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
|
||||
% endif
|
||||
<hr/>
|
||||
</header>
|
||||
|
||||
<div id="unenroll_error" class="modal-form-error"></div>
|
||||
|
||||
<form id="unenroll_form" method="post" data-remote="true" action="${reverse('change_enrollment')}">
|
||||
<input name="course_id" id="unenroll_course_id" type="hidden" />
|
||||
<input name="enrollment_action" type="hidden" value="unenroll" />
|
||||
<div class="submit">
|
||||
<input name="submit" type="submit" value="${_('Unregister')}" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user