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
b8e04f30
Commit
b8e04f30
authored
Jun 22, 2015
by
Awais
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ECOM-1597 adding signals to update min-grade requirement.
parent
614bcafb
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
233 additions
and
25 deletions
+233
-25
lms/djangoapps/ccx/tests/test_field_override_performance.py
+24
-24
lms/djangoapps/courseware/grades.py
+15
-1
lms/djangoapps/courseware/tests/test_submitting_problems.py
+42
-0
lms/djangoapps/courseware/tests/test_views.py
+1
-0
openedx/core/djangoapps/credit/signals.py
+42
-0
openedx/core/djangoapps/credit/tests/test_signals.py
+100
-0
openedx/core/djangoapps/signals/__init__.py
+0
-0
openedx/core/djangoapps/signals/signals.py
+9
-0
No files found.
lms/djangoapps/ccx/tests/test_field_override_performance.py
View file @
b8e04f30
...
...
@@ -173,18 +173,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
TEST_DATA
=
{
# (providers, course_width, enable_ccx): # of sql queries, # of mongo queries, # of xblocks
(
'no_overrides'
,
1
,
True
):
(
2
6
,
7
,
14
),
(
'no_overrides'
,
2
,
True
):
(
13
4
,
7
,
85
),
(
'no_overrides'
,
3
,
True
):
(
59
4
,
7
,
336
),
(
'ccx'
,
1
,
True
):
(
2
6
,
7
,
14
),
(
'ccx'
,
2
,
True
):
(
13
4
,
7
,
85
),
(
'ccx'
,
3
,
True
):
(
59
4
,
7
,
336
),
(
'no_overrides'
,
1
,
False
):
(
2
6
,
7
,
14
),
(
'no_overrides'
,
2
,
False
):
(
13
4
,
7
,
85
),
(
'no_overrides'
,
3
,
False
):
(
59
4
,
7
,
336
),
(
'ccx'
,
1
,
False
):
(
2
6
,
7
,
14
),
(
'ccx'
,
2
,
False
):
(
13
4
,
7
,
85
),
(
'ccx'
,
3
,
False
):
(
59
4
,
7
,
336
),
(
'no_overrides'
,
1
,
True
):
(
2
7
,
7
,
14
),
(
'no_overrides'
,
2
,
True
):
(
13
5
,
7
,
85
),
(
'no_overrides'
,
3
,
True
):
(
59
5
,
7
,
336
),
(
'ccx'
,
1
,
True
):
(
2
7
,
7
,
14
),
(
'ccx'
,
2
,
True
):
(
13
5
,
7
,
85
),
(
'ccx'
,
3
,
True
):
(
59
5
,
7
,
336
),
(
'no_overrides'
,
1
,
False
):
(
2
7
,
7
,
14
),
(
'no_overrides'
,
2
,
False
):
(
13
5
,
7
,
85
),
(
'no_overrides'
,
3
,
False
):
(
59
5
,
7
,
336
),
(
'ccx'
,
1
,
False
):
(
2
7
,
7
,
14
),
(
'ccx'
,
2
,
False
):
(
13
5
,
7
,
85
),
(
'ccx'
,
3
,
False
):
(
59
5
,
7
,
336
),
}
...
...
@@ -196,16 +196,16 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__
=
True
TEST_DATA
=
{
(
'no_overrides'
,
1
,
True
):
(
2
6
,
4
,
9
),
(
'no_overrides'
,
2
,
True
):
(
13
4
,
19
,
54
),
(
'no_overrides'
,
3
,
True
):
(
59
4
,
84
,
215
),
(
'ccx'
,
1
,
True
):
(
2
6
,
4
,
9
),
(
'ccx'
,
2
,
True
):
(
13
4
,
19
,
54
),
(
'ccx'
,
3
,
True
):
(
59
4
,
84
,
215
),
(
'no_overrides'
,
1
,
False
):
(
2
6
,
4
,
9
),
(
'no_overrides'
,
2
,
False
):
(
13
4
,
19
,
54
),
(
'no_overrides'
,
3
,
False
):
(
59
4
,
84
,
215
),
(
'ccx'
,
1
,
False
):
(
2
6
,
4
,
9
),
(
'ccx'
,
2
,
False
):
(
13
4
,
19
,
54
),
(
'ccx'
,
3
,
False
):
(
59
4
,
84
,
215
),
(
'no_overrides'
,
1
,
True
):
(
2
7
,
4
,
9
),
(
'no_overrides'
,
2
,
True
):
(
13
5
,
19
,
54
),
(
'no_overrides'
,
3
,
True
):
(
59
5
,
84
,
215
),
(
'ccx'
,
1
,
True
):
(
2
7
,
4
,
9
),
(
'ccx'
,
2
,
True
):
(
13
5
,
19
,
54
),
(
'ccx'
,
3
,
True
):
(
59
5
,
84
,
215
),
(
'no_overrides'
,
1
,
False
):
(
2
7
,
4
,
9
),
(
'no_overrides'
,
2
,
False
):
(
13
5
,
19
,
54
),
(
'no_overrides'
,
3
,
False
):
(
59
5
,
84
,
215
),
(
'ccx'
,
1
,
False
):
(
2
7
,
4
,
9
),
(
'ccx'
,
2
,
False
):
(
13
5
,
19
,
54
),
(
'ccx'
,
3
,
False
):
(
59
5
,
84
,
215
),
}
lms/djangoapps/courseware/grades.py
View file @
b8e04f30
...
...
@@ -26,6 +26,7 @@ from submissions import api as sub_api # installed from the edx-submissions rep
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.signals.signals
import
GRADES_UPDATED
log
=
logging
.
getLogger
(
"edx.courseware"
)
...
...
@@ -126,9 +127,22 @@ def grade(student, request, course, keep_raw_scores=False):
"""
Wraps "_grade" with the manual_transaction context manager just in case
there are unanticipated errors.
Send a signal to update the minimum grade requirement status.
"""
with
manual_transaction
():
return
_grade
(
student
,
request
,
course
,
keep_raw_scores
)
grade_summary
=
_grade
(
student
,
request
,
course
,
keep_raw_scores
)
responses
=
GRADES_UPDATED
.
send_robust
(
sender
=
None
,
username
=
request
.
user
.
username
,
grade_summary
=
grade_summary
,
course_key
=
course
.
id
,
deadline
=
course
.
end
)
for
receiver
,
response
in
responses
:
log
.
info
(
'Signal fired when student grade is calculated. Receiver:
%
s. Response:
%
s'
,
receiver
,
response
)
return
grade_summary
def
_grade
(
student
,
request
,
course
,
keep_raw_scores
):
...
...
lms/djangoapps/courseware/tests/test_submitting_problems.py
View file @
b8e04f30
...
...
@@ -26,6 +26,10 @@ from student.models import anonymous_id_for_user
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.partitions.partitions
import
Group
,
UserPartition
from
openedx.core.djangoapps.credit.api
import
(
set_credit_requirements
,
get_credit_requirement_status
)
from
openedx.core.djangoapps.credit.models
import
CreditCourse
,
CreditProvider
from
openedx.core.djangoapps.user_api.tests.factories
import
UserCourseTagFactory
...
...
@@ -238,6 +242,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
reverse
(
'progress'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
)
fake_request
.
user
=
self
.
student_user
return
grades
.
grade
(
self
.
student_user
,
fake_request
,
self
.
course
)
def
get_progress_summary
(
self
):
...
...
@@ -594,6 +599,43 @@ 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
):
"""
Test for credit course. If user passes minimum grade requirement then
status will be updated as satisfied in requirement status table.
"""
self
.
basic_setup
()
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
,
)
# Configure a credit provider for the course
credit_provider
=
CreditProvider
.
objects
.
create
(
provider_id
=
"ASU"
,
enable_integration
=
True
,
provider_url
=
"https://credit.example.com/request"
,
)
credit_course
.
providers
.
add
(
credit_provider
)
credit_course
.
save
()
requirements
=
[{
"namespace"
:
"grade"
,
"name"
:
"grade"
,
"display_name"
:
"Grade"
,
"criteria"
:
{
"min_grade"
:
0.52
}
}]
# Add a single credit requirement (final grade)
set_credit_requirements
(
self
.
course
.
id
,
requirements
)
self
.
get_grade_summary
()
req_status
=
get_credit_requirement_status
(
self
.
course
.
id
,
self
.
student_user
.
username
,
'grade'
,
'grade'
)
self
.
assertEqual
(
req_status
[
0
][
"status"
],
'satisfied'
)
@attr
(
'shard_1'
)
class
ProblemWithUploadedFilesTest
(
TestSubmittingProblems
):
...
...
lms/djangoapps/courseware/tests/test_views.py
View file @
b8e04f30
...
...
@@ -946,6 +946,7 @@ class IsCoursePassedTests(ModuleStoreTestCase):
grade_cutoffs
=
{
'cutoff'
:
0.75
,
'Pass'
:
self
.
SUCCESS_CUTOFF
}
)
self
.
request
=
RequestFactory
()
self
.
request
.
user
=
self
.
student
def
test_user_fails_if_not_clear_exam
(
self
):
# If user has not grade then false will return
...
...
openedx/core/djangoapps/credit/signals.py
View file @
b8e04f30
...
...
@@ -3,8 +3,11 @@ This file contains receivers of course publication signals.
"""
from
django.dispatch
import
receiver
from
django.utils
import
timezone
from
opaque_keys.edx.keys
import
CourseKey
from
xmodule.modulestore.django
import
SignalHandler
from
openedx.core.djangoapps.signals.signals
import
GRADES_UPDATED
@receiver
(
SignalHandler
.
course_published
)
...
...
@@ -18,3 +21,42 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=
from
.tasks
import
update_credit_course_requirements
update_credit_course_requirements
.
delay
(
unicode
(
course_key
))
@receiver
(
GRADES_UPDATED
)
def
listen_for_grade_calculation
(
sender
,
username
,
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
username(string): user name
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
Kwargs:
kwargs : None
"""
from
openedx.core.djangoapps.credit.api
import
(
is_credit_course
,
get_credit_requirement
,
set_credit_requirement_status
)
course_id
=
CourseKey
.
from_string
(
unicode
(
course_key
))
is_credit
=
is_credit_course
(
course_id
)
if
is_credit
:
requirement
=
get_credit_requirement
(
course_id
,
'grade'
,
'grade'
)
if
requirement
:
criteria
=
requirement
.
get
(
'criteria'
)
if
criteria
:
min_grade
=
criteria
.
get
(
'min_grade'
)
if
grade_summary
[
'percent'
]
>=
min_grade
:
reason_dict
=
{
'final_grade'
:
grade_summary
[
'percent'
]}
set_credit_requirement_status
(
username
,
course_id
,
'grade'
,
'grade'
,
status
=
"satisfied"
,
reason
=
reason_dict
)
elif
deadline
and
deadline
<
timezone
.
now
():
set_credit_requirement_status
(
username
,
course_id
,
'grade'
,
'grade'
,
status
=
"failed"
,
reason
=
{}
)
openedx/core/djangoapps/credit/tests/test_signals.py
0 → 100644
View file @
b8e04f30
"""
Tests for minimum grade requirement status
"""
import
pytz
import
ddt
from
datetime
import
timedelta
,
datetime
from
django.test.client
import
RequestFactory
from
openedx.core.djangoapps.credit.api
import
(
set_credit_requirements
,
get_credit_requirement_status
)
from
openedx.core.djangoapps.credit.models
import
CreditCourse
,
CreditProvider
from
openedx.core.djangoapps.credit.signals
import
listen_for_grade_calculation
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
@ddt.ddt
class
TestMinGradedRequirementStatus
(
ModuleStoreTestCase
):
"""Test cases to check the minimum grade requirement status updated.
If user grade is above or equal to min-grade then status will be
satisfied. But if student grade is less than and deadline is passed then
user will be marked as failed.
"""
VALID_DUE_DATE
=
datetime
.
now
(
pytz
.
UTC
)
+
timedelta
(
days
=
20
)
EXPIRED_DUE_DATE
=
datetime
.
now
(
pytz
.
UTC
)
-
timedelta
(
days
=
20
)
def
setUp
(
self
):
super
(
TestMinGradedRequirementStatus
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
(
org
=
'Robot'
,
number
=
'999'
,
display_name
=
'Test Course'
)
self
.
user
=
UserFactory
()
self
.
request
=
RequestFactory
()
.
get
(
'/'
)
self
.
request
.
user
=
self
.
user
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
user
.
password
)
# Enable the course for credit
credit_course
=
CreditCourse
.
objects
.
create
(
course_key
=
self
.
course
.
id
,
enabled
=
True
,
)
# Configure a credit provider for the course
credit_provider
=
CreditProvider
.
objects
.
create
(
provider_id
=
"ASU"
,
enable_integration
=
True
,
provider_url
=
"https://credit.example.com/request"
,
)
credit_course
.
providers
.
add
(
credit_provider
)
credit_course
.
save
()
requirements
=
[{
"namespace"
:
"grade"
,
"name"
:
"grade"
,
"display_name"
:
"Grade"
,
"criteria"
:
{
"min_grade"
:
0.52
}
}]
# Add a single credit requirement (final grade)
set_credit_requirements
(
self
.
course
.
id
,
requirements
)
@ddt.data
(
(
0.6
,
VALID_DUE_DATE
),
(
0.52
,
VALID_DUE_DATE
),
(
0.70
,
EXPIRED_DUE_DATE
),
)
@ddt.unpack
def
test_min_grade_requirement_with_valid_grade
(
self
,
grade_achieved
,
due_date
):
"""Test with valid grades. Deadline date does not effect in case
of valid grade.
"""
listen_for_grade_calculation
(
None
,
self
.
user
.
username
,
{
'percent'
:
grade_achieved
},
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"
],
'satisfied'
)
@ddt.data
(
(
0.50
,
None
),
(
0.51
,
None
),
(
0.40
,
VALID_DUE_DATE
),
)
@ddt.unpack
def
test_min_grade_requirement_failed_grade_valid_deadline
(
self
,
grade_achieved
,
due_date
):
"""Test with failed grades and deadline is still open or not defined."""
listen_for_grade_calculation
(
None
,
self
.
user
.
username
,
{
'percent'
:
grade_achieved
},
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"
],
None
)
def
test_min_grade_requirement_failed_grade_expired_deadline
(
self
):
"""Test with failed grades and deadline expire"""
listen_for_grade_calculation
(
None
,
self
.
user
.
username
,
{
'percent'
:
0.22
},
self
.
course
.
id
,
self
.
EXPIRED_DUE_DATE
)
req_status
=
get_credit_requirement_status
(
self
.
course
.
id
,
self
.
request
.
user
.
username
,
'grade'
,
'grade'
)
self
.
assertEqual
(
req_status
[
0
][
"status"
],
'failed'
)
openedx/core/djangoapps/signals/__init__.py
0 → 100644
View file @
b8e04f30
openedx/core/djangoapps/signals/signals.py
0 → 100644
View file @
b8e04f30
"""
This module contains all signals.
"""
from
django.dispatch
import
Signal
# Signal that fires when a user is graded (in lms/courseware/grades.py)
GRADES_UPDATED
=
Signal
(
providing_args
=
[
"username"
,
"grade_summary"
,
"course_key"
,
"deadline"
])
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