From ebd63b6188c4cd1cf45fb3dda48590af2b419751 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 27 Sep 2013 11:32:15 -0400 Subject: [PATCH 01/20] Add an upgrade parameter to the verification views. --- common/djangoapps/course_modes/views.py | 10 +++++++--- lms/djangoapps/verify_student/views.py | 10 +++++++++- lms/templates/verify_student/show_requirements.html | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index e247ac08a2..84e6f65785 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -38,6 +38,8 @@ class ChooseModeView(View): return redirect(reverse('dashboard')) modes = CourseMode.modes_for_course_dict(course_id) + upgrade = request.GET.get('upgrade', False) + donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(course_id, None) @@ -50,6 +52,7 @@ class ChooseModeView(View): "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, + "upgrade": upgrade, } if "verified" in modes: context["suggested_prices"] = [decimal.Decimal(x) for x in modes["verified"].suggested_prices.split(",")] @@ -70,6 +73,8 @@ class ChooseModeView(View): error_msg = _("Enrollment is closed") return self.get(request, course_id, error=error_msg) + upgrade = request.GET.get('upgrade', False) + requested_mode = self.get_requested_mode(request.POST.get("mode")) if requested_mode == "verified" and request.POST.get("honor-code"): requested_mode = "honor" @@ -106,13 +111,12 @@ class ChooseModeView(View): if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): return redirect( reverse('verify_student_verified', - kwargs={'course_id': course_id}) + kwargs={'course_id': course_id}) + "?upgrade={}".format(upgrade) ) return redirect( reverse('verify_student_show_requirements', - kwargs={'course_id': course_id}), - ) + kwargs={'course_id': course_id}) + "?upgrade={}".format(upgrade)) def get_requested_mode(self, user_choice): """ diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index b220ff6a97..8fafc26834 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -44,12 +44,15 @@ class VerifyView(View): before proceeding to payment """ + upgrade = request.GET.get('upgrade', False) + # If the user has already been verified within the given time period, # redirect straight to the payment -- no need to verify again. if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): return redirect( reverse('verify_student_verified', - kwargs={'course_id': course_id})) + kwargs={'course_id': course_id}) + "?upgrade={}".format(upgrade) + ) elif CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified': return redirect(reverse('dashboard')) else: @@ -85,6 +88,7 @@ class VerifyView(View): "currency": verify_mode.currency.upper(), "chosen_price": chosen_price, "min_price": verify_mode.min_price, + "upgrade": upgrade, } return render_to_response('verify_student/photo_verification.html', context) @@ -100,6 +104,7 @@ class VerifiedView(View): """ Handle the case where we have a get request """ + upgrade = request.GET.get('upgrade', False) if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified': return redirect(reverse('dashboard')) verify_mode = CourseMode.mode_for_course(course_id, "verified") @@ -117,6 +122,7 @@ class VerifiedView(View): "purchase_endpoint": get_purchase_endpoint(), "currency": verify_mode.currency.upper(), "chosen_price": chosen_price, + "upgrade": upgrade, } return render_to_response('verify_student/verified.html', context) @@ -250,6 +256,7 @@ def show_requirements(request, course_id): if CourseEnrollment.enrollment_mode_for_user(request.user, course_id) == 'verified': return redirect(reverse('dashboard')) + upgrade = request.GET.get('upgrade', False) course = course_from_id(course_id) context = { "course_id": course_id, @@ -257,5 +264,6 @@ def show_requirements(request, course_id): "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "is_not_active": not request.user.is_active, + "upgrade": upgrade, } return render_to_response("verify_student/show_requirements.html", context) diff --git a/lms/templates/verify_student/show_requirements.html b/lms/templates/verify_student/show_requirements.html index 432a13fc62..4acd8b645a 100644 --- a/lms/templates/verify_student/show_requirements.html +++ b/lms/templates/verify_student/show_requirements.html @@ -153,7 +153,7 @@
  1. - +
From 56f6711252a2716da17209d3cc06cbea2c875e1a Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 8 Oct 2013 17:29:02 -0400 Subject: [PATCH 02/20] Add in new hooks for the dashboard into the upgrade path. --- common/djangoapps/course_modes/models.py | 14 ++++++++++---- .../course_modes/tests/test_models.py | 19 ++++++++++--------- common/djangoapps/student/views.py | 2 ++ lms/templates/dashboard.html | 3 ++- .../dashboard/dashboard_course_listing.html | 18 +++++++++++++++--- 5 files changed, 39 insertions(+), 17 deletions(-) diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index ce15059643..8e475d1ec1 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -9,7 +9,7 @@ from collections import namedtuple from django.utils.translation import ugettext as _ from django.db.models import Q -Mode = namedtuple('Mode', ['slug', 'name', 'min_price', 'suggested_prices', 'currency']) +Mode = namedtuple('Mode', ['slug', 'name', 'min_price', 'suggested_prices', 'currency', 'expiration_date']) class CourseMode(models.Model): @@ -39,7 +39,7 @@ class CourseMode(models.Model): # turn this mode off after the given expiration date expiration_date = models.DateField(default=None, null=True, blank=True) - DEFAULT_MODE = Mode('honor', _('Honor Code Certificate'), 0, '', 'usd') + DEFAULT_MODE = Mode('honor', _('Honor Code Certificate'), 0, '', 'usd', None) DEFAULT_MODE_SLUG = 'honor' class Meta: @@ -57,8 +57,14 @@ class CourseMode(models.Model): found_course_modes = cls.objects.filter(Q(course_id=course_id) & (Q(expiration_date__isnull=True) | Q(expiration_date__gte=now))) - modes = ([Mode(mode.mode_slug, mode.mode_display_name, mode.min_price, mode.suggested_prices, mode.currency) - for mode in found_course_modes]) + modes = ([Mode( + mode.mode_slug, + mode.mode_display_name, + mode.min_price, + mode.suggested_prices, + mode.currency, + mode.expiration_date + ) for mode in found_course_modes]) if not modes: modes = [cls.DEFAULT_MODE] return modes diff --git a/common/djangoapps/course_modes/tests/test_models.py b/common/djangoapps/course_modes/tests/test_models.py index 651c7c51a5..7a01c30dc4 100644 --- a/common/djangoapps/course_modes/tests/test_models.py +++ b/common/djangoapps/course_modes/tests/test_models.py @@ -49,7 +49,7 @@ class CourseModeModelTest(TestCase): self.create_mode('verified', 'Verified Certificate') modes = CourseMode.modes_for_course(self.course_id) - mode = Mode(u'verified', u'Verified Certificate', 0, '', 'usd') + mode = Mode(u'verified', u'Verified Certificate', 0, '', 'usd', None) self.assertEqual([mode], modes) modes_dict = CourseMode.modes_for_course_dict(self.course_id) @@ -61,8 +61,8 @@ class CourseModeModelTest(TestCase): """ Finding the modes when there's multiple modes """ - mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd') - mode2 = Mode(u'verified', u'Verified Certificate', 0, '', 'usd') + mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None) + mode2 = Mode(u'verified', u'Verified Certificate', 0, '', 'usd', None) set_modes = [mode1, mode2] for mode in set_modes: self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices) @@ -81,9 +81,9 @@ class CourseModeModelTest(TestCase): self.assertEqual(0, CourseMode.min_course_price_for_currency(self.course_id, 'usd')) # create some modes - mode1 = Mode(u'honor', u'Honor Code Certificate', 10, '', 'usd') - mode2 = Mode(u'verified', u'Verified Certificate', 20, '', 'usd') - mode3 = Mode(u'honor', u'Honor Code Certificate', 80, '', 'cny') + mode1 = Mode(u'honor', u'Honor Code Certificate', 10, '', 'usd', None) + mode2 = Mode(u'verified', u'Verified Certificate', 20, '', 'usd', None) + mode3 = Mode(u'honor', u'Honor Code Certificate', 80, '', 'cny', None) set_modes = [mode1, mode2, mode3] for mode in set_modes: self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices, mode.currency) @@ -98,14 +98,15 @@ class CourseModeModelTest(TestCase): modes = CourseMode.modes_for_course(self.course_id) self.assertEqual([CourseMode.DEFAULT_MODE], modes) - mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd') + mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None) self.create_mode(mode1.slug, mode1.name, mode1.min_price, mode1.suggested_prices) modes = CourseMode.modes_for_course(self.course_id) self.assertEqual([mode1], modes) - expired_mode.expiration_date = datetime.now(pytz.UTC) + timedelta(days=1) + expiration_date = datetime.now(pytz.UTC) + timedelta(days=1) + expired_mode.expiration_date = expiration_date expired_mode.save() - expired_mode_value = Mode(u'verified', u'Verified Certificate', 0, '', 'usd') + expired_mode_value = Mode(u'verified', u'Verified Certificate', 0, '', 'usd', expiration_date.date()) modes = CourseMode.modes_for_course(self.course_id) self.assertEqual([expired_mode_value, mode1], modes) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 9d3d0bc63b..3172026209 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -300,6 +300,7 @@ def dashboard(request): show_courseware_links_for = frozenset(course.id for course, _enrollment in courses if has_access(request.user, course, 'load')) + course_modes = {course.id: CourseMode.modes_for_course_dict(course.id) for course, _enrollment in courses} cert_statuses = {course.id: cert_info(request.user, course) for course, _enrollment in courses} # only show email settings for Mongo course and when bulk email is turned on @@ -324,6 +325,7 @@ def dashboard(request): 'staff_access': staff_access, 'errored_courses': errored_courses, 'show_courseware_links_for': show_courseware_links_for, + 'all_course_modes': course_modes, 'cert_statuses': cert_statuses, 'show_email_settings_for': show_email_settings_for, } diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 683422133f..1bcdd4582f 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -179,7 +179,8 @@ <% show_courseware_link = (course.id in show_courseware_links_for) %> <% cert_status = cert_statuses.get(course.id) %> <% show_email_settings = (course.id in show_email_settings_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_modes = 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_modes=course_modes" /> % endfor diff --git a/lms/templates/dashboard/dashboard_course_listing.html b/lms/templates/dashboard/dashboard_course_listing.html index 3442cb93aa..2f9069034a 100644 --- a/lms/templates/dashboard/dashboard_course_listing.html +++ b/lms/templates/dashboard/dashboard_course_listing.html @@ -1,8 +1,9 @@ -<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings" /> +<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings, course_modes" /> <%! from django.utils.translation import ugettext as _ %> <%! from django.core.urlresolvers import reverse + from datetime import date from courseware.courses import course_image_url, get_course_about_section import waffle %> @@ -116,8 +117,6 @@ % endif % endif - - ${_('Unregister')} % if show_email_settings: @@ -125,5 +124,18 @@ % endif + %if enrollment.mode != 'verified' and 'verified' in course_modes: +
+ ${_("Get a verified certificate for this course!")} + %if course_modes['verified'].expiration_date: + <% days_left = (course_modes['verified'].expiration_date - date.today()).days %> +

+ ${_('Only {days} days left!').format(days=days_left)} +

+ %endif + +
+ %endif + From 349573dcdc99637c6868e3575d4a0f4096ef7747 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 9 Oct 2013 15:00:28 -0400 Subject: [PATCH 03/20] Add acceptance tests. --- .../courseware/features/certificates.feature | 11 +++++++++++ lms/djangoapps/courseware/features/certificates.py | 6 ++++++ lms/templates/dashboard/dashboard_course_listing.html | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/features/certificates.feature b/lms/djangoapps/courseware/features/certificates.feature index 1b385b32e5..279ef019d6 100644 --- a/lms/djangoapps/courseware/features/certificates.feature +++ b/lms/djangoapps/courseware/features/certificates.feature @@ -70,3 +70,14 @@ Feature: LMS.Verified certificates And the course has an honor mode When I give a reason why I cannot pay Then I should see the course on my dashboard + + Scenario: The upsell offer is on the dashboard if I am auditing. + Given I am logged in + When I select the audit track + And I navigate to my dashboard + Then I see the upsell link on my dashboard + + Scenario: I can take the upsell offer and be registered for a verified course + Given I am logged in + And I select the audit track + And I navigate to my dashboard diff --git a/lms/djangoapps/courseware/features/certificates.py b/lms/djangoapps/courseware/features/certificates.py index b8a5b958b6..39c23e5cf8 100644 --- a/lms/djangoapps/courseware/features/certificates.py +++ b/lms/djangoapps/courseware/features/certificates.py @@ -221,6 +221,12 @@ def see_the_course_on_my_dashboard(step): assert world.is_css_present(course_link_css) +@step(u'I see the upsell link on my dashboard') +def see_upsell_link_on_my_dashboard(step): + course_link_css = 'div.verified-upsell a[href*="edx/999/Certificates"' + assert world.is_css_present(course_link_css) + + @step(u'I see that I am on the verified track') def see_that_i_am_on_the_verified_track(step): id_verified_css = 'li.course-item article.course.verified' diff --git a/lms/templates/dashboard/dashboard_course_listing.html b/lms/templates/dashboard/dashboard_course_listing.html index 2f9069034a..fb78980da3 100644 --- a/lms/templates/dashboard/dashboard_course_listing.html +++ b/lms/templates/dashboard/dashboard_course_listing.html @@ -125,7 +125,7 @@ %if enrollment.mode != 'verified' and 'verified' in course_modes: -
+
${_("Get a verified certificate for this course!")} %if course_modes['verified'].expiration_date: <% days_left = (course_modes['verified'].expiration_date - date.today()).days %> From c42bff6528f26043b224c56285b2302a5a5596ca Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 9 Oct 2013 17:14:09 -0400 Subject: [PATCH 04/20] LMS: preps track selection view for upgrade cases --- common/templates/course_modes/choose.html | 45 ++++---- lms/static/sass/views/_verification.scss | 102 +++++++++++++++++- .../verify_student/_verification_header.html | 6 +- .../verify_student/_verification_support.html | 2 +- 4 files changed, 132 insertions(+), 23 deletions(-) diff --git a/common/templates/course_modes/choose.html b/common/templates/course_modes/choose.html index 738728042b..5aa65a04d7 100644 --- a/common/templates/course_modes/choose.html +++ b/common/templates/course_modes/choose.html @@ -2,7 +2,7 @@ <%! from django.core.urlresolvers import reverse %> <%inherit file="../main.html" /> -<%block name="bodyclass">register verification-process step-select-track +<%block name="bodyclass">register verification-process step-select-track ${'is-upgrading' if upgrade else ''} <%block name="title">${_("Register for {} | Choose Your Track").format(course_name)} <%block name="js_extra"> @@ -48,7 +48,10 @@ $(document).ready(function() {
+ + %if not upgrade:

${_("Select your track:")}

+ %endif
@@ -133,25 +136,29 @@ $(document).ready(function() {
% endif - % if "audit" in modes: - - ${_("or")} - -
-
-

${_("Audit This Course")}

-
-

${_("Sign up to audit this course for free and track your own progress.")}

-
-
+ %if not upgrade: -
    -
  • - -
  • -
-
- % endif + % if "audit" in modes: + + ${_("or")} + +
+
+

${_("Audit This Course")}

+
+

${_("Sign up to audit this course for free and track your own progress.")}

+
+
+ +
    +
  • + +
  • +
+
+ % endif + + %endif diff --git a/lms/static/sass/views/_verification.scss b/lms/static/sass/views/_verification.scss index 589c45f7d3..ece257b7ee 100644 --- a/lms/static/sass/views/_verification.scss +++ b/lms/static/sass/views/_verification.scss @@ -1,6 +1,95 @@ // lms - views - verification flow // ==================== +// MISC: extends - type +// application: canned headings +%hd-lv1 { + @extend %t-title1; + @extend %t-weight1; + color: $m-gray-d4; + margin: 0 0 ($baseline*2) 0; +} + +%hd-lv2 { + @extend %t-title4; + @extend %t-weight1; + margin: 0 0 ($baseline*0.75) 0; + border-bottom: 1px solid $m-gray-l4; + padding-bottom: ($baseline/2); + color: $m-gray-d4; +} + +%hd-lv3 { + @extend %t-title6; + @extend %t-weight4; + margin: 0 0 ($baseline/4) 0; + color: $m-gray-d4; +} + +%hd-lv4 { + @extend %t-title6; + @extend %t-weight2; + margin: 0 0 $baseline 0; + color: $m-gray-d4; +} + +%hd-lv5 { + @extend %t-title7; + @extend %t-weight4; + margin: 0 0 ($baseline/4) 0; + color: $m-gray-d4; +} + +// application: canned copy +%copy-base { + @extend %t-copy-base; + color: $m-gray-d2; +} + +%copy-lead1 { + @extend %t-copy-lead2; + color: $m-gray; +} + +%copy-detail { + @extend %t-copy-sub1; + @extend %t-weight3; + color: $m-gray-d1; +} + +%copy-metadata { + @extend %t-copy-sub2; + color: $m-gray-d1; + + + %copy-metadata-value { + @extend %t-weight2; + } + + %copy-metadata-value { + @extend %t-weight4; + } +} + +// application: canned links +%copy-link { + border-bottom: 1px dotted transparent; + + &:hover, &:active { + border-color: $link-color-d1; + } +} + +%copy-badge { + @extend %t-title8; + @extend %t-weight5; + border-radius: ($baseline/5); + padding: ($baseline/2) $baseline; + text-transform: uppercase; +} + +// ==================== + // MISC: extends - button %btn-verify-primary { @extend %btn-primary-green; @@ -72,7 +161,7 @@ // ==================== // VIEW: all verification steps -.register.verification-process { +.verification-process { // reset: box-sizing (making things so right its scary) * { @@ -388,11 +477,12 @@ .sts-label { @extend %t-title7; + @extend %t-weight4; display: block; margin-bottom: ($baseline/2); border-bottom: ($baseline/10) solid $m-gray-l4; padding-bottom: ($baseline/2); - color: $m-gray; + color: $m-gray-d1; } .sts-course { @@ -1816,3 +1906,11 @@ width: 32% !important; } } + +// STATE: upgrading registration type +.register.is-upgrading { + + .form-register-choose { + margin-top: ($baseline*2); + } +} diff --git a/lms/templates/verify_student/_verification_header.html b/lms/templates/verify_student/_verification_header.html index 4870a59c49..0f4878221d 100644 --- a/lms/templates/verify_student/_verification_header.html +++ b/lms/templates/verify_student/_verification_header.html @@ -2,7 +2,11 @@
diff --git a/lms/templates/verify_student/show_requirements.html b/lms/templates/verify_student/show_requirements.html index 4acd8b645a..3280fec89a 100644 --- a/lms/templates/verify_student/show_requirements.html +++ b/lms/templates/verify_student/show_requirements.html @@ -1,8 +1,16 @@ <%! from django.utils.translation import ugettext as _ %> <%! from django.core.urlresolvers import reverse %> <%inherit file="../main.html" /> -<%block name="bodyclass">register verification-process step-requirements -<%block name="title">${_("Register for {}").format(course_name)} +<%block name="bodyclass">register verification-process step-requirements ${'is-upgrading' if upgrade else ''} +<%block name="title"> + + %if upgrade: + ${_("Upgrade Your Registration for {}").format(course_name)} + %else: + ${_("Register for {}").format(course_name)} + %endif + + <%block name="content"> %if is_not_active: @@ -71,11 +79,19 @@
-

${_("What You Will Need to Register")}

+ %if upgrade: +

${_("What You Will Need to Upgrade")}

-
-

${_("There are three things you will need to register as an ID verified student:")}

-
+
+

${_("There are three things you will need to upgrade to being an ID verified student:")}

+
+ %else: +

${_("What You Will Need to Register")}

+ +
+

${_("There are three things you will need to register as an ID verified student:")}

+
+ %endif
    %if is_not_active: @@ -149,7 +165,12 @@