Adapt flow for new 'professional' mode.
ECOM-41
This commit is contained in:
committed by
Julia Hansbrough
parent
805c325653
commit
9aa095dd21
@@ -112,6 +112,21 @@ class CourseMode(models.Model):
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def verified_mode_for_course(cls, course_id):
|
||||
"""
|
||||
Since we have two separate modes that can go through the verify flow,
|
||||
we want to be able to select the 'correct' verified mode for a given course.
|
||||
|
||||
Currently, we prefer to return the professional mode over the verified one
|
||||
if both exist for the given course.
|
||||
"""
|
||||
modes_dict = cls.modes_for_course_dict(course_id)
|
||||
verified_mode = modes_dict.get('verified', None)
|
||||
professional_mode = modes_dict.get('professional', None)
|
||||
# we prefer professional over verify
|
||||
return professional_mode if professional_mode else verified_mode
|
||||
|
||||
@classmethod
|
||||
def min_course_price_for_verified_for_currency(cls, course_id, currency):
|
||||
"""
|
||||
|
||||
@@ -113,3 +113,17 @@ class CourseModeModelTest(TestCase):
|
||||
|
||||
modes = CourseMode.modes_for_course(SlashSeparatedCourseKey('TestOrg', 'TestCourse', 'TestRun'))
|
||||
self.assertEqual([CourseMode.DEFAULT_MODE], modes)
|
||||
|
||||
def test_verified_mode_for_course(self):
|
||||
self.create_mode('verified', 'Verified Certificate')
|
||||
|
||||
mode = CourseMode.verified_mode_for_course(self.course_key)
|
||||
|
||||
self.assertEqual(mode.slug, 'verified')
|
||||
|
||||
# verify that the professional mode is preferred
|
||||
self.create_mode('professional', 'Professional Education Verified Certificate')
|
||||
|
||||
mode = CourseMode.verified_mode_for_course(self.course_key)
|
||||
|
||||
self.assertEqual(mode.slug, 'professional')
|
||||
|
||||
@@ -59,8 +59,6 @@ class ChooseModeView(View):
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
donation_for_course = request.session.get("donation_for_course", {})
|
||||
chosen_price = donation_for_course.get(course_key, None)
|
||||
|
||||
@@ -135,11 +133,6 @@ class ChooseModeView(View):
|
||||
donation_for_course = request.session.get("donation_for_course", {})
|
||||
donation_for_course[course_key] = amount_value
|
||||
request.session["donation_for_course"] = donation_for_course
|
||||
if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
|
||||
return redirect(
|
||||
reverse('verify_student_verified',
|
||||
kwargs={'course_id': course_key.to_deprecated_string()}) + "?upgrade={}".format(upgrade)
|
||||
)
|
||||
|
||||
return redirect(
|
||||
reverse('verify_student_show_requirements',
|
||||
|
||||
@@ -701,8 +701,13 @@ class CertificateItem(OrderItem):
|
||||
item.qty = 1
|
||||
item.unit_cost = cost
|
||||
course_name = modulestore().get_course(course_id).display_name
|
||||
item.line_desc = _("Certificate of Achievement, {mode_name} for course {course}").format(mode_name=mode_info.name,
|
||||
course=course_name)
|
||||
# Translators: In this particular case, mode_name refers to a
|
||||
# particular mode (i.e. Honor Code Certificate, Verified Certificate, etc)
|
||||
# by which a user could enroll in the given course.
|
||||
item.line_desc = _("{mode_name} for course {course}").format(
|
||||
mode_name=mode_info.name,
|
||||
course=course_name
|
||||
)
|
||||
item.currency = currency
|
||||
order.currency = currency
|
||||
order.save()
|
||||
@@ -725,7 +730,7 @@ class CertificateItem(OrderItem):
|
||||
|
||||
@property
|
||||
def single_item_receipt_template(self):
|
||||
if self.mode == 'verified':
|
||||
if self.mode in ('verified', 'professional'):
|
||||
return 'shoppingcart/verified_cert_receipt.html'
|
||||
else:
|
||||
return super(CertificateItem, self).single_item_receipt_template
|
||||
|
||||
@@ -222,7 +222,7 @@ class ItemizedPurchaseReportTest(ModuleStoreTestCase):
|
||||
self.CORRECT_CSV = dedent("""
|
||||
Purchase Time,Order ID,Status,Quantity,Unit Cost,Total Cost,Currency,Description,Comments
|
||||
{time_str},1,purchased,1,40,40,usd,Registration for Course: Robot Super Course,Ba\xc3\xbc\xe5\x8c\x85
|
||||
{time_str},1,purchased,1,40,40,usd,"Certificate of Achievement, verified cert for course Robot Super Course",
|
||||
{time_str},1,purchased,1,40,40,usd,verified cert for course Robot Super Course,
|
||||
""".format(time_str=str(self.now)))
|
||||
|
||||
def test_purchased_items_btw_dates(self):
|
||||
|
||||
@@ -74,18 +74,27 @@ class VerifyView(View):
|
||||
# bookkeeping-wise just to start over.
|
||||
progress_state = "start"
|
||||
|
||||
modes_dict = CourseMode.modes_for_course_dict(course_id)
|
||||
verify_mode = modes_dict.get('verified', None)
|
||||
# we prefer professional over verify
|
||||
current_mode = CourseMode.verified_mode_for_course(course_id)
|
||||
|
||||
# if the course doesn't have a verified mode, we want to kick them
|
||||
# from the flow
|
||||
if not verify_mode:
|
||||
if not current_mode:
|
||||
return redirect(reverse('dashboard'))
|
||||
if course_id.to_deprecated_string() in request.session.get("donation_for_course", {}):
|
||||
chosen_price = request.session["donation_for_course"][course_id.to_deprecated_string()]
|
||||
else:
|
||||
chosen_price = verify_mode.min_price
|
||||
chosen_price = current_mode.min_price
|
||||
|
||||
course = modulestore().get_course(course_id)
|
||||
if current_mode.suggested_prices != '':
|
||||
suggested_prices = [
|
||||
decimal.Decimal(price)
|
||||
for price in current_mode.suggested_prices.split(",")
|
||||
]
|
||||
else:
|
||||
suggested_prices = []
|
||||
|
||||
context = {
|
||||
"progress_state": progress_state,
|
||||
"user_full_name": request.user.profile.name,
|
||||
@@ -95,15 +104,13 @@ class VerifyView(View):
|
||||
"course_org": course.display_org_with_default,
|
||||
"course_num": course.display_number_with_default,
|
||||
"purchase_endpoint": get_purchase_endpoint(),
|
||||
"suggested_prices": [
|
||||
decimal.Decimal(price)
|
||||
for price in verify_mode.suggested_prices.split(",")
|
||||
],
|
||||
"currency": verify_mode.currency.upper(),
|
||||
"suggested_prices": suggested_prices,
|
||||
"currency": current_mode.currency.upper(),
|
||||
"chosen_price": chosen_price,
|
||||
"min_price": verify_mode.min_price,
|
||||
"min_price": current_mode.min_price,
|
||||
"upgrade": upgrade == u'True',
|
||||
"can_audit": "audit" in modes_dict,
|
||||
"can_audit": CourseMode.mode_for_course(course_id, 'audit') is not None,
|
||||
"modes_dict": CourseMode.modes_for_course_dict(course_id),
|
||||
}
|
||||
|
||||
return render_to_response('verify_student/photo_verification.html', context)
|
||||
@@ -124,19 +131,20 @@ class VerifiedView(View):
|
||||
if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == ('verified', True):
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
|
||||
modes_dict = CourseMode.modes_for_course_dict(course_id)
|
||||
verify_mode = modes_dict.get('verified', None)
|
||||
|
||||
if verify_mode is None:
|
||||
# we prefer professional over verify
|
||||
current_mode = CourseMode.verified_mode_for_course(course_id)
|
||||
|
||||
# if the course doesn't have a verified mode, we want to kick them
|
||||
# from the flow
|
||||
if not current_mode:
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
chosen_price = request.session.get(
|
||||
"donation_for_course",
|
||||
{}
|
||||
).get(
|
||||
course_id.to_deprecated_string(),
|
||||
verify_mode.min_price
|
||||
)
|
||||
if course_id.to_deprecated_string() in request.session.get("donation_for_course", {}):
|
||||
chosen_price = request.session["donation_for_course"][course_id.to_deprecated_string()]
|
||||
else:
|
||||
chosen_price = current_mode.min_price
|
||||
|
||||
course = modulestore().get_course(course_id)
|
||||
context = {
|
||||
@@ -146,11 +154,12 @@ class VerifiedView(View):
|
||||
"course_org": course.display_org_with_default,
|
||||
"course_num": course.display_number_with_default,
|
||||
"purchase_endpoint": get_purchase_endpoint(),
|
||||
"currency": verify_mode.currency.upper(),
|
||||
"currency": current_mode.currency.upper(),
|
||||
"chosen_price": chosen_price,
|
||||
"create_order_url": reverse("verify_student_create_order"),
|
||||
"upgrade": upgrade == u'True',
|
||||
"can_audit": "audit" in modes_dict,
|
||||
"modes_dict": modes_dict,
|
||||
}
|
||||
return render_to_response('verify_student/verified.html', context)
|
||||
|
||||
@@ -185,19 +194,24 @@ def create_order(request):
|
||||
donation_for_course[course_id] = amount
|
||||
request.session['donation_for_course'] = donation_for_course
|
||||
|
||||
verified_mode = CourseMode.modes_for_course_dict(course_id).get('verified', None)
|
||||
# prefer professional mode over verified_mode
|
||||
current_mode = CourseMode.verified_mode_for_course(course_id)
|
||||
|
||||
if current_mode.slug == 'professional':
|
||||
amount = current_mode.min_price
|
||||
|
||||
# make sure this course has a verified mode
|
||||
if not verified_mode:
|
||||
if not current_mode:
|
||||
return HttpResponseBadRequest(_("This course doesn't support verified certificates"))
|
||||
|
||||
if amount < verified_mode.min_price:
|
||||
if amount < current_mode.min_price:
|
||||
return HttpResponseBadRequest(_("No selected price or selected price is below minimum."))
|
||||
|
||||
# I know, we should check this is valid. All kinds of stuff missing here
|
||||
cart = Order.get_cart_for_user(request.user)
|
||||
cart.clear()
|
||||
CertificateItem.add_to_order(cart, course_id, amount, 'verified')
|
||||
enrollment_mode = current_mode.slug
|
||||
CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode)
|
||||
|
||||
params = get_signed_purchase_params(cart)
|
||||
|
||||
@@ -288,12 +302,20 @@ def show_requirements(request, course_id):
|
||||
"""
|
||||
Show the requirements necessary for the verification flow.
|
||||
"""
|
||||
# TODO: seems borked for professional; we're told we need to take photos even if there's a pending verification
|
||||
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
upgrade = request.GET.get('upgrade', False)
|
||||
if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == ('verified', True):
|
||||
return redirect(reverse('dashboard'))
|
||||
if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
|
||||
return redirect(
|
||||
reverse('verify_student_verified',
|
||||
kwargs={'course_id': course_id.to_deprecated_string()}) + "?upgrade={}".format(upgrade)
|
||||
)
|
||||
|
||||
upgrade = request.GET.get('upgrade', False)
|
||||
course = modulestore().get_course(course_id)
|
||||
modes_dict = CourseMode.modes_for_course_dict(course_id)
|
||||
context = {
|
||||
"course_id": course_id.to_deprecated_string(),
|
||||
"course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_id.to_deprecated_string()}),
|
||||
@@ -303,6 +325,7 @@ def show_requirements(request, course_id):
|
||||
"course_num": course.display_number_with_default,
|
||||
"is_not_active": not request.user.is_active,
|
||||
"upgrade": upgrade == u'True',
|
||||
"modes_dict": modes_dict,
|
||||
}
|
||||
return render_to_response("verify_student/show_requirements.html", context)
|
||||
|
||||
|
||||
@@ -177,12 +177,13 @@
|
||||
|
||||
<dt class="faq-question">${_("What do you do with this picture?")}</dt>
|
||||
<dd class="faq-answer">${_("We only use it to verify your identity. It is not displayed anywhere.")}</dd>
|
||||
<dt class="faq-question">${_("What if my camera isn't working?")}</dt>
|
||||
|
||||
%if upgrade:
|
||||
<dd class="faq-answer">${_("You can always continue to audit the course without verifying.")}</dd>
|
||||
%else:
|
||||
<dd class="faq-answer">${_("You can always {a_start} audit the course for free {a_end} without verifying.").format(a_start='<a rel="external" href="{}">'.format(course_modes_choose_url), a_end="</a>")}</dd>
|
||||
%if "professional" not in modes_dict:
|
||||
<dt class="faq-question">${_("What if my camera isn't working?")}</dt>
|
||||
%if upgrade:
|
||||
<dd class="faq-answer">${_("You can always continue to audit the course without verifying.")}</dd>
|
||||
%else:
|
||||
<dd class="faq-answer">${_("You can always {a_start} audit the course for free {a_end} without verifying.").format(a_start='<a rel="external" href="{}">'.format(course_modes_choose_url), a_end="</a>")}</dd>
|
||||
%endif
|
||||
%endif
|
||||
</dl>
|
||||
</div>
|
||||
@@ -366,6 +367,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
%if len(suggested_prices) > 0:
|
||||
<li class="review-task review-task-contribution">
|
||||
<h4 class="title">${_("Check Your Contribution Level")}</h4>
|
||||
|
||||
@@ -376,12 +378,28 @@
|
||||
<%include file="/course_modes/_contribution.html" args="suggested_prices=suggested_prices, currency=currency, chosen_price=chosen_price, min_price=min_price"/>
|
||||
|
||||
</li>
|
||||
%else:
|
||||
<li class="review-task review-task-contribution">
|
||||
<h4 class="title">${_("Your Course Total")}</h4>
|
||||
<div class="copy">
|
||||
<p>${_("To complete your registration, you will need to pay:")}</p>
|
||||
</div>
|
||||
<ul class="list-fields contribution-options">
|
||||
<li class="field contribution-option">
|
||||
<span class="deco-denomination">$</span>
|
||||
<span class="label-value">${chosen_price}</span>
|
||||
<span class="denomination-name">${currency}</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
%endif
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wizard">
|
||||
<div class="prompt-verify">
|
||||
<h3 class="title">Before proceeding, please confirm that your details match</h3>
|
||||
<h3 class="title">${_("Before proceeding, please confirm that your details match")}</h3>
|
||||
|
||||
<p class="copy"> ${_("Once you verify your details match the requirements, you can move on to step 4, payment on our secure server.")}</p>
|
||||
|
||||
|
||||
@@ -85,8 +85,11 @@ $(document).ready(function() {
|
||||
</div>
|
||||
|
||||
<nav class="nav-wizard is-ready">
|
||||
|
||||
%if "professional" in modes_dict:
|
||||
<span class="help help-inline price-value">${_("Your Course Total is $ ")} <strong>${chosen_price}</strong></span>
|
||||
%else:
|
||||
<span class="help help-inline price-value">${_("You have decided to pay $ ")} <strong>${chosen_price}</strong></span>
|
||||
%endif
|
||||
|
||||
<ol class="wizard-steps">
|
||||
<li class="wizard-step step-proceed">
|
||||
|
||||
Reference in New Issue
Block a user