Commit 4b94d636 by Bill DeRusha

Update dashboard banners and track selection for audit track

Remove cert messaging from audit cert/grade info partial

move enrollment display method to helpers
parent 439684b4
""" Helper methods for CourseModes. """
from django.utils.translation import ugettext_lazy as _
from course_modes.models import CourseMode
from student.helpers import (
VERIFY_STATUS_NEED_TO_VERIFY,
VERIFY_STATUS_SUBMITTED,
VERIFY_STATUS_APPROVED
)
DISPLAY_VERIFIED = "verified"
DISPLAY_HONOR = "honor"
DISPLAY_AUDIT = "audit"
DISPLAY_PROFESSIONAL = "professional"
def enrollment_mode_display(mode, verification_status, course_id):
""" Select appropriate display strings and CSS classes.
Uses mode and verification status to select appropriate display strings and CSS classes
for certificate display.
Args:
mode (str): enrollment mode.
verification_status (str) : verification status of student
Returns:
dictionary:
"""
show_image = False
image_alt = ''
enrollment_title = ''
enrollment_value = ''
display_mode = _enrollment_mode_display(mode, verification_status, course_id)
if display_mode == DISPLAY_VERIFIED:
if verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED]:
enrollment_title = _("Your verification is pending")
enrollment_value = _("Verified: Pending Verification")
show_image = True
image_alt = _("ID verification pending")
elif verification_status == VERIFY_STATUS_APPROVED:
enrollment_title = _("You're enrolled as a verified student")
enrollment_value = _("Verified")
show_image = True
image_alt = _("ID Verified Ribbon/Badge")
elif display_mode == DISPLAY_HONOR:
enrollment_title = _("You're enrolled as an honor code student")
enrollment_value = _("Honor Code")
elif display_mode == DISPLAY_PROFESSIONAL:
enrollment_title = _("You're enrolled as a professional education student")
enrollment_value = _("Professional Ed")
return {
'enrollment_title': unicode(enrollment_title),
'enrollment_value': unicode(enrollment_value),
'show_image': show_image,
'image_alt': unicode(image_alt),
'display_mode': _enrollment_mode_display(mode, verification_status, course_id)
}
def _enrollment_mode_display(enrollment_mode, verification_status, course_id):
"""Checking enrollment mode and status and returns the display mode
Args:
enrollment_mode (str): enrollment mode.
verification_status (str) : verification status of student
Returns:
display_mode (str) : display mode for certs
"""
course_mode_slugs = [mode.slug for mode in CourseMode.modes_for_course(course_id)]
if enrollment_mode == CourseMode.VERIFIED:
if verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_APPROVED]:
display_mode = DISPLAY_VERIFIED
elif DISPLAY_HONOR in course_mode_slugs:
display_mode = DISPLAY_HONOR
else:
display_mode = DISPLAY_AUDIT
elif enrollment_mode in [CourseMode.PROFESSIONAL, CourseMode.NO_ID_PROFESSIONAL_MODE]:
display_mode = DISPLAY_PROFESSIONAL
else:
display_mode = enrollment_mode
return display_mode
......@@ -506,8 +506,27 @@ class CourseMode(models.Model):
if cls.is_white_label(course_id, modes_dict=modes_dict):
return False
# Check that the default mode is available.
return cls.DEFAULT_MODE_SLUG in modes_dict
# Check that a free mode is available.
return cls.AUDIT in modes_dict or cls.HONOR in modes_dict
@classmethod
def auto_enroll_mode(cls, course_id, modes_dict=None):
"""
return the auto-enrollable mode from given dict
Args:
modes_dict (dict): course modes.
Returns:
String: Mode name
"""
if modes_dict is None:
modes_dict = cls.modes_for_course_dict(course_id)
if cls.HONOR in modes_dict:
return cls.HONOR
elif cls.AUDIT in modes_dict:
return cls.AUDIT
@classmethod
def is_white_label(cls, course_id, modes_dict=None):
......@@ -547,96 +566,6 @@ class CourseMode(models.Model):
modes = cls.modes_for_course(course_id)
return min(mode.min_price for mode in modes if mode.currency.lower() == currency.lower())
@classmethod
def enrollment_mode_display(cls, mode, verification_status):
""" Select appropriate display strings and CSS classes.
Uses mode and verification status to select appropriate display strings and CSS classes
for certificate display.
Args:
mode (str): enrollment mode.
verification_status (str) : verification status of student
Returns:
dictionary:
"""
# import inside the function to avoid the circular import
from student.helpers import (
VERIFY_STATUS_NEED_TO_VERIFY,
VERIFY_STATUS_SUBMITTED,
VERIFY_STATUS_APPROVED
)
show_image = False
image_alt = ''
if mode == cls.VERIFIED:
if verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED]:
enrollment_title = _("Your verification is pending")
enrollment_value = _("Verified: Pending Verification")
show_image = True
image_alt = _("ID verification pending")
elif verification_status == VERIFY_STATUS_APPROVED:
enrollment_title = _("You're enrolled as a verified student")
enrollment_value = _("Verified")
show_image = True
image_alt = _("ID Verified Ribbon/Badge")
else:
enrollment_title = _("You're enrolled as an honor code student")
enrollment_value = _("Honor Code")
elif mode == cls.HONOR:
enrollment_title = _("You're enrolled as an honor code student")
enrollment_value = _("Honor Code")
elif mode == cls.AUDIT:
enrollment_title = _("You're auditing this course")
enrollment_value = _("Auditing")
elif mode in [cls.PROFESSIONAL, cls.NO_ID_PROFESSIONAL_MODE]:
enrollment_title = _("You're enrolled as a professional education student")
enrollment_value = _("Professional Ed")
else:
enrollment_title = ''
enrollment_value = ''
return {
'enrollment_title': unicode(enrollment_title),
'enrollment_value': unicode(enrollment_value),
'show_image': show_image,
'image_alt': unicode(image_alt),
'display_mode': cls._enrollment_mode_display(mode, verification_status)
}
@staticmethod
def _enrollment_mode_display(enrollment_mode, verification_status):
"""Checking enrollment mode and status and returns the display mode
Args:
enrollment_mode (str): enrollment mode.
verification_status (str) : verification status of student
Returns:
display_mode (str) : display mode for certs
"""
# import inside the function to avoid the circular import
from student.helpers import (
VERIFY_STATUS_NEED_TO_VERIFY,
VERIFY_STATUS_SUBMITTED,
VERIFY_STATUS_APPROVED
)
if enrollment_mode == CourseMode.VERIFIED:
if verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_APPROVED]:
display_mode = "verified"
else:
display_mode = "honor"
elif enrollment_mode in [CourseMode.PROFESSIONAL, CourseMode.NO_ID_PROFESSIONAL_MODE]:
display_mode = "professional"
else:
display_mode = enrollment_mode
return display_mode
def to_tuple(self):
"""
Takes a mode model and turns it into a model named tuple.
......
......@@ -15,6 +15,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.locator import CourseLocator
import pytz
from course_modes.helpers import enrollment_mode_display
from course_modes.models import CourseMode, Mode
......@@ -193,6 +194,21 @@ class CourseModeModelTest(TestCase):
# Verify that we can or cannot auto enroll
self.assertEqual(CourseMode.can_auto_enroll(self.course_key), can_auto_enroll)
@ddt.data(
([], None),
(["honor", "audit", "verified"], "honor"),
(["honor", "audit"], "honor"),
(["audit", "verified"], "audit"),
(["professional"], None),
(["no-id-professional"], None),
(["credit", "audit", "verified"], "audit"),
(["credit"], None),
)
@ddt.unpack
def test_auto_enroll_mode(self, modes, result):
# Verify that the proper auto enroll mode is returned
self.assertEqual(CourseMode.auto_enroll_mode(self.course_key, modes), result)
def test_all_modes_for_courses(self):
now = datetime.now(pytz.UTC)
future = now + timedelta(days=1)
......@@ -316,30 +332,30 @@ class CourseModeModelTest(TestCase):
def test_enrollment_mode_display(self, mode, verification_status):
if mode == "verified":
self.assertEqual(
CourseMode.enrollment_mode_display(mode, verification_status),
enrollment_mode_display(mode, verification_status, self.course_key),
self._enrollment_display_modes_dicts(verification_status)
)
self.assertEqual(
CourseMode.enrollment_mode_display(mode, verification_status),
enrollment_mode_display(mode, verification_status, self.course_key),
self._enrollment_display_modes_dicts(verification_status)
)
self.assertEqual(
CourseMode.enrollment_mode_display(mode, verification_status),
enrollment_mode_display(mode, verification_status, self.course_key),
self._enrollment_display_modes_dicts(verification_status)
)
elif mode == "honor":
self.assertEqual(
CourseMode.enrollment_mode_display(mode, verification_status),
enrollment_mode_display(mode, verification_status, self.course_key),
self._enrollment_display_modes_dicts(mode)
)
elif mode == "audit":
self.assertEqual(
CourseMode.enrollment_mode_display(mode, verification_status),
enrollment_mode_display(mode, verification_status, self.course_key),
self._enrollment_display_modes_dicts(mode)
)
elif mode == "professional":
self.assertEqual(
CourseMode.enrollment_mode_display(mode, verification_status),
enrollment_mode_display(mode, verification_status, self.course_key),
self._enrollment_display_modes_dicts(mode)
)
......@@ -375,9 +391,9 @@ class CourseModeModelTest(TestCase):
'ID verification pending', 'verified'],
"verify_approved": ["You're enrolled as a verified student", "Verified", True, 'ID Verified Ribbon/Badge',
'verified'],
"verify_none": ["You're enrolled as an honor code student", "Honor Code", False, '', 'honor'],
"verify_none": ["", "", False, '', 'audit'],
"honor": ["You're enrolled as an honor code student", "Honor Code", False, '', 'honor'],
"audit": ["You're auditing this course", "Auditing", False, '', 'audit'],
"audit": ["", "", False, '', 'audit'],
"professional": ["You're enrolled as a professional education student", "Professional Ed", False, '',
'professional']
}
......
......@@ -174,12 +174,16 @@ class ChooseModeView(View):
if requested_mode not in allowed_modes:
return HttpResponseBadRequest(_("Enrollment mode not supported"))
if requested_mode == 'honor':
# The user will have already been enrolled in the honor mode at this
if requested_mode == 'audit':
# The user will have already been enrolled in the audit mode at this
# point, so we just redirect them to the dashboard, thereby avoiding
# hitting the database a second time attempting to enroll them.
return redirect(reverse('dashboard'))
if requested_mode == 'honor':
CourseEnrollment.enroll(user, course_key, mode=requested_mode)
return redirect(reverse('dashboard'))
mode_info = allowed_modes[requested_mode]
if requested_mode == 'verified':
......@@ -224,6 +228,8 @@ class ChooseModeView(View):
return 'verified'
if 'honor_mode' in request_dict:
return 'honor'
if 'audit_mode' in request_dict:
return 'audit'
else:
return None
......
......@@ -45,10 +45,17 @@ class EnrollmentTest(UrlResetMixin, ModuleStoreTestCase):
# and automatically enrolled
([], '', CourseMode.DEFAULT_MODE_SLUG),
# Audit / Verified / Honor
# Audit / Verified
# We should always go to the "choose your course" page.
# We should also be enrolled as the default mode.
(['honor', 'verified', 'audit'], 'course_modes_choose', CourseMode.DEFAULT_MODE_SLUG),
(['verified', 'audit'], 'course_modes_choose', CourseMode.DEFAULT_MODE_SLUG),
# Audit / Verified / Honor
# We should always go to the "choose your course" page.
# We should also be enrolled as the honor mode.
# Since honor and audit are currently offered together this precedence must
# be maintained.
(['honor', 'verified', 'audit'], 'course_modes_choose', CourseMode.HONOR),
# Professional ed
# Expect that we're sent to the "choose your track" page
......
......@@ -44,7 +44,7 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
self.dashboard_url = reverse('dashboard')
def test_enrolled_as_non_verified(self):
self._setup_mode_and_enrollment(None, "honor")
self._setup_mode_and_enrollment(None, "audit")
# Expect that the course appears on the dashboard
# without any verification messaging
......@@ -290,12 +290,9 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
VerificationDeadline.set_deadline(self.course.id, deadline)
BANNER_ALT_MESSAGES = {
None: "Honor",
VERIFY_STATUS_NEED_TO_VERIFY: "ID verification pending",
VERIFY_STATUS_SUBMITTED: "ID verification pending",
VERIFY_STATUS_APPROVED: "ID Verified Ribbon/Badge",
VERIFY_STATUS_MISSED_DEADLINE: "Honor",
VERIFY_STATUS_NEED_TO_REVERIFY: "Honor"
}
NOTIFICATION_MESSAGES = {
......@@ -309,12 +306,12 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
}
MODE_CLASSES = {
None: "honor",
None: "audit",
VERIFY_STATUS_NEED_TO_VERIFY: "verified",
VERIFY_STATUS_SUBMITTED: "verified",
VERIFY_STATUS_APPROVED: "verified",
VERIFY_STATUS_MISSED_DEADLINE: "honor",
VERIFY_STATUS_NEED_TO_REVERIFY: "honor"
VERIFY_STATUS_MISSED_DEADLINE: "audit",
VERIFY_STATUS_NEED_TO_REVERIFY: "audit"
}
def _assert_course_verification_status(self, status):
......@@ -334,7 +331,9 @@ class TestCourseVerificationStatus(UrlResetMixin, ModuleStoreTestCase):
self.assertContains(response, unicode(self.course.id))
# Verify that the correct banner is rendered on the dashboard
self.assertContains(response, self.BANNER_ALT_MESSAGES[status])
alt_text = self.BANNER_ALT_MESSAGES.get(status)
if alt_text:
self.assertContains(response, alt_text)
# Verify that the correct banner color is rendered
self.assertContains(
......
......@@ -11,6 +11,7 @@ from urlparse import urljoin
import pytz
from mock import Mock, patch
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from pyquery import PyQuery as pq
from django.conf import settings
from django.contrib.auth.models import User, AnonymousUser
......@@ -249,7 +250,7 @@ class DashboardTest(ModuleStoreTestCase):
self.client.login(username="jack", password="test")
self._check_verification_status_on('verified', 'You\'re enrolled as a verified student')
self._check_verification_status_on('honor', 'You\'re enrolled as an honor code student')
self._check_verification_status_on('audit', 'You\'re auditing this course')
self._check_verification_status_off('audit', '')
self._check_verification_status_on('professional', 'You\'re enrolled as a professional education student')
self._check_verification_status_on('no-id-professional', 'You\'re enrolled as a professional education student')
......@@ -269,6 +270,11 @@ class DashboardTest(ModuleStoreTestCase):
attempt.approve()
response = self.client.get(reverse('dashboard'))
if mode == 'audit':
# Audit mode does not have a banner. Assert no banner element.
self.assertEqual(pq(response.content)(".sts-enrollment").length, 0)
else:
self.assertNotContains(response, "class=\"course {0}\"".format(mode))
self.assertNotContains(response, value)
......@@ -281,7 +287,7 @@ class DashboardTest(ModuleStoreTestCase):
self.client.login(username="jack", password="test")
self._check_verification_status_off('verified', 'You\'re enrolled as a verified student')
self._check_verification_status_off('honor', 'You\'re enrolled as an honor code student')
self._check_verification_status_off('audit', 'You\'re auditing this course')
self._check_verification_status_off('audit', '')
def test_course_mode_info(self):
verified_mode = CourseModeFactory.create(
......
......@@ -1011,14 +1011,16 @@ def change_enrollment(request, check_access=True):
# Check that auto enrollment is allowed for this course
# (= the course is NOT behind a paywall)
if CourseMode.can_auto_enroll(course_id):
# Enroll the user using the default mode (honor)
# Enroll the user using the default mode (audit)
# We're assuming that users of the course enrollment table
# will NOT try to look up the course enrollment model
# by its slug. If they do, it's possible (based on the state of the database)
# for no such model to exist, even though we've set the enrollment type
# to "honor".
# to "audit".
try:
CourseEnrollment.enroll(user, course_id, check_access=check_access)
enroll_mode = CourseMode.auto_enroll_mode(course_id, available_modes)
if enroll_mode:
CourseEnrollment.enroll(user, course_id, check_access=check_access, mode=enroll_mode)
except Exception:
return HttpResponseBadRequest(_("Could not enroll"))
......
......@@ -143,15 +143,35 @@ from django.core.urlresolvers import reverse
<div class="register-choice register-choice-audit">
<div class="wrapper-copy">
<span class="deco-ribbon"></span>
<h4 class="title">${_("Earn an Honor Certificate")}</h4>
<div class="copy">
<p>${_("Take this course for free and have complete access to all the course material, activities, tests, and forums. Please note that learners who earn a passing grade will earn a certificate in this course.")}</p>
</div>
</div>
<ul class="list-actions">
<li class="action action-select">
<input type="submit" name="honor_mode" value="${_('Pursue an Honor Certificate')}" />
</li>
</ul>
</div>
% elif "audit" in modes:
<span class="deco-divider">
<span class="copy">${_("or")}</span>
</span>
<div class="register-choice register-choice-audit">
<div class="wrapper-copy">
<span class="deco-ribbon"></span>
<h4 class="title">${_("Audit This Course")}</h4>
<div class="copy">
<p>${_("Audit this course for free and have complete access to all the course material, activities, tests, and forums. If your work is satisfactory and you abide by the Honor Code, you'll receive a personalized Honor Code Certificate to showcase your achievement.")}</p>
<p>${_("Audit this course for free and have complete access to all the course material, activities, tests, and forums. Please note that this track does not offer a certificate for learners who earn a passing grade.")}</p>
</div>
</div>
<ul class="list-actions">
<li class="action action-select">
<input type="submit" name="honor_mode" value="${_('Audit This Course')}" />
<input type="submit" name="audit_mode" value="${_('Audit This Course')}" />
</li>
</ul>
</div>
......
......@@ -33,8 +33,12 @@ else:
% elif cert_status['status'] in ('generating', 'ready', 'notpassing', 'restricted'):
<p class="message-copy">${_("Your final grade:")}
<span class="grade-value">${"{0:.0f}%".format(float(cert_status['grade'])*100)}</span>.
% if cert_status['status'] == 'notpassing' and enrollment.mode != 'audit':
% if cert_status['status'] == 'notpassing':
% if enrollment.mode != 'audit':
${_("Grade required for a {cert_name_short}:").format(cert_name_short=cert_name_short)} <span class="grade-value">
% else:
${_("Grade required to pass this course:")} <span class="grade-value">
% endif
${"{0:.0f}%".format(float(course_overview.lowest_passing_grade)*100)}</span>.
% elif cert_status['status'] == 'restricted' and enrollment.mode == 'verified':
<p class="message-copy">
......
......@@ -9,6 +9,7 @@ from django.core.urlresolvers import reverse
from markupsafe import escape
from courseware.courses import get_course_university_about_section
from course_modes.models import CourseMode
from course_modes.helpers import enrollment_mode_display
from student.helpers import (
VERIFY_STATUS_NEED_TO_VERIFY,
VERIFY_STATUS_SUBMITTED,
......@@ -34,7 +35,13 @@ from student.helpers import (
<li class="course-item">
% if settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES'):
<% course_verified_certs = CourseMode.enrollment_mode_display(enrollment.mode, verification_status.get('status')) %>
<%
course_verified_certs = enrollment_mode_display(
enrollment.mode,
verification_status.get('status'),
course_overview.id
)
%>
<%
mode_class = course_verified_certs.get('display_mode', '')
if mode_class != '':
......@@ -69,7 +76,7 @@ from student.helpers import (
<img src="${course_overview.course_image_url}" class="course-image" alt="${_('{course_number} {course_name} Cover Image').format(course_number=course_overview.number, course_name=course_overview.display_name_with_default) | h}" />
</a>
% endif
% if settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES'):
% if settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES') and course_verified_certs.get('display_mode') != 'audit':
<span class="sts-enrollment" title="${course_verified_certs.get('enrollment_title')}">
<span class="label">${_("Enrolled as: ")}</span>
% if course_verified_certs.get('show_image'):
......
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