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
511de4c6
Commit
511de4c6
authored
Aug 05, 2016
by
Awais Jibran
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Hide credit button from audit track students
ECOM-5081
parent
220cf1b4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
145 additions
and
43 deletions
+145
-43
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
+0
-0
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 @
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.
...
...
common/djangoapps/student/tests/test_credit.py
View file @
511de4c6
...
@@ -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"
,
...
...
lms/djangoapps/courseware/tests/test_credit_requirements.py
View file @
511de4c6
...
@@ -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
=
{}
)
)
...
...
lms/djangoapps/courseware/tests/test_module_render.py
View file @
511de4c6
...
@@ -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
):
...
...
lms/djangoapps/courseware/tests/test_submitting_problems.py
View file @
511de4c6
...
@@ -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
(
...
...
lms/djangoapps/grades/new/course_grade.py
View file @
511de4c6
...
@@ -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
,
user
name
=
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
...
...
lms/djangoapps/verify_student/services.py
View file @
511de4c6
...
@@ -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
,
...
...
lms/djangoapps/verify_student/tests/test_services.py
View file @
511de4c6
...
@@ -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
,
[
[
...
...
lms/djangoapps/verify_student/views.py
View file @
511de4c6
...
@@ -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
,
...
...
openedx/core/djangoapps/credit/api/eligibility.py
View file @
511de4c6
...
@@ -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
(
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.
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:
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.
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
.
user
name
,
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
.
user
name
,
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
.
user
name
)
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
.
user
name
,
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
.
user
name
,
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
.
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
# 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
.
user
name
,
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
.
user
name
,
course_key
)
except
Exception
:
# pylint: disable=broad-except
except
Exception
:
# pylint: disable=broad-except
log
.
error
(
"Error sending email"
)
log
.
error
(
"Error sending email"
)
...
...
openedx/core/djangoapps/credit/services.py
View file @
511de4c6
...
@@ -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
,
...
...
openedx/core/djangoapps/credit/signals.py
View file @
511de4c6
...
@@ -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
,
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.
"""Receive 'MIN_GRADE_REQUIREMENT_STATUS' signal and update minimum grade requirement status.
Args:
Args:
sender: None
sender: None
user
name(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
(
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 @
511de4c6
This diff is collapsed.
Click to expand it.
openedx/core/djangoapps/credit/tests/test_services.py
View file @
511de4c6
...
@@ -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'
)
...
...
openedx/core/djangoapps/credit/tests/test_signals.py
View file @
511de4c6
...
@@ -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
)
openedx/core/djangoapps/signals/signals.py
View file @
511de4c6
...
@@ -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
=
[
"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)
# 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
...
...
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