Commit 72549cf3 by Awais Jibran Committed by GitHub

Merge pull request #13185 from edx/aj/ECOM5081-hide-credit-btn

Hide credit button from  audit track students
parents 3bf3e0dc 511de4c6
...@@ -128,6 +128,9 @@ class CourseMode(models.Model): ...@@ -128,6 +128,9 @@ class CourseMode(models.Model):
# Modes that allow a student to earn credit with a university partner # Modes that allow a student to earn credit with a university partner
CREDIT_MODES = [CREDIT_MODE] CREDIT_MODES = [CREDIT_MODE]
# Modes that are eligible to purchase credit
CREDIT_ELIGIBLE_MODES = [VERIFIED, PROFESSIONAL, NO_ID_PROFESSIONAL_MODE]
# Modes that are allowed to upsell # Modes that are allowed to upsell
UPSELL_TO_VERIFIED_MODES = [HONOR, AUDIT] UPSELL_TO_VERIFIED_MODES = [HONOR, AUDIT]
...@@ -489,6 +492,18 @@ class CourseMode(models.Model): ...@@ -489,6 +492,18 @@ class CourseMode(models.Model):
return mode_slug in cls.VERIFIED_MODES return mode_slug in cls.VERIFIED_MODES
@classmethod @classmethod
def is_credit_eligible_slug(cls, mode_slug):
"""Check whether the given mode_slug is credit eligible or not.
Args:
mode_slug(str): Mode Slug
Returns:
bool: True iff the course mode slug is credit eligible else False.
"""
return mode_slug in cls.CREDIT_ELIGIBLE_MODES
@classmethod
def is_credit_mode(cls, course_mode_tuple): def is_credit_mode(cls, course_mode_tuple):
"""Check whether this is a credit mode. """Check whether this is a credit mode.
......
...@@ -177,7 +177,7 @@ class CreditCourseDashboardTest(ModuleStoreTestCase): ...@@ -177,7 +177,7 @@ class CreditCourseDashboardTest(ModuleStoreTestCase):
def _make_eligible(self): def _make_eligible(self):
"""Make the user eligible for credit in the course. """ """Make the user eligible for credit in the course. """
credit_api.set_credit_requirement_status( credit_api.set_credit_requirement_status(
self.USERNAME, self.user,
self.course.id, # pylint: disable=no-member self.course.id, # pylint: disable=no-member
"grade", "grade", "grade", "grade",
status="satisfied", status="satisfied",
......
...@@ -97,16 +97,19 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase): ...@@ -97,16 +97,19 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase):
) )
def test_credit_requirements_eligible(self): def test_credit_requirements_eligible(self):
# Mark the user as eligible for all requirements """
Mark the user as eligible for all requirements. Requirements are only displayed
for credit and verified enrollments.
"""
credit_api.set_credit_requirement_status( credit_api.set_credit_requirement_status(
self.user.username, self.course.id, self.user, self.course.id,
"grade", "grade", "grade", "grade",
status="satisfied", status="satisfied",
reason={"final_grade": 0.95} reason={"final_grade": 0.95}
) )
credit_api.set_credit_requirement_status( credit_api.set_credit_requirement_status(
self.user.username, self.course.id, self.user, self.course.id,
"reverification", "midterm", "reverification", "midterm",
status="satisfied", reason={} status="satisfied", reason={}
) )
...@@ -123,9 +126,12 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase): ...@@ -123,9 +126,12 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase):
self.assertNotContains(response, "95%") self.assertNotContains(response, "95%")
def test_credit_requirements_not_eligible(self): def test_credit_requirements_not_eligible(self):
# Mark the user as having failed both requirements """
Mark the user as having failed both requirements. Requirements are only displayed
for credit and verified enrollments.
"""
credit_api.set_credit_requirement_status( credit_api.set_credit_requirement_status(
self.user.username, self.course.id, self.user, self.course.id,
"reverification", "midterm", "reverification", "midterm",
status="failed", reason={} status="failed", reason={}
) )
......
...@@ -940,7 +940,6 @@ class TestProctoringRendering(SharedModuleStoreTestCase): ...@@ -940,7 +940,6 @@ class TestProctoringRendering(SharedModuleStoreTestCase):
Verifies gated content from the student view rendering of a sequence Verifies gated content from the student view rendering of a sequence
this is labeled as a proctored exam this is labeled as a proctored exam
""" """
usage_key = self._setup_test_data(enrollment_mode, is_practice_exam, attempt_status) usage_key = self._setup_test_data(enrollment_mode, is_practice_exam, attempt_status)
# initialize some credit requirements, if so then specify # initialize some credit requirements, if so then specify
...@@ -966,7 +965,7 @@ class TestProctoringRendering(SharedModuleStoreTestCase): ...@@ -966,7 +965,7 @@ class TestProctoringRendering(SharedModuleStoreTestCase):
) )
set_credit_requirement_status( set_credit_requirement_status(
self.request.user.username, self.request.user,
self.course_key, self.course_key,
'reverification', 'reverification',
'ICRV1' 'ICRV1'
...@@ -1021,7 +1020,6 @@ class TestProctoringRendering(SharedModuleStoreTestCase): ...@@ -1021,7 +1020,6 @@ class TestProctoringRendering(SharedModuleStoreTestCase):
if attempt_status: if attempt_status:
create_exam_attempt(exam_id, self.request.user.id, taking_as_proctored=True) create_exam_attempt(exam_id, self.request.user.id, taking_as_proctored=True)
update_attempt_status(exam_id, self.request.user.id, attempt_status) update_attempt_status(exam_id, self.request.user.id, attempt_status)
return usage_key return usage_key
def _find_url_name(self, toc, url_name): def _find_url_name(self, toc, url_name):
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
""" """
Integration tests for submitting problem responses and getting grades. Integration tests for submitting problem responses and getting grades.
""" """
import ddt
import json import json
import os import os
from textwrap import dedent from textwrap import dedent
...@@ -19,10 +20,11 @@ from capa.tests.response_xml_factory import ( ...@@ -19,10 +20,11 @@ from capa.tests.response_xml_factory import (
CodeResponseXMLFactory, CodeResponseXMLFactory,
) )
from lms.djangoapps.grades import course_grades, progress from lms.djangoapps.grades import course_grades, progress
from course_modes.models import CourseMode
from courseware.models import StudentModule, BaseStudentModuleHistory from courseware.models import StudentModule, BaseStudentModuleHistory
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase
from lms.djangoapps.lms_xblock.runtime import quote_slashes from lms.djangoapps.lms_xblock.runtime import quote_slashes
from student.models import anonymous_id_for_user from student.models import anonymous_id_for_user, CourseEnrollment
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.partitions.partitions import Group, UserPartition from xmodule.partitions.partitions import Group, UserPartition
...@@ -304,6 +306,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase, Probl ...@@ -304,6 +306,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase, Probl
@attr(shard=3) @attr(shard=3)
@ddt.ddt
class TestCourseGrader(TestSubmittingProblems): class TestCourseGrader(TestSubmittingProblems):
""" """
Suite of tests for the course grader. Suite of tests for the course grader.
...@@ -315,7 +318,6 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -315,7 +318,6 @@ class TestCourseGrader(TestSubmittingProblems):
""" """
Set up a simple course for testing basic grading functionality. Set up a simple course for testing basic grading functionality.
""" """
grading_policy = { grading_policy = {
"GRADER": [{ "GRADER": [{
"type": "Homework", "type": "Homework",
...@@ -646,20 +648,26 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -646,20 +648,26 @@ class TestCourseGrader(TestSubmittingProblems):
self.assertEqual(self.earned_hw_scores(), [1.0, 2.0, 2.0]) # Order matters self.assertEqual(self.earned_hw_scores(), [1.0, 2.0, 2.0]) # Order matters
self.assertEqual(self.score_for_hw('homework3'), [1.0, 1.0]) self.assertEqual(self.score_for_hw('homework3'), [1.0, 1.0])
def test_min_grade_credit_requirements_status(self): @ddt.data(
*CourseMode.CREDIT_ELIGIBLE_MODES
)
def test_min_grade_credit_requirements_status(self, mode):
""" """
Test for credit course. If user passes minimum grade requirement then Test for credit course. If user passes minimum grade requirement then
status will be updated as satisfied in requirement status table. status will be updated as satisfied in requirement status table.
""" """
self.basic_setup() self.basic_setup()
# Enroll student in credit eligible mode.
# Note that we can't call self.enroll here since that goes through
# the Django student views, and does not update enrollment if it already exists.
CourseEnrollment.enroll(self.student_user, self.course.id, mode)
self.submit_question_answer('p1', {'2_1': 'Correct'}) self.submit_question_answer('p1', {'2_1': 'Correct'})
self.submit_question_answer('p2', {'2_1': 'Correct'}) self.submit_question_answer('p2', {'2_1': 'Correct'})
# Enable the course for credit # Enable the course for credit
credit_course = CreditCourse.objects.create( CreditCourse.objects.create(course_key=self.course.id, enabled=True)
course_key=self.course.id,
enabled=True,
)
# Configure a credit provider for the course # Configure a credit provider for the course
CreditProvider.objects.create( CreditProvider.objects.create(
......
...@@ -155,7 +155,7 @@ class CourseGrade(object): ...@@ -155,7 +155,7 @@ class CourseGrade(object):
""" """
responses = GRADES_UPDATED.send_robust( responses = GRADES_UPDATED.send_robust(
sender=None, sender=None,
username=self.student.username, user=self.student,
grade_summary=self.summary, grade_summary=self.summary,
course_key=self.course.id, course_key=self.course.id,
deadline=self.course.end deadline=self.course.end
......
...@@ -112,7 +112,7 @@ class ReverificationService(object): ...@@ -112,7 +112,7 @@ class ReverificationService(object):
# As a user skips the reverification it declines to fulfill the requirement so # As a user skips the reverification it declines to fulfill the requirement so
# requirement sets to declined. # requirement sets to declined.
set_credit_requirement_status( set_credit_requirement_status(
user.username, user,
course_key, course_key,
'reverification', 'reverification',
checkpoint.checkpoint_location, checkpoint.checkpoint_location,
......
...@@ -124,7 +124,10 @@ class TestReverificationService(ModuleStoreTestCase): ...@@ -124,7 +124,10 @@ class TestReverificationService(ModuleStoreTestCase):
'skipped' 'skipped'
) )
def test_declined_verification_on_skip(self): @ddt.data(
*CourseMode.CREDIT_ELIGIBLE_MODES
)
def test_declined_verification_on_skip(self, mode):
"""Test that status with value 'declined' is added in credit """Test that status with value 'declined' is added in credit
requirement status model when a user skip's an ICRV. requirement status model when a user skip's an ICRV.
""" """
...@@ -135,6 +138,8 @@ class TestReverificationService(ModuleStoreTestCase): ...@@ -135,6 +138,8 @@ class TestReverificationService(ModuleStoreTestCase):
) )
# Create credit course and set credit requirements. # Create credit course and set credit requirements.
CreditCourse.objects.create(course_key=self.course_key, enabled=True) CreditCourse.objects.create(course_key=self.course_key, enabled=True)
self.enrollment.update_enrollment(mode=mode)
set_credit_requirements( set_credit_requirements(
self.course_key, self.course_key,
[ [
......
...@@ -1245,7 +1245,7 @@ def _set_user_requirement_status(attempt, namespace, status, reason=None): ...@@ -1245,7 +1245,7 @@ def _set_user_requirement_status(attempt, namespace, status, reason=None):
if checkpoint is not None: if checkpoint is not None:
try: try:
set_credit_requirement_status( set_credit_requirement_status(
attempt.user.username, attempt.user,
checkpoint.course_id, checkpoint.course_id,
namespace, namespace,
checkpoint.checkpoint_location, checkpoint.checkpoint_location,
......
...@@ -13,6 +13,9 @@ from openedx.core.djangoapps.credit.models import ( ...@@ -13,6 +13,9 @@ from openedx.core.djangoapps.credit.models import (
CreditCourse, CreditRequirement, CreditRequirementStatus, CreditEligibility, CreditRequest CreditCourse, CreditRequirement, CreditRequirementStatus, CreditEligibility, CreditRequest
) )
from course_modes.models import CourseMode
from student.models import CourseEnrollment
# TODO: Cleanup this mess! ECOM-2908 # TODO: Cleanup this mess! ECOM-2908
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -196,7 +199,7 @@ def get_eligibilities_for_user(username, course_key=None): ...@@ -196,7 +199,7 @@ def get_eligibilities_for_user(username, course_key=None):
] ]
def set_credit_requirement_status(username, course_key, req_namespace, req_name, status="satisfied", reason=None): def set_credit_requirement_status(user, course_key, req_namespace, req_name, status="satisfied", reason=None):
""" """
Update the user's requirement status. Update the user's requirement status.
...@@ -205,7 +208,7 @@ def set_credit_requirement_status(username, course_key, req_namespace, req_name, ...@@ -205,7 +208,7 @@ def set_credit_requirement_status(username, course_key, req_namespace, req_name,
as eligible for credit in the course. as eligible for credit in the course.
Args: Args:
username (str): Username of the user user(User): User object to set credit requirement for.
course_key (CourseKey): Identifier for the course associated with the requirement. course_key (CourseKey): Identifier for the course associated with the requirement.
req_namespace (str): Namespace of the requirement (e.g. "grade" or "reverification") req_namespace (str): Namespace of the requirement (e.g. "grade" or "reverification")
req_name (str): Name of the requirement (e.g. "grade" or the location of the ICRV XBlock) req_name (str): Name of the requirement (e.g. "grade" or the location of the ICRV XBlock)
...@@ -225,22 +228,30 @@ def set_credit_requirement_status(username, course_key, req_namespace, req_name, ...@@ -225,22 +228,30 @@ def set_credit_requirement_status(username, course_key, req_namespace, req_name,
) )
""" """
# Check whether user has credit eligible enrollment.
enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_key)
has_credit_eligible_enrollment = (CourseMode.is_credit_eligible_slug(enrollment_mode) and is_active)
# Refuse to set status of requirement if the user enrollment is not credit eligible.
if not has_credit_eligible_enrollment:
return
# Do not allow students who have requested credit to change their eligibility # Do not allow students who have requested credit to change their eligibility
if CreditRequest.get_user_request_status(username, course_key): if CreditRequest.get_user_request_status(user.username, course_key):
log.info( log.info(
u'Refusing to set status of requirement with namespace "%s" and name "%s" because the ' u'Refusing to set status of requirement with namespace "%s" and name "%s" because the '
u'user "%s" has already requested credit for the course "%s".', u'user "%s" has already requested credit for the course "%s".',
req_namespace, req_name, username, course_key req_namespace, req_name, user.username, course_key
) )
return return
# Do not allow a student who has earned eligibility to un-earn eligibility # Do not allow a student who has earned eligibility to un-earn eligibility
eligible_before_update = CreditEligibility.is_user_eligible_for_credit(course_key, username) eligible_before_update = CreditEligibility.is_user_eligible_for_credit(course_key, user.username)
if eligible_before_update and status == 'failed': if eligible_before_update and status == 'failed':
log.info( log.info(
u'Refusing to set status of requirement with namespace "%s" and name "%s" to "failed" because the ' u'Refusing to set status of requirement with namespace "%s" and name "%s" to "failed" because the '
u'user "%s" is already eligible for credit in the course "%s".', u'user "%s" is already eligible for credit in the course "%s".',
req_namespace, req_name, username, course_key req_namespace, req_name, user.username, course_key
) )
return return
...@@ -269,22 +280,22 @@ def set_credit_requirement_status(username, course_key, req_namespace, req_name, ...@@ -269,22 +280,22 @@ def set_credit_requirement_status(username, course_key, req_namespace, req_name,
u'because the requirement does not exist. ' u'because the requirement does not exist. '
u'The user "%s" should have had his/her status updated to "%s".' u'The user "%s" should have had his/her status updated to "%s".'
), ),
unicode(course_key), req_namespace, req_name, username, status unicode(course_key), req_namespace, req_name, user.username, status
) )
return return
# Update the requirement status # Update the requirement status
CreditRequirementStatus.add_or_update_requirement_status( CreditRequirementStatus.add_or_update_requirement_status(
username, req_to_update, status=status, reason=reason user.username, req_to_update, status=status, reason=reason
) )
# If we're marking this requirement as "satisfied", there's a chance that the user has met all eligibility # If we're marking this requirement as "satisfied", there's a chance that the user has met all eligibility
# requirements, and should be notified. However, if the user was already eligible, do not send another notification. # requirements, and should be notified. However, if the user was already eligible, do not send another notification.
if status == "satisfied" and not eligible_before_update: if status == "satisfied" and not eligible_before_update:
is_eligible, eligibility_record_created = CreditEligibility.update_eligibility(reqs, username, course_key) is_eligible, eligibility_record_created = CreditEligibility.update_eligibility(reqs, user.username, course_key)
if eligibility_record_created and is_eligible: if eligibility_record_created and is_eligible:
try: try:
send_credit_notifications(username, course_key) send_credit_notifications(user.username, course_key)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
log.error("Error sending email") log.error("Error sending email")
......
...@@ -155,7 +155,7 @@ class CreditService(object): ...@@ -155,7 +155,7 @@ class CreditService(object):
return None return None
api_set_credit_requirement_status( api_set_credit_requirement_status(
user.username, user,
course_key, course_key,
req_namespace, req_namespace,
req_name, req_name,
......
...@@ -53,12 +53,12 @@ def on_pre_publish(sender, course_key, **kwargs): # pylint: disable=unused-argu ...@@ -53,12 +53,12 @@ def on_pre_publish(sender, course_key, **kwargs): # pylint: disable=unused-argu
@receiver(GRADES_UPDATED) @receiver(GRADES_UPDATED)
def listen_for_grade_calculation(sender, username, grade_summary, course_key, deadline, **kwargs): # pylint: disable=unused-argument def listen_for_grade_calculation(sender, user, grade_summary, course_key, deadline, **kwargs): # pylint: disable=unused-argument
"""Receive 'MIN_GRADE_REQUIREMENT_STATUS' signal and update minimum grade requirement status. """Receive 'MIN_GRADE_REQUIREMENT_STATUS' signal and update minimum grade requirement status.
Args: Args:
sender: None sender: None
username(string): user name user(User): User Model object
grade_summary(dict): Dict containing output from the course grader grade_summary(dict): Dict containing output from the course grader
course_key(CourseKey): The key for the course course_key(CourseKey): The key for the course
deadline(datetime): Course end date or None deadline(datetime): Course end date or None
...@@ -70,7 +70,6 @@ def listen_for_grade_calculation(sender, username, grade_summary, course_key, de ...@@ -70,7 +70,6 @@ def listen_for_grade_calculation(sender, username, grade_summary, course_key, de
# This needs to be imported here to avoid a circular dependency # This needs to be imported here to avoid a circular dependency
# that can cause syncdb to fail. # that can cause syncdb to fail.
from openedx.core.djangoapps.credit import api from openedx.core.djangoapps.credit import api
course_id = CourseKey.from_string(unicode(course_key)) course_id = CourseKey.from_string(unicode(course_key))
is_credit = api.is_credit_course(course_id) is_credit = api.is_credit_course(course_id)
if is_credit: if is_credit:
...@@ -113,5 +112,5 @@ def listen_for_grade_calculation(sender, username, grade_summary, course_key, de ...@@ -113,5 +112,5 @@ def listen_for_grade_calculation(sender, username, grade_summary, course_key, de
# time to do so. # time to do so.
if status and reason: if status and reason:
api.set_credit_requirement_status( api.set_credit_requirement_status(
username, course_id, 'grade', 'grade', status=status, reason=reason user, course_id, 'grade', 'grade', status=status, reason=reason
) )
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
Tests for the Credit xBlock service Tests for the Credit xBlock service
""" """
import ddt
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from course_modes.models import CourseMode
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
...@@ -15,6 +17,7 @@ from student.models import CourseEnrollment, UserProfile ...@@ -15,6 +17,7 @@ from student.models import CourseEnrollment, UserProfile
@attr(shard=2) @attr(shard=2)
@ddt.ddt
class CreditServiceTests(ModuleStoreTestCase): class CreditServiceTests(ModuleStoreTestCase):
""" """
Tests for the Credit xBlock service Tests for the Credit xBlock service
...@@ -28,14 +31,14 @@ class CreditServiceTests(ModuleStoreTestCase): ...@@ -28,14 +31,14 @@ class CreditServiceTests(ModuleStoreTestCase):
self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True) self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True)
self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar') self.profile = UserProfile.objects.create(user_id=self.user.id, name='Foo Bar')
def enroll(self, course_id=None): def enroll(self, course_id=None, mode=CourseMode.VERIFIED):
""" """
Enroll the test user in the given course's honor mode, or the test Enroll the test user in the given course's mode. Use course/mode if they are
course if not provided. provided.
""" """
if course_id is None: if course_id is None:
course_id = self.course.id course_id = self.course.id
return CourseEnrollment.enroll(self.user, course_id, mode='honor') return CourseEnrollment.enroll(self.user, course_id, mode=mode)
def test_user_not_found(self): def test_user_not_found(self):
""" """
...@@ -127,7 +130,7 @@ class CreditServiceTests(ModuleStoreTestCase): ...@@ -127,7 +130,7 @@ class CreditServiceTests(ModuleStoreTestCase):
self.assertIsNotNone(credit_state) self.assertIsNotNone(credit_state)
self.assertTrue(credit_state['is_credit_course']) self.assertTrue(credit_state['is_credit_course'])
self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['enrollment_mode'], 'verified')
self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(len(credit_state['credit_requirement_status']), 1)
self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade')
...@@ -286,6 +289,48 @@ class CreditServiceTests(ModuleStoreTestCase): ...@@ -286,6 +289,48 @@ class CreditServiceTests(ModuleStoreTestCase):
self.assertFalse(credit_state['is_credit_course']) self.assertFalse(credit_state['is_credit_course'])
self.assertEqual(len(credit_state['credit_requirement_status']), 0) self.assertEqual(len(credit_state['credit_requirement_status']), 0)
@ddt.data(
CourseMode.AUDIT,
CourseMode.HONOR,
CourseMode.CREDIT_MODE
)
def test_set_status_non_verified_enrollment(self, mode):
"""
Test that we can still try to update a credit status but return quickly if
user has non-credit eligible enrollment.
"""
self.enroll(mode=mode)
# set course requirements
set_credit_requirements(
self.course.id,
[
{
"namespace": "grade",
"name": "grade",
"display_name": "Grade",
"criteria": {
"min_grade": 0.8
},
},
]
)
# this should be a no-op
self.service.set_credit_requirement_status(
self.user.id,
self.course.id,
'grade',
'grade'
)
# Verify credit requirement status for user in the course should be None.
credit_state = self.service.get_credit_state(self.user.id, self.course.id)
self.assertIsNotNone(credit_state)
self.assertEqual(credit_state['enrollment_mode'], mode)
self.assertEqual(len(credit_state['credit_requirement_status']), 1)
self.assertIsNone(credit_state['credit_requirement_status'][0]['status'])
self.assertIsNone(credit_state['credit_requirement_status'][0]['status_date'])
def test_bad_user(self): def test_bad_user(self):
""" """
Try setting requirements status with a bad user_id Try setting requirements status with a bad user_id
...@@ -348,7 +393,7 @@ class CreditServiceTests(ModuleStoreTestCase): ...@@ -348,7 +393,7 @@ class CreditServiceTests(ModuleStoreTestCase):
credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id)) credit_state = self.service.get_credit_state(self.user.id, unicode(self.course.id))
self.assertIsNotNone(credit_state) self.assertIsNotNone(credit_state)
self.assertEqual(credit_state['enrollment_mode'], 'honor') self.assertEqual(credit_state['enrollment_mode'], 'verified')
self.assertEqual(credit_state['profile_fullname'], 'Foo Bar') self.assertEqual(credit_state['profile_fullname'], 'Foo Bar')
self.assertEqual(len(credit_state['credit_requirement_status']), 1) self.assertEqual(len(credit_state['credit_requirement_status']), 1)
self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade') self.assertEqual(credit_state['credit_requirement_status'][0]['name'], 'grade')
......
...@@ -10,6 +10,8 @@ from unittest import skipUnless ...@@ -10,6 +10,8 @@ from unittest import skipUnless
from django.conf import settings from django.conf import settings
from django.test.client import RequestFactory from django.test.client import RequestFactory
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from course_modes.models import CourseMode
from student.models import CourseEnrollment
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
...@@ -66,9 +68,12 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase): ...@@ -66,9 +68,12 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase):
# Add a single credit requirement (final grade) # Add a single credit requirement (final grade)
set_credit_requirements(self.course.id, requirements) set_credit_requirements(self.course.id, requirements)
# Enroll user in verified mode.
self.enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.VERIFIED)
def assert_requirement_status(self, grade, due_date, expected_status): def assert_requirement_status(self, grade, due_date, expected_status):
""" Verify the user's credit requirement status is as expected after simulating a grading calculation. """ """ Verify the user's credit requirement status is as expected after simulating a grading calculation. """
listen_for_grade_calculation(None, self.user.username, {'percent': grade}, self.course.id, due_date) listen_for_grade_calculation(None, self.user, {'percent': grade}, self.course.id, due_date)
req_status = get_credit_requirement_status(self.course.id, self.request.user.username, 'grade', 'grade') req_status = get_credit_requirement_status(self.course.id, self.request.user.username, 'grade', 'grade')
self.assertEqual(req_status[0]['status'], expected_status) self.assertEqual(req_status[0]['status'], expected_status)
...@@ -109,3 +114,13 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase): ...@@ -109,3 +114,13 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase):
def test_min_grade_requirement_failed_grade_expired_deadline(self): def test_min_grade_requirement_failed_grade_expired_deadline(self):
"""Test with failed grades and deadline expire""" """Test with failed grades and deadline expire"""
self.assert_requirement_status(0.22, self.EXPIRED_DUE_DATE, 'failed') self.assert_requirement_status(0.22, self.EXPIRED_DUE_DATE, 'failed')
@ddt.data(
CourseMode.AUDIT,
CourseMode.HONOR,
CourseMode.CREDIT_MODE
)
def test_requirement_failed_for_non_verified_enrollment(self, mode):
"""Test with valid grades submitted before deadline with non-verified enrollment."""
self.enrollment.update_enrollment(mode, True)
self.assert_requirement_status(0.8, self.VALID_DUE_DATE, None)
...@@ -6,7 +6,7 @@ from django.dispatch import Signal ...@@ -6,7 +6,7 @@ from django.dispatch import Signal
# Signal that fires when a user is graded (in lms/grades/course_grades.py) # Signal that fires when a user is graded (in lms/grades/course_grades.py)
GRADES_UPDATED = Signal(providing_args=["username", "grade_summary", "course_key", "deadline"]) GRADES_UPDATED = Signal(providing_args=["user", "grade_summary", "course_key", "deadline"])
# Signal that fires when a user is awarded a certificate in a course (in the certificates django app) # Signal that fires when a user is awarded a certificate in a course (in the certificates django app)
# TODO: runtime coupling between apps will be reduced if this event is changed to carry a username # TODO: runtime coupling between apps will be reduced if this event is changed to carry a username
......
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