diff --git a/common/djangoapps/student/tests/test_credit.py b/common/djangoapps/student/tests/test_credit.py index 3167069ccb..b85d711e2d 100644 --- a/common/djangoapps/student/tests/test_credit.py +++ b/common/djangoapps/student/tests/test_credit.py @@ -6,7 +6,6 @@ import datetime from mock import patch import pytz -from mock import patch from django.conf import settings from django.core.urlresolvers import reverse diff --git a/lms/djangoapps/courseware/tests/test_credit_requirements.py b/lms/djangoapps/courseware/tests/test_credit_requirements.py new file mode 100644 index 0000000000..6727be5740 --- /dev/null +++ b/lms/djangoapps/courseware/tests/test_credit_requirements.py @@ -0,0 +1,147 @@ +""" +Tests for credit requirement display on the progress page. +""" + +import datetime + +from mock import patch +from pytz import UTC + +from django.conf import settings +from django.core.urlresolvers import reverse + +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory +from student.tests.factories import UserFactory, CourseEnrollmentFactory +from util.date_utils import get_time_display, DEFAULT_SHORT_DATE_FORMAT + +from openedx.core.djangoapps.credit import api as credit_api +from openedx.core.djangoapps.credit.models import CreditCourse + + +@patch.dict(settings.FEATURES, {"ENABLE_CREDIT_ELIGIBILITY": True}) +class ProgressPageCreditRequirementsTest(ModuleStoreTestCase): + """ + Tests for credit requirement display on the progress page. + """ + + USERNAME = "bob" + PASSWORD = "test" + USER_FULL_NAME = "Bob" + + MIN_GRADE_REQ_DISPLAY = "Final Grade Credit Requirement" + VERIFICATION_REQ_DISPLAY = "Midterm Exam Credit Requirement" + + def setUp(self): + super(ProgressPageCreditRequirementsTest, self).setUp() + + # Create a course and configure it as a credit course + self.course = CourseFactory.create() + CreditCourse.objects.create(course_key=self.course.id, enabled=True) # pylint: disable=no-member + + # Configure credit requirements (passing grade and in-course reverification) + credit_api.set_credit_requirements( + self.course.id, # pylint: disable=no-member + [ + { + "namespace": "grade", + "name": "grade", + "display_name": self.MIN_GRADE_REQ_DISPLAY, + "criteria": { + "min_grade": 0.8 + } + }, + { + "namespace": "reverification", + "name": "midterm", + "display_name": self.VERIFICATION_REQ_DISPLAY, + "criteria": {} + } + ] + ) + + # Create a user and log in + self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD) + self.user.profile.name = self.USER_FULL_NAME + self.user.profile.save() + + result = self.client.login(username=self.USERNAME, password=self.PASSWORD) + self.assertTrue(result, msg="Could not log in") + + # Enroll the user in the course as "verified" + self.enrollment = CourseEnrollmentFactory( + user=self.user, + course_id=self.course.id, # pylint: disable=no-member + mode="verified" + ) + + def test_credit_requirements_maybe_eligible(self): + # The user hasn't satisfied any of the credit requirements yet, but she + # also hasn't failed any. + response = self._get_progress_page() + + # Expect that the requirements are displayed + self.assertContains(response, self.MIN_GRADE_REQ_DISPLAY) + self.assertContains(response, self.VERIFICATION_REQ_DISPLAY) + self.assertContains(response, "Upcoming") + self.assertContains( + response, + "{}, you have not yet met the requirements for credit".format(self.USER_FULL_NAME) + ) + + def test_credit_requirements_eligible(self): + # Mark the user as eligible for all requirements + credit_api.set_credit_requirement_status( + self.user.username, self.course.id, + "grade", "grade", + status="satisfied", + reason={"final_grade": 0.95} + ) + + credit_api.set_credit_requirement_status( + self.user.username, self.course.id, + "reverification", "midterm", + status="satisfied", reason={} + ) + + # Check the progress page display + response = self._get_progress_page() + self.assertContains(response, self.MIN_GRADE_REQ_DISPLAY) + self.assertContains(response, self.VERIFICATION_REQ_DISPLAY) + self.assertContains( + response, + "{}, you have met the requirements for credit in this course.".format(self.USER_FULL_NAME) + ) + self.assertContains(response, "Verified on {date}".format(date=self._now_formatted_date())) + self.assertContains(response, "95%") + + def test_credit_requirements_not_eligible(self): + # Mark the user as having failed both requirements + credit_api.set_credit_requirement_status( + self.user.username, self.course.id, + "reverification", "midterm", + status="failed", reason={} + ) + + # Check the progress page display + response = self._get_progress_page() + self.assertContains(response, self.MIN_GRADE_REQ_DISPLAY) + self.assertContains(response, self.VERIFICATION_REQ_DISPLAY) + self.assertContains( + response, + "{}, you are no longer eligible for credit in this course.".format(self.USER_FULL_NAME) + ) + self.assertContains(response, "Verification Failed") + + def _get_progress_page(self): + """Load the progress page for the course the user is enrolled in. """ + url = reverse("progress", kwargs={"course_id": unicode(self.course.id)}) + return self.client.get(url) + + def _now_formatted_date(self): + """Retrieve the formatted current date. """ + return get_time_display( + datetime.datetime.now(UTC), + DEFAULT_SHORT_DATE_FORMAT, + settings.TIME_ZONE + ) diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html index ad3a71140d..6a7a36bc09 100644 --- a/lms/templates/courseware/progress.html +++ b/lms/templates/courseware/progress.html @@ -105,13 +105,13 @@ from django.utils.http import urlquote_plus

${_("Requirements for Course Credit")}

%if credit_course_requirements['eligibility_status'] == 'not_eligible': - ${student.get_full_name()}, ${_("You are no longer eligible for this course.")} + ${_("{student_name}, you are no longer eligible for credit in this course.").format(student_name=student.profile.name)} %elif credit_course_requirements['eligibility_status'] == 'eligible': - ${student.get_full_name()}, ${_("You have met the requirements for credit in this course.")} + ${_("{student_name}, you have met the requirements for credit in this course.").format(student_name=student.profile.name)} ${_("{link} to purchase course credit.").format(link="{url_name}".format(url = reverse('dashboard'), url_name = _('Go to your dashboard')))} %elif credit_course_requirements['eligibility_status'] == 'partial_eligible': - ${_("{student_name}, you have not yet met the requirements for credit.").format(student_name=student.get_full_name())} + ${_("{student_name}, you have not yet met the requirements for credit.").format(student_name=student.profile.name)} %endif ${_("Information about course credit requirements")}
@@ -127,7 +127,13 @@ from django.utils.http import urlquote_plus ${_("Verification Failed" )} %elif requirement['status'] == 'satisfied': + % if requirement['namespace'] == 'reverification': Verified on ${get_time_display(requirement['status_date'], DEFAULT_SHORT_DATE_FORMAT, settings.TIME_ZONE)} + % elif requirement['namespace'] == 'grade' and 'final_grade' in requirement['reason']: + ${int(requirement['reason']['final_grade'] * 100)}% + % else: + Completed + % endif %endif %else: ${_("Upcoming")} diff --git a/openedx/core/djangoapps/credit/api/eligibility.py b/openedx/core/djangoapps/credit/api/eligibility.py index 4b0d884179..551b978a5b 100644 --- a/openedx/core/djangoapps/credit/api/eligibility.py +++ b/openedx/core/djangoapps/credit/api/eligibility.py @@ -294,6 +294,7 @@ def get_credit_requirement_status(course_key, username, namespace=None, name=Non "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid", "display_name": "In Course Reverification", "criteria": {}, + "reason": {}, "status": "failed", "status_date": "2015-06-26 07:49:13", }, @@ -302,6 +303,7 @@ def get_credit_requirement_status(course_key, username, namespace=None, name=Non "name": "i4x://edX/DemoX/proctoring-block/final_uuid", "display_name": "Proctored Mid Term Exam", "criteria": {}, + "reason": {}, "status": "satisfied", "status_date": "2015-06-26 11:07:42", }, @@ -310,7 +312,8 @@ def get_credit_requirement_status(course_key, username, namespace=None, name=Non "name": "i4x://edX/DemoX/proctoring-block/final_uuid", "display_name": "Minimum Passing Grade", "criteria": {"min_grade": 0.8}, - "status": "failed", + "reason": {"final_grade": 0.95}, + "status": "satisfied", "status_date": "2015-06-26 11:07:44", }, ] @@ -329,6 +332,7 @@ def get_credit_requirement_status(course_key, username, namespace=None, name=Non "name": requirement.name, "display_name": requirement.display_name, "criteria": requirement.criteria, + "reason": requirement_status.reason if requirement_status else None, "status": requirement_status.status if requirement_status else None, "status_date": requirement_status.modified if requirement_status else None, })