Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
72549cf3
Commit
72549cf3
authored
Aug 12, 2016
by
Awais Jibran
Committed by
GitHub
Aug 12, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #13185 from edx/aj/ECOM5081-hide-credit-btn
Hide credit button from audit track students
parents
3bf3e0dc
511de4c6
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
251 additions
and
83 deletions
+251
-83
common/djangoapps/course_modes/models.py
+15
-0
common/djangoapps/student/tests/test_credit.py
+1
-1
lms/djangoapps/courseware/tests/test_credit_requirements.py
+11
-5
lms/djangoapps/courseware/tests/test_module_render.py
+1
-3
lms/djangoapps/courseware/tests/test_submitting_problems.py
+15
-7
lms/djangoapps/grades/new/course_grade.py
+1
-1
lms/djangoapps/verify_student/services.py
+1
-1
lms/djangoapps/verify_student/tests/test_services.py
+6
-1
lms/djangoapps/verify_student/views.py
+1
-1
openedx/core/djangoapps/credit/api/eligibility.py
+21
-10
openedx/core/djangoapps/credit/services.py
+1
-1
openedx/core/djangoapps/credit/signals.py
+3
-4
openedx/core/djangoapps/credit/tests/test_api.py
+106
-40
openedx/core/djangoapps/credit/tests/test_services.py
+51
-6
openedx/core/djangoapps/credit/tests/test_signals.py
+16
-1
openedx/core/djangoapps/signals/signals.py
+1
-1
No files found.
common/djangoapps/course_modes/models.py
View file @
72549cf3
...
...
@@ -128,6 +128,9 @@ class CourseMode(models.Model):
# Modes that allow a student to earn credit with a university partner
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
UPSELL_TO_VERIFIED_MODES
=
[
HONOR
,
AUDIT
]
...
...
@@ -489,6 +492,18 @@ class CourseMode(models.Model):
return
mode_slug
in
cls
.
VERIFIED_MODES
@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
):
"""Check whether this is a credit mode.
...
...
common/djangoapps/student/tests/test_credit.py
View file @
72549cf3
...
...
@@ -177,7 +177,7 @@ class CreditCourseDashboardTest(ModuleStoreTestCase):
def
_make_eligible
(
self
):
"""Make the user eligible for credit in the course. """
credit_api
.
set_credit_requirement_status
(
self
.
USERNAME
,
self
.
user
,
self
.
course
.
id
,
# pylint: disable=no-member
"grade"
,
"grade"
,
status
=
"satisfied"
,
...
...
lms/djangoapps/courseware/tests/test_credit_requirements.py
View file @
72549cf3
...
...
@@ -97,16 +97,19 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase):
)
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
(
self
.
user
.
username
,
self
.
course
.
id
,
self
.
user
,
self
.
course
.
id
,
"grade"
,
"grade"
,
status
=
"satisfied"
,
reason
=
{
"final_grade"
:
0.95
}
)
credit_api
.
set_credit_requirement_status
(
self
.
user
.
username
,
self
.
course
.
id
,
self
.
user
,
self
.
course
.
id
,
"reverification"
,
"midterm"
,
status
=
"satisfied"
,
reason
=
{}
)
...
...
@@ -123,9 +126,12 @@ class ProgressPageCreditRequirementsTest(SharedModuleStoreTestCase):
self
.
assertNotContains
(
response
,
"95
%
"
)
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
(
self
.
user
.
username
,
self
.
course
.
id
,
self
.
user
,
self
.
course
.
id
,
"reverification"
,
"midterm"
,
status
=
"failed"
,
reason
=
{}
)
...
...
lms/djangoapps/courseware/tests/test_module_render.py
View file @
72549cf3
...
...
@@ -940,7 +940,6 @@ class TestProctoringRendering(SharedModuleStoreTestCase):
Verifies gated content from the student view rendering of a sequence
this is labeled as a proctored exam
"""
usage_key
=
self
.
_setup_test_data
(
enrollment_mode
,
is_practice_exam
,
attempt_status
)
# initialize some credit requirements, if so then specify
...
...
@@ -966,7 +965,7 @@ class TestProctoringRendering(SharedModuleStoreTestCase):
)
set_credit_requirement_status
(
self
.
request
.
user
.
username
,
self
.
request
.
user
,
self
.
course_key
,
'reverification'
,
'ICRV1'
...
...
@@ -1021,7 +1020,6 @@ class TestProctoringRendering(SharedModuleStoreTestCase):
if
attempt_status
:
create_exam_attempt
(
exam_id
,
self
.
request
.
user
.
id
,
taking_as_proctored
=
True
)
update_attempt_status
(
exam_id
,
self
.
request
.
user
.
id
,
attempt_status
)
return
usage_key
def
_find_url_name
(
self
,
toc
,
url_name
):
...
...
lms/djangoapps/courseware/tests/test_submitting_problems.py
View file @
72549cf3
...
...
@@ -2,6 +2,7 @@
"""
Integration tests for submitting problem responses and getting grades.
"""
import
ddt
import
json
import
os
from
textwrap
import
dedent
...
...
@@ -19,10 +20,11 @@ from capa.tests.response_xml_factory import (
CodeResponseXMLFactory
,
)
from
lms.djangoapps.grades
import
course_grades
,
progress
from
course_modes.models
import
CourseMode
from
courseware.models
import
StudentModule
,
BaseStudentModuleHistory
from
courseware.tests.helpers
import
LoginEnrollmentTestCase
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.factories
import
CourseFactory
,
ItemFactory
from
xmodule.partitions.partitions
import
Group
,
UserPartition
...
...
@@ -304,6 +306,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase, Probl
@attr
(
shard
=
3
)
@ddt.ddt
class
TestCourseGrader
(
TestSubmittingProblems
):
"""
Suite of tests for the course grader.
...
...
@@ -315,7 +318,6 @@ class TestCourseGrader(TestSubmittingProblems):
"""
Set up a simple course for testing basic grading functionality.
"""
grading_policy
=
{
"GRADER"
:
[{
"type"
:
"Homework"
,
...
...
@@ -646,20 +648,26 @@ class TestCourseGrader(TestSubmittingProblems):
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
])
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
status will be updated as satisfied in requirement status table.
"""
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
(
'p2'
,
{
'2_1'
:
'Correct'
})
# Enable the course for credit
credit_course
=
CreditCourse
.
objects
.
create
(
course_key
=
self
.
course
.
id
,
enabled
=
True
,
)
CreditCourse
.
objects
.
create
(
course_key
=
self
.
course
.
id
,
enabled
=
True
)
# Configure a credit provider for the course
CreditProvider
.
objects
.
create
(
...
...
lms/djangoapps/grades/new/course_grade.py
View file @
72549cf3
...
...
@@ -155,7 +155,7 @@ class CourseGrade(object):
"""
responses
=
GRADES_UPDATED
.
send_robust
(
sender
=
None
,
user
name
=
self
.
student
.
username
,
user
=
self
.
student
,
grade_summary
=
self
.
summary
,
course_key
=
self
.
course
.
id
,
deadline
=
self
.
course
.
end
...
...
lms/djangoapps/verify_student/services.py
View file @
72549cf3
...
...
@@ -112,7 +112,7 @@ class ReverificationService(object):
# As a user skips the reverification it declines to fulfill the requirement so
# requirement sets to declined.
set_credit_requirement_status
(
user
.
username
,
user
,
course_key
,
'reverification'
,
checkpoint
.
checkpoint_location
,
...
...
lms/djangoapps/verify_student/tests/test_services.py
View file @
72549cf3
...
...
@@ -124,7 +124,10 @@ class TestReverificationService(ModuleStoreTestCase):
'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
requirement status model when a user skip's an ICRV.
"""
...
...
@@ -135,6 +138,8 @@ class TestReverificationService(ModuleStoreTestCase):
)
# Create credit course and set credit requirements.
CreditCourse
.
objects
.
create
(
course_key
=
self
.
course_key
,
enabled
=
True
)
self
.
enrollment
.
update_enrollment
(
mode
=
mode
)
set_credit_requirements
(
self
.
course_key
,
[
...
...
lms/djangoapps/verify_student/views.py
View file @
72549cf3
...
...
@@ -1245,7 +1245,7 @@ def _set_user_requirement_status(attempt, namespace, status, reason=None):
if
checkpoint
is
not
None
:
try
:
set_credit_requirement_status
(
attempt
.
user
.
username
,
attempt
.
user
,
checkpoint
.
course_id
,
namespace
,
checkpoint
.
checkpoint_location
,
...
...
openedx/core/djangoapps/credit/api/eligibility.py
View file @
72549cf3
...
...
@@ -13,6 +13,9 @@ from openedx.core.djangoapps.credit.models import (
CreditCourse
,
CreditRequirement
,
CreditRequirementStatus
,
CreditEligibility
,
CreditRequest
)
from
course_modes.models
import
CourseMode
from
student.models
import
CourseEnrollment
# TODO: Cleanup this mess! ECOM-2908
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -196,7 +199,7 @@ def get_eligibilities_for_user(username, course_key=None):
]
def
set_credit_requirement_status
(
user
name
,
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.
...
...
@@ -205,7 +208,7 @@ def set_credit_requirement_status(username, course_key, req_namespace, req_name,
as eligible for credit in the course.
Args:
user
name (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.
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)
...
...
@@ -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
if
CreditRequest
.
get_user_request_status
(
username
,
course_key
):
if
CreditRequest
.
get_user_request_status
(
user
.
user
name
,
course_key
):
log
.
info
(
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".'
,
req_namespace
,
req_name
,
username
,
course_key
req_namespace
,
req_name
,
user
.
user
name
,
course_key
)
return
# 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
.
user
name
)
if
eligible_before_update
and
status
==
'failed'
:
log
.
info
(
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".'
,
req_namespace
,
req_name
,
username
,
course_key
req_namespace
,
req_name
,
user
.
user
name
,
course_key
)
return
...
...
@@ -269,22 +280,22 @@ def set_credit_requirement_status(username, course_key, req_namespace, req_name,
u'because the requirement does not exist. '
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
.
user
name
,
status
)
return
# Update the requirement status
CreditRequirementStatus
.
add_or_update_requirement_status
(
username
,
req_to_update
,
status
=
status
,
reason
=
reason
user
.
user
name
,
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
# 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
:
is_eligible
,
eligibility_record_created
=
CreditEligibility
.
update_eligibility
(
reqs
,
username
,
course_key
)
is_eligible
,
eligibility_record_created
=
CreditEligibility
.
update_eligibility
(
reqs
,
user
.
user
name
,
course_key
)
if
eligibility_record_created
and
is_eligible
:
try
:
send_credit_notifications
(
username
,
course_key
)
send_credit_notifications
(
user
.
user
name
,
course_key
)
except
Exception
:
# pylint: disable=broad-except
log
.
error
(
"Error sending email"
)
...
...
openedx/core/djangoapps/credit/services.py
View file @
72549cf3
...
...
@@ -155,7 +155,7 @@ class CreditService(object):
return
None
api_set_credit_requirement_status
(
user
.
username
,
user
,
course_key
,
req_namespace
,
req_name
,
...
...
openedx/core/djangoapps/credit/signals.py
View file @
72549cf3
...
...
@@ -53,12 +53,12 @@ def on_pre_publish(sender, course_key, **kwargs): # pylint: disable=unused-argu
@receiver
(
GRADES_UPDATED
)
def
listen_for_grade_calculation
(
sender
,
user
name
,
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.
Args:
sender: None
user
name(string): user name
user
(User): User Model object
grade_summary(dict): Dict containing output from the course grader
course_key(CourseKey): The key for the course
deadline(datetime): Course end date or None
...
...
@@ -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
# that can cause syncdb to fail.
from
openedx.core.djangoapps.credit
import
api
course_id
=
CourseKey
.
from_string
(
unicode
(
course_key
))
is_credit
=
api
.
is_credit_course
(
course_id
)
if
is_credit
:
...
...
@@ -113,5 +112,5 @@ def listen_for_grade_calculation(sender, username, grade_summary, course_key, de
# time to do so.
if
status
and
reason
:
api
.
set_credit_requirement_status
(
user
name
,
course_id
,
'grade'
,
'grade'
,
status
=
status
,
reason
=
reason
user
,
course_id
,
'grade'
,
'grade'
,
status
=
status
,
reason
=
reason
)
openedx/core/djangoapps/credit/tests/test_api.py
View file @
72549cf3
...
...
@@ -36,6 +36,8 @@ from openedx.core.djangoapps.credit.models import (
CreditEligibility
,
CreditRequest
)
from
course_modes.models
import
CourseMode
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
util.date_utils
import
from_timestamp
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
...
@@ -177,6 +179,19 @@ class CreditApiTestBase(ModuleStoreTestCase):
return
credit_course
def
create_and_enroll_user
(
self
,
username
,
password
,
course_id
=
None
,
mode
=
CourseMode
.
VERIFIED
):
""" Create and enroll the user in the given course's and given mode."""
if
course_id
is
None
:
course_id
=
self
.
course_key
user
=
UserFactory
.
create
(
username
=
username
,
password
=
password
)
self
.
enroll
(
user
,
course_id
,
mode
)
return
user
def
enroll
(
self
,
user
,
course_id
,
mode
):
"""Enroll user in given course and mode"""
return
CourseEnrollment
.
enroll
(
user
,
course_id
,
mode
=
mode
)
def
_mock_ecommerce_courses_api
(
self
,
course_key
,
body
,
status
=
200
):
""" Mock GET requests to the ecommerce course API endpoint. """
httpretty
.
reset
()
...
...
@@ -330,14 +345,54 @@ class CreditRequirementApiTests(CreditApiTestBase):
def
test_is_user_eligible_for_credit
(
self
):
credit_course
=
self
.
add_credit_course
()
CreditEligibility
.
objects
.
create
(
course
=
credit_course
,
username
=
"staff"
course
=
credit_course
,
username
=
self
.
user
.
username
)
is_eligible
=
api
.
is_user_eligible_for_credit
(
'staff'
,
credit_course
.
course_key
)
is_eligible
=
api
.
is_user_eligible_for_credit
(
self
.
user
.
username
,
credit_course
.
course_key
)
self
.
assertTrue
(
is_eligible
)
is_eligible
=
api
.
is_user_eligible_for_credit
(
'abc'
,
credit_course
.
course_key
)
self
.
assertFalse
(
is_eligible
)
@ddt.data
(
CourseMode
.
AUDIT
,
CourseMode
.
HONOR
,
CourseMode
.
CREDIT_MODE
)
def
test_user_eligibility_with_non_verified_enrollment
(
self
,
mode
):
"""
Tests that user do not become credit eligible even after meeting the credit requirements.
User can not become credit eligible if he does not has credit eligible enrollment in the course.
"""
self
.
add_credit_course
()
# Enroll user and verify his enrollment.
self
.
enroll
(
self
.
user
,
self
.
course_key
,
mode
)
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course_key
))
self
.
assertTrue
(
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
self
.
course_key
),
(
mode
,
True
))
requirements
=
[
{
"namespace"
:
"grade"
,
"name"
:
"grade"
,
"display_name"
:
"Grade"
,
"criteria"
:
{
"min_grade"
:
0.6
},
}
]
# Set & verify course credit requirements.
api
.
set_credit_requirements
(
self
.
course_key
,
requirements
)
requirements
=
api
.
get_credit_requirements
(
self
.
course_key
)
self
.
assertEqual
(
len
(
requirements
),
1
)
# Set the requirement to "satisfied" and check that they are not set for non-credit eligible enrollment.
api
.
set_credit_requirement_status
(
self
.
user
,
self
.
course_key
,
"grade"
,
"grade"
,
status
=
'satisfied'
)
self
.
assert_grade_requirement_status
(
None
,
0
)
# Verify user is not eligible for credit.
self
.
assertFalse
(
api
.
is_user_eligible_for_credit
(
self
.
user
.
username
,
self
.
course_key
))
def
test_eligibility_expired
(
self
):
# Configure a credit eligibility that expired yesterday
credit_course
=
self
.
add_credit_course
()
...
...
@@ -376,14 +431,23 @@ class CreditRequirementApiTests(CreditApiTestBase):
def
assert_grade_requirement_status
(
self
,
expected_status
,
expected_order
):
""" Assert the status and order of the grade requirement. """
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
'staff'
,
namespace
=
"grade"
,
name
=
"grade"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
self
.
user
,
namespace
=
"grade"
,
name
=
"grade"
)
self
.
assertEqual
(
req_status
[
0
][
"status"
],
expected_status
)
self
.
assertEqual
(
req_status
[
0
][
"order"
],
expected_order
)
return
req_status
def
test_set_credit_requirement_status
(
self
):
username
=
"staff"
@ddt.data
(
*
CourseMode
.
CREDIT_ELIGIBLE_MODES
)
def
test_set_credit_requirement_status
(
self
,
mode
):
username
=
self
.
user
.
username
credit_course
=
self
.
add_credit_course
()
# Enroll user and verify his enrollment.
self
.
enroll
(
self
.
user
,
self
.
course_key
,
mode
)
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course_key
))
self
.
assertTrue
(
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
self
.
course_key
),
(
mode
,
True
))
requirements
=
[
{
"namespace"
:
"grade"
,
...
...
@@ -400,7 +464,6 @@ class CreditRequirementApiTests(CreditApiTestBase):
"criteria"
:
{},
}
]
api
.
set_credit_requirements
(
self
.
course_key
,
requirements
)
course_requirements
=
api
.
get_credit_requirements
(
self
.
course_key
)
self
.
assertEqual
(
len
(
course_requirements
),
2
)
...
...
@@ -414,19 +477,19 @@ class CreditRequirementApiTests(CreditApiTestBase):
provider
=
CreditProvider
.
objects
.
first
(),
username
=
username
,
)
api
.
set_credit_requirement_status
(
username
,
self
.
course_key
,
"grade"
,
"grade"
)
api
.
set_credit_requirement_status
(
self
.
user
,
self
.
course_key
,
"grade"
,
"grade"
)
self
.
assert_grade_requirement_status
(
None
,
0
)
credit_request
.
delete
()
# Set the requirement to "satisfied" and check that it's actually set
api
.
set_credit_requirement_status
(
username
,
self
.
course_key
,
"grade"
,
"grade"
)
api
.
set_credit_requirement_status
(
self
.
user
,
self
.
course_key
,
"grade"
,
"grade"
)
self
.
assert_grade_requirement_status
(
'satisfied'
,
0
)
# Set the requirement to "failed" and check that it's actually set
api
.
set_credit_requirement_status
(
username
,
self
.
course_key
,
"grade"
,
"grade"
,
status
=
"failed"
)
api
.
set_credit_requirement_status
(
self
.
user
,
self
.
course_key
,
"grade"
,
"grade"
,
status
=
"failed"
)
self
.
assert_grade_requirement_status
(
'failed'
,
0
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
"staff"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
username
)
self
.
assertEqual
(
req_status
[
0
][
"status"
],
"failed"
)
self
.
assertEqual
(
req_status
[
0
][
"order"
],
0
)
...
...
@@ -436,7 +499,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
# Set the requirement to "declined" and check that it's actually set
api
.
set_credit_requirement_status
(
username
,
self
.
course_key
,
self
.
user
,
self
.
course_key
,
"reverification"
,
"i4x://edX/DemoX/edx-reverification-block/assessment_uuid"
,
status
=
"declined"
...
...
@@ -449,8 +512,13 @@ class CreditRequirementApiTests(CreditApiTestBase):
)
self
.
assertEqual
(
req_status
[
0
][
"status"
],
"declined"
)
def
test_remove_credit_requirement_status
(
self
):
@ddt.data
(
*
CourseMode
.
CREDIT_ELIGIBLE_MODES
)
def
test_remove_credit_requirement_status
(
self
,
mode
):
self
.
add_credit_course
()
self
.
enroll
(
self
.
user
,
self
.
course_key
,
mode
)
username
=
self
.
user
.
username
requirements
=
[
{
"namespace"
:
"grade"
,
...
...
@@ -467,27 +535,26 @@ class CreditRequirementApiTests(CreditApiTestBase):
"criteria"
:
{},
}
]
api
.
set_credit_requirements
(
self
.
course_key
,
requirements
)
course_requirements
=
api
.
get_credit_requirements
(
self
.
course_key
)
self
.
assertEqual
(
len
(
course_requirements
),
2
)
# before setting credit_requirement_status
api
.
remove_credit_requirement_status
(
"staff"
,
self
.
course_key
,
"grade"
,
"grade"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
"staff"
,
namespace
=
"grade"
,
name
=
"grade"
)
api
.
remove_credit_requirement_status
(
username
,
self
.
course_key
,
"grade"
,
"grade"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
username
,
namespace
=
"grade"
,
name
=
"grade"
)
self
.
assertIsNone
(
req_status
[
0
][
"status"
])
self
.
assertIsNone
(
req_status
[
0
][
"status_date"
])
self
.
assertIsNone
(
req_status
[
0
][
"reason"
])
# Set the requirement to "satisfied" and check that it's actually set
api
.
set_credit_requirement_status
(
"staff"
,
self
.
course_key
,
"grade"
,
"grade"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
"staff"
,
namespace
=
"grade"
,
name
=
"grade"
)
api
.
set_credit_requirement_status
(
self
.
user
,
self
.
course_key
,
"grade"
,
"grade"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
username
,
namespace
=
"grade"
,
name
=
"grade"
)
self
.
assertEqual
(
len
(
req_status
),
1
)
self
.
assertEqual
(
req_status
[
0
][
"status"
],
"satisfied"
)
# remove the credit requirement status and check that it's actually removed
api
.
remove_credit_requirement_status
(
"staff"
,
self
.
course_key
,
"grade"
,
"grade"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
"staff"
,
namespace
=
"grade"
,
name
=
"grade"
)
api
.
remove_credit_requirement_status
(
self
.
user
.
username
,
self
.
course_key
,
"grade"
,
"grade"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
username
,
namespace
=
"grade"
,
name
=
"grade"
)
self
.
assertIsNone
(
req_status
[
0
][
"status"
])
self
.
assertIsNone
(
req_status
[
0
][
"status_date"
])
self
.
assertIsNone
(
req_status
[
0
][
"reason"
])
...
...
@@ -522,6 +589,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
# Configure a course with two credit requirements
self
.
add_credit_course
()
user
=
self
.
create_and_enroll_user
(
username
=
self
.
USER_INFO
[
'username'
],
password
=
self
.
USER_INFO
[
'password'
])
CourseFactory
.
create
(
org
=
'edX'
,
number
=
'DemoX'
,
display_name
=
'Demo_Course'
)
requirements
=
[
...
...
@@ -542,31 +610,29 @@ class CreditRequirementApiTests(CreditApiTestBase):
]
api
.
set_credit_requirements
(
self
.
course_key
,
requirements
)
user
=
UserFactory
.
create
(
username
=
self
.
USER_INFO
[
'username'
],
password
=
self
.
USER_INFO
[
'password'
])
# Satisfy one of the requirements, but not the other
with
self
.
assertNumQueries
(
1
2
):
with
self
.
assertNumQueries
(
1
3
):
api
.
set_credit_requirement_status
(
user
.
username
,
user
,
self
.
course_key
,
requirements
[
0
][
"namespace"
],
requirements
[
0
][
"name"
]
)
# The user should not be eligible (because only one requirement is satisfied)
self
.
assertFalse
(
api
.
is_user_eligible_for_credit
(
"bob"
,
self
.
course_key
))
self
.
assertFalse
(
api
.
is_user_eligible_for_credit
(
user
.
username
,
self
.
course_key
))
# Satisfy the other requirement
with
self
.
assertNumQueries
(
2
1
):
with
self
.
assertNumQueries
(
2
2
):
api
.
set_credit_requirement_status
(
"bob"
,
user
,
self
.
course_key
,
requirements
[
1
][
"namespace"
],
requirements
[
1
][
"name"
]
)
# Now the user should be eligible
self
.
assertTrue
(
api
.
is_user_eligible_for_credit
(
"bob"
,
self
.
course_key
))
self
.
assertTrue
(
api
.
is_user_eligible_for_credit
(
user
.
username
,
self
.
course_key
))
# Credit eligibility email should be sent
self
.
assertEqual
(
len
(
mail
.
outbox
),
1
)
...
...
@@ -611,9 +677,9 @@ class CreditRequirementApiTests(CreditApiTestBase):
# Delete the eligibility entries and satisfy the user's eligibility
# requirement again to trigger eligibility notification
CreditEligibility
.
objects
.
all
()
.
delete
()
with
self
.
assertNumQueries
(
1
6
):
with
self
.
assertNumQueries
(
1
7
):
api
.
set_credit_requirement_status
(
"bob"
,
user
,
self
.
course_key
,
requirements
[
1
][
"namespace"
],
requirements
[
1
][
"name"
]
...
...
@@ -629,26 +695,27 @@ class CreditRequirementApiTests(CreditApiTestBase):
# The user should remain eligible even if the requirement status is later changed
api
.
set_credit_requirement_status
(
"bob"
,
user
,
self
.
course_key
,
requirements
[
0
][
"namespace"
],
requirements
[
0
][
"name"
],
status
=
"failed"
)
self
.
assertTrue
(
api
.
is_user_eligible_for_credit
(
"bob"
,
self
.
course_key
))
self
.
assertTrue
(
api
.
is_user_eligible_for_credit
(
user
.
username
,
self
.
course_key
))
def
test_set_credit_requirement_status_req_not_configured
(
self
):
# Configure a credit course with no requirements
username
=
self
.
user
.
username
self
.
add_credit_course
()
# A user satisfies a requirement. This could potentially
# happen if there's a lag when the requirements are updated
# after the course is published.
api
.
set_credit_requirement_status
(
"bob"
,
self
.
course_key
,
"grade"
,
"grade"
)
api
.
set_credit_requirement_status
(
self
.
user
,
self
.
course_key
,
"grade"
,
"grade"
)
# Since the requirement hasn't been published yet, it won't show
# up in the list of requirements.
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
"bob"
,
namespace
=
"grade"
,
name
=
"grade"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
username
,
namespace
=
"grade"
,
name
=
"grade"
)
self
.
assertEqual
(
req_status
,
[])
# Now add the requirements, simulating what happens when a course is published.
...
...
@@ -672,7 +739,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
# The user should not have satisfied the requirements, since they weren't
# in effect when the user completed the requirement
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
"bob"
)
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
username
)
self
.
assertEqual
(
len
(
req_status
),
2
)
self
.
assertEqual
(
req_status
[
0
][
"status"
],
None
)
self
.
assertEqual
(
req_status
[
0
][
"status"
],
None
)
...
...
@@ -680,7 +747,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
# The user should *not* have satisfied the reverification requirement
req_status
=
api
.
get_credit_requirement_status
(
self
.
course_key
,
"bob"
,
username
,
namespace
=
requirements
[
1
][
"namespace"
],
name
=
requirements
[
1
][
"name"
]
)
...
...
@@ -713,6 +780,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
"""
# Configure a course with two credit requirements
self
.
add_credit_course
()
user
=
self
.
create_and_enroll_user
(
username
=
self
.
USER_INFO
[
'username'
],
password
=
self
.
USER_INFO
[
'password'
])
CourseFactory
.
create
(
org
=
'edX'
,
number
=
'DemoX'
,
display_name
=
'Demo_Course'
)
requirements
=
[
{
...
...
@@ -732,11 +800,9 @@ class CreditRequirementApiTests(CreditApiTestBase):
]
api
.
set_credit_requirements
(
self
.
course_key
,
requirements
)
user
=
UserFactory
.
create
(
username
=
self
.
USER_INFO
[
'username'
],
password
=
self
.
USER_INFO
[
'password'
])
# Satisfy one of the requirements, but not the other
api
.
set_credit_requirement_status
(
user
.
username
,
user
,
self
.
course_key
,
requirements
[
0
][
"namespace"
],
requirements
[
0
][
"name"
]
...
...
@@ -745,13 +811,13 @@ class CreditRequirementApiTests(CreditApiTestBase):
with
mock
.
patch
(
'openedx.core.djangoapps.credit.email_utils.get_credit_provider_display_names'
)
as
mock_method
:
mock_method
.
return_value
=
providers_list
api
.
set_credit_requirement_status
(
"bob"
,
user
,
self
.
course_key
,
requirements
[
1
][
"namespace"
],
requirements
[
1
][
"name"
]
)
# Now the user should be eligible
self
.
assertTrue
(
api
.
is_user_eligible_for_credit
(
"bob"
,
self
.
course_key
))
self
.
assertTrue
(
api
.
is_user_eligible_for_credit
(
user
.
username
,
self
.
course_key
))
# Credit eligibility email should be sent
self
.
assertEqual
(
len
(
mail
.
outbox
),
1
)
...
...
openedx/core/djangoapps/credit/tests/test_services.py
View file @
72549cf3
...
...
@@ -2,7 +2,9 @@
Tests for the Credit xBlock service
"""
import
ddt
from
nose.plugins.attrib
import
attr
from
course_modes.models
import
CourseMode
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
...
...
@@ -15,6 +17,7 @@ from student.models import CourseEnrollment, UserProfile
@attr
(
shard
=
2
)
@ddt.ddt
class
CreditServiceTests
(
ModuleStoreTestCase
):
"""
Tests for the Credit xBlock service
...
...
@@ -28,14 +31,14 @@ class CreditServiceTests(ModuleStoreTestCase):
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'
)
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
course if not
provided.
Enroll the test user in the given course's
mode. Use course/mode if they are
provided.
"""
if
course_id
is
None
:
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
):
"""
...
...
@@ -127,7 +130,7 @@ class CreditServiceTests(ModuleStoreTestCase):
self
.
assertIsNotNone
(
credit_state
)
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
(
len
(
credit_state
[
'credit_requirement_status'
]),
1
)
self
.
assertEqual
(
credit_state
[
'credit_requirement_status'
][
0
][
'name'
],
'grade'
)
...
...
@@ -286,6 +289,48 @@ class CreditServiceTests(ModuleStoreTestCase):
self
.
assertFalse
(
credit_state
[
'is_credit_course'
])
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
):
"""
Try setting requirements status with a bad user_id
...
...
@@ -348,7 +393,7 @@ class CreditServiceTests(ModuleStoreTestCase):
credit_state
=
self
.
service
.
get_credit_state
(
self
.
user
.
id
,
unicode
(
self
.
course
.
id
))
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
(
len
(
credit_state
[
'credit_requirement_status'
]),
1
)
self
.
assertEqual
(
credit_state
[
'credit_requirement_status'
][
0
][
'name'
],
'grade'
)
...
...
openedx/core/djangoapps/credit/tests/test_signals.py
View file @
72549cf3
...
...
@@ -10,6 +10,8 @@ from unittest import skipUnless
from
django.conf
import
settings
from
django.test.client
import
RequestFactory
from
nose.plugins.attrib
import
attr
from
course_modes.models
import
CourseMode
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
...
...
@@ -66,9 +68,12 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase):
# Add a single credit requirement (final grade)
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
):
""" 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'
)
self
.
assertEqual
(
req_status
[
0
][
'status'
],
expected_status
)
...
...
@@ -109,3 +114,13 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase):
def
test_min_grade_requirement_failed_grade_expired_deadline
(
self
):
"""Test with failed grades and deadline expire"""
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
)
openedx/core/djangoapps/signals/signals.py
View file @
72549cf3
...
...
@@ -6,7 +6,7 @@ from django.dispatch import Signal
# Signal that fires when a user is graded (in lms/grades/course_grades.py)
GRADES_UPDATED
=
Signal
(
providing_args
=
[
"user
name
"
,
"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)
# TODO: runtime coupling between apps will be reduced if this event is changed to carry a username
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment