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
b9425c5b
Commit
b9425c5b
authored
Jun 23, 2017
by
Gregory Martin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement signal/handler for learner passing grades
parent
fd2efcba
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
144 additions
and
6 deletions
+144
-6
lms/djangoapps/certificates/signals.py
+39
-1
lms/djangoapps/certificates/tests/test_signals.py
+87
-3
lms/djangoapps/grades/new/course_grade_factory.py
+10
-2
openedx/core/djangoapps/signals/signals.py
+8
-0
No files found.
lms/djangoapps/certificates/signals.py
View file @
b9425c5b
...
...
@@ -9,10 +9,14 @@ from django.dispatch import receiver
from
opaque_keys.edx.keys
import
CourseKey
from
.config
import
waffle
from
certificates.models
import
CertificateGenerationCourseSetting
,
CertificateWhitelist
from
certificates.models
import
\
CertificateGenerationCourseSetting
,
\
CertificateWhitelist
,
\
GeneratedCertificate
from
certificates.tasks
import
generate_certificate
from
courseware
import
courses
from
openedx.core.djangoapps.models.course_details
import
COURSE_PACING_CHANGE
from
openedx.core.djangoapps.signals.signals
import
COURSE_GRADE_NOW_PASSED
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -64,3 +68,37 @@ def toggle_self_generated_certs(course_key, course_self_paced):
"""
course_key
=
CourseKey
.
from_string
(
course_key
)
CertificateGenerationCourseSetting
.
set_enabled_for_course
(
course_key
,
course_self_paced
)
@receiver
(
COURSE_GRADE_NOW_PASSED
,
dispatch_uid
=
"new_passing_learner"
)
def
_listen_for_passing_grade
(
sender
,
user
,
course_id
,
**
kwargs
):
# pylint: disable=unused-argument
"""
Listen for a learner passing a course, send cert generation task,
downstream signal from COURSE_GRADE_CHANGED
"""
# No flags enabled
if
(
not
waffle
.
waffle
()
.
is_enabled
(
waffle
.
SELF_PACED_ONLY
)
and
not
waffle
.
waffle
()
.
is_enabled
(
waffle
.
INSTRUCTOR_PACED_ONLY
)
):
return
# Only SELF_PACED_ONLY flag enabled
if
waffle
.
waffle
()
.
is_enabled
(
waffle
.
SELF_PACED_ONLY
):
if
not
courses
.
get_course_by_id
(
course_key
,
depth
=
0
)
.
self_paced
:
return
# Only INSTRUCTOR_PACED_ONLY flag enabled
elif
waffle
.
waffle
()
.
is_enabled
(
waffle
.
INSTRUCTOR_PACED_ONLY
):
if
courses
.
get_course_by_id
(
course_key
,
depth
=
0
)
.
self_paced
:
return
if
GeneratedCertificate
.
certificate_for_student
(
self
.
user
,
self
.
course_id
)
is
None
:
generate_certificate
.
apply_async
(
student
=
user
,
course_key
=
course_id
,
)
log
.
info
(
u'Certificate generation task initiated for {user} : {course} via passing grade'
.
format
(
user
=
user
.
id
,
course
=
course_id
))
lms/djangoapps/certificates/tests/test_signals.py
View file @
b9425c5b
...
...
@@ -8,8 +8,10 @@ from certificates import api as certs_api
from
certificates.config
import
waffle
from
certificates.models
import
CertificateGenerationConfiguration
,
CertificateWhitelist
from
certificates.signals
import
_listen_for_course_pacing_changed
from
lms.djangoapps.grades.new.course_grade_factory
import
CourseGradeFactory
from
lms.djangoapps.grades.tests.utils
import
mock_get_score
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
...
...
@@ -56,9 +58,10 @@ class WhitelistGeneratedCertificatesTest(ModuleStoreTestCase):
self
.
user
=
UserFactory
.
create
()
self
.
ip_course
=
CourseFactory
.
create
(
self_paced
=
False
)
def
test_cert_generation_on_whitelist_append
(
self
):
def
test_cert_generation_on_whitelist_append
_self_paced
(
self
):
"""
Verify that signal is sent, received, and fires task based on various flag configs
Verify that signal is sent, received, and fires task
based on 'SELF_PACED_ONLY' flag
"""
with
mock
.
patch
(
'lms.djangoapps.certificates.signals.generate_certificate.apply_async'
,
...
...
@@ -82,6 +85,16 @@ class WhitelistGeneratedCertificatesTest(ModuleStoreTestCase):
student
=
self
.
user
,
course_key
=
self
.
course
.
id
,
)
def
test_cert_generation_on_whitelist_append_instructor_paced
(
self
):
"""
Verify that signal is sent, received, and fires task
based on 'INSTRUCTOR_PACED_ONLY' flag
"""
with
mock
.
patch
(
'lms.djangoapps.certificates.signals.generate_certificate.apply_async'
,
return_value
=
None
)
as
mock_generate_certificate_apply_async
:
with
waffle
.
waffle
()
.
override
(
waffle
.
INSTRUCTOR_PACED_ONLY
,
active
=
False
):
CertificateWhitelist
.
objects
.
create
(
user
=
self
.
user
,
...
...
@@ -100,3 +113,74 @@ class WhitelistGeneratedCertificatesTest(ModuleStoreTestCase):
student
=
self
.
user
,
course_key
=
self
.
ip_course
.
id
)
class
PassingGradeCertsTest
(
ModuleStoreTestCase
):
"""
Tests for certificate generation task firing on passing grade receipt
"""
def
setUp
(
self
):
super
(
PassingGradeCertsTest
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
(
self_paced
=
True
)
self
.
user
=
UserFactory
.
create
()
self
.
enrollment
=
CourseEnrollmentFactory
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
,
is_active
=
True
,
mode
=
"verified"
,
)
self
.
ip_course
=
CourseFactory
.
create
(
self_paced
=
False
)
def
test_cert_generation_on_passing_self_paced
(
self
):
with
mock
.
patch
(
'lms.djangoapps.certificates.signals.generate_certificate.apply_async'
,
return_value
=
None
)
as
mock_generate_certificate_apply_async
:
with
waffle
.
waffle
()
.
override
(
waffle
.
SELF_PACED_ONLY
,
active
=
True
):
grade_factory
=
CourseGradeFactory
()
with
mock_get_score
(
0
,
2
):
grade_factory
.
update
(
self
.
user
,
self
.
course
)
mock_generate_certificate_apply_async
.
assert_not_called
(
student
=
self
.
user
,
course_key
=
self
.
course
.
id
)
with
mock_get_score
(
1
,
2
):
grade_factory
.
update
(
self
.
user
,
self
.
course
)
mock_generate_certificate_apply_async
.
assert_called
(
student
=
self
.
user
,
course_key
=
self
.
course
.
id
)
# Certs are not re-fired after passing
with
mock_get_score
(
2
,
2
):
grade_factory
.
update
(
self
.
user
,
self
.
course
)
mock_generate_certificate_apply_async
.
assert_not_called
(
student
=
self
.
user
,
course_key
=
self
.
course
.
id
)
def
test_cert_generation_on_passing_instructor_paced
(
self
):
with
mock
.
patch
(
'lms.djangoapps.certificates.signals.generate_certificate.apply_async'
,
return_value
=
None
)
as
mock_generate_certificate_apply_async
:
with
waffle
.
waffle
()
.
override
(
waffle
.
INSTRUCTOR_PACED_ONLY
,
active
=
True
):
grade_factory
=
CourseGradeFactory
()
with
mock_get_score
(
0
,
2
):
grade_factory
.
update
(
self
.
user
,
self
.
ip_course
)
mock_generate_certificate_apply_async
.
assert_not_called
(
student
=
self
.
user
,
course_key
=
self
.
ip_course
.
id
)
with
mock_get_score
(
1
,
2
):
grade_factory
.
update
(
self
.
user
,
self
.
ip_course
)
mock_generate_certificate_apply_async
.
assert_called
(
student
=
self
.
user
,
course_key
=
self
.
ip_course
.
id
)
# Certs are not re-fired after passing
with
mock_get_score
(
2
,
2
):
grade_factory
.
update
(
self
.
user
,
self
.
ip_course
)
mock_generate_certificate_apply_async
.
assert_not_called
(
student
=
self
.
user
,
course_key
=
self
.
ip_course
.
id
)
lms/djangoapps/grades/new/course_grade_factory.py
View file @
b9425c5b
...
...
@@ -3,7 +3,8 @@ from contextlib import contextmanager
from
logging
import
getLogger
import
dogstats_wrapper
as
dog_stats_api
from
openedx.core.djangoapps.signals.signals
import
COURSE_GRADE_CHANGED
from
openedx.core.djangoapps.signals.signals
import
COURSE_GRADE_CHANGED
,
COURSE_GRADE_NOW_PASSED
from
..config
import
assume_zero_if_absent
,
should_persist_grades
from
..config.waffle
import
WRITE_ONLY_IF_ENGAGED
,
waffle
...
...
@@ -167,7 +168,8 @@ class CourseGradeFactory(object):
"""
Computes, saves, and returns a CourseGrade object for the
given user and course.
Sends a COURSE_GRADE_CHANGED signal to listeners.
Sends a COURSE_GRADE_CHANGED signal to listeners and a
COURSE_GRADE_NOW_PASSED if learner has passed course.
"""
course_grade
=
CourseGrade
(
user
,
course_data
)
course_grade
.
update
()
...
...
@@ -197,6 +199,12 @@ class CourseGradeFactory(object):
course_key
=
course_data
.
course_key
,
deadline
=
course_data
.
course
.
end
,
)
if
course_grade
.
passed
is
True
:
COURSE_GRADE_NOW_PASSED
.
send_robust
(
sender
=
CourseGradeFactory
,
user
=
user
,
course_key
=
course_data
.
course_key
,
)
log
.
info
(
u'Grades: Update,
%
s, User:
%
s,
%
s, persisted:
%
s'
,
...
...
openedx/core/djangoapps/signals/signals.py
View file @
b9425c5b
...
...
@@ -11,3 +11,11 @@ COURSE_GRADE_CHANGED = Signal(providing_args=["user", "course_grade", "course_ke
# TODO: runtime coupling between apps will be reduced if this event is changed to carry a username
# rather than a User object; however, this will require changes to the milestones and badges APIs
COURSE_CERT_AWARDED
=
Signal
(
providing_args
=
[
"user"
,
"course_key"
,
"mode"
,
"status"
])
# Signal that indicates that a user has passed a course.
COURSE_GRADE_NOW_PASSED
=
Signal
(
providing_args
=
[
'user'
,
# user object
'course_key'
,
# course.id
]
)
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