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
fc8b2a5f
Commit
fc8b2a5f
authored
Nov 12, 2014
by
Stephen Sanchez
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5720 from edx/sanchez/avoid_event_suppression
Changing event suppression to signal suppression.
parents
d981f98b
6e85ec07
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
118 additions
and
17 deletions
+118
-17
common/djangoapps/student/management/commands/transfer_students.py
+2
-2
common/djangoapps/student/management/tests/test_transfer_students.py
+89
-6
common/djangoapps/student/models.py
+7
-7
lms/djangoapps/shoppingcart/models.py
+2
-2
lms/djangoapps/shoppingcart/tests/test_models.py
+18
-0
No files found.
common/djangoapps/student/management/commands/transfer_students.py
View file @
fc8b2a5f
...
@@ -83,7 +83,7 @@ class Command(TrackedCommand):
...
@@ -83,7 +83,7 @@ class Command(TrackedCommand):
# Move the Student between the classes.
# Move the Student between the classes.
mode
=
enrollment
.
mode
mode
=
enrollment
.
mode
old_is_active
=
enrollment
.
is_active
old_is_active
=
enrollment
.
is_active
CourseEnrollment
.
unenroll
(
user
,
source_key
,
emit_unenrollment_event
=
Fals
e
)
CourseEnrollment
.
unenroll
(
user
,
source_key
,
skip_refund
=
Tru
e
)
print
(
u"Unenrolled {} from {}"
.
format
(
user
.
username
,
unicode
(
source_key
)))
print
(
u"Unenrolled {} from {}"
.
format
(
user
.
username
,
unicode
(
source_key
)))
for
dest_key
in
dest_keys
:
for
dest_key
in
dest_keys
:
...
@@ -98,7 +98,7 @@ class Command(TrackedCommand):
...
@@ -98,7 +98,7 @@ class Command(TrackedCommand):
# Un-enroll from the new course if the user had un-enrolled
# Un-enroll from the new course if the user had un-enrolled
# form the old course.
# form the old course.
if
not
old_is_active
:
if
not
old_is_active
:
new_enrollment
.
update_enrollment
(
is_active
=
False
,
emit_unenrollment_event
=
Fals
e
)
new_enrollment
.
update_enrollment
(
is_active
=
False
,
skip_refund
=
Tru
e
)
if
transfer_certificates
:
if
transfer_certificates
:
self
.
_transfer_certificate_item
(
source_key
,
enrollment
,
user
,
dest_keys
,
new_enrollment
)
self
.
_transfer_certificate_item
(
source_key
,
enrollment
,
user
,
dest_keys
,
new_enrollment
)
...
...
common/djangoapps/student/management/tests/test_transfer_students.py
View file @
fc8b2a5f
...
@@ -2,11 +2,16 @@
...
@@ -2,11 +2,16 @@
Tests the transfer student management command
Tests the transfer student management command
"""
"""
from
django.conf
import
settings
from
django.conf
import
settings
from
mock
import
patch
,
call
from
opaque_keys.edx
import
locator
from
opaque_keys.edx
import
locator
import
unittest
import
unittest
import
ddt
import
ddt
from
shoppingcart.models
import
Order
,
CertificateItem
# pylint: disable=F0401
from
course_modes.models
import
CourseMode
from
student.management.commands
import
transfer_students
from
student.management.commands
import
transfer_students
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
,
UNENROLL_DONE
,
EVENT_NAME_ENROLLMENT_DEACTIVATED
,
\
EVENT_NAME_ENROLLMENT_ACTIVATED
,
EVENT_NAME_ENROLLMENT_MODE_CHANGED
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
...
@@ -18,18 +23,40 @@ class TestTransferStudents(ModuleStoreTestCase):
...
@@ -18,18 +23,40 @@ class TestTransferStudents(ModuleStoreTestCase):
"""Tests for transferring students between courses."""
"""Tests for transferring students between courses."""
PASSWORD
=
'test'
PASSWORD
=
'test'
signal_fired
=
False
def
setUp
(
self
,
**
kwargs
):
"""Connect a stub receiver, and analytics event tracking."""
UNENROLL_DONE
.
connect
(
self
.
assert_unenroll_signal
)
patcher
=
patch
(
'student.models.tracker'
)
self
.
mock_tracker
=
patcher
.
start
()
self
.
addCleanup
(
patcher
.
stop
)
def
tearDown
(
self
):
"""Disconnects the UNENROLL stub receiver."""
UNENROLL_DONE
.
disconnect
(
self
.
assert_unenroll_signal
)
def
assert_unenroll_signal
(
self
,
skip_refund
=
False
,
**
kwargs
):
# pylint: disable=W0613
""" Signal Receiver stub for testing that the unenroll signal was fired. """
self
.
assertFalse
(
self
.
signal_fired
)
self
.
assertTrue
(
skip_refund
)
self
.
signal_fired
=
True
def
test_transfer_students
(
self
):
def
test_transfer_students
(
self
):
student
=
UserFactory
()
""" Verify the transfer student command works as intended. """
student
=
UserFactory
.
create
()
student
.
set_password
(
self
.
PASSWORD
)
# pylint: disable=E1101
student
.
set_password
(
self
.
PASSWORD
)
# pylint: disable=E1101
student
.
save
()
# pylint: disable=E1101
student
.
save
()
# pylint: disable=E1101
mode
=
'verified'
# Original Course
# Original Course
original_course_location
=
locator
.
CourseLocator
(
'Org0'
,
'Course0'
,
'Run0'
)
original_course_location
=
locator
.
CourseLocator
(
'Org0'
,
'Course0'
,
'Run0'
)
course
=
self
.
_create_course
(
original_course_location
)
course
=
self
.
_create_course
(
original_course_location
)
# Enroll the student in 'verified'
# Enroll the student in 'verified'
CourseEnrollment
.
enroll
(
student
,
course
.
id
,
mode
=
"verified"
)
CourseEnrollment
.
enroll
(
student
,
course
.
id
,
mode
=
"verified"
)
# Create and purchase a verified cert for the original course.
self
.
_create_and_purchase_verified
(
student
,
course
.
id
)
# New Course 1
# New Course 1
course_location_one
=
locator
.
CourseLocator
(
'Org1'
,
'Course1'
,
'Run1'
)
course_location_one
=
locator
.
CourseLocator
(
'Org1'
,
'Course1'
,
'Run1'
)
new_course_one
=
self
.
_create_course
(
course_location_one
)
new_course_one
=
self
.
_create_course
(
course_location_one
)
...
@@ -45,11 +72,55 @@ class TestTransferStudents(ModuleStoreTestCase):
...
@@ -45,11 +72,55 @@ class TestTransferStudents(ModuleStoreTestCase):
transfer_students
.
Command
()
.
handle
(
transfer_students
.
Command
()
.
handle
(
source_course
=
original_key
,
dest_course_list
=
new_key_one
+
","
+
new_key_two
source_course
=
original_key
,
dest_course_list
=
new_key_one
+
","
+
new_key_two
)
)
self
.
assertTrue
(
self
.
signal_fired
)
# Confirm the analytics event was emitted.
self
.
mock_tracker
.
emit
.
assert_has_calls
(
# pylint: disable=E1103
[
call
(
EVENT_NAME_ENROLLMENT_ACTIVATED
,
{
'course_id'
:
original_key
,
'user_id'
:
student
.
id
,
'mode'
:
mode
}
),
call
(
EVENT_NAME_ENROLLMENT_MODE_CHANGED
,
{
'course_id'
:
original_key
,
'user_id'
:
student
.
id
,
'mode'
:
mode
}
),
call
(
EVENT_NAME_ENROLLMENT_DEACTIVATED
,
{
'course_id'
:
original_key
,
'user_id'
:
student
.
id
,
'mode'
:
mode
}
),
call
(
EVENT_NAME_ENROLLMENT_ACTIVATED
,
{
'course_id'
:
new_key_one
,
'user_id'
:
student
.
id
,
'mode'
:
mode
}
),
call
(
EVENT_NAME_ENROLLMENT_MODE_CHANGED
,
{
'course_id'
:
new_key_one
,
'user_id'
:
student
.
id
,
'mode'
:
mode
}
),
call
(
EVENT_NAME_ENROLLMENT_ACTIVATED
,
{
'course_id'
:
new_key_two
,
'user_id'
:
student
.
id
,
'mode'
:
mode
}
),
call
(
EVENT_NAME_ENROLLMENT_MODE_CHANGED
,
{
'course_id'
:
new_key_two
,
'user_id'
:
student
.
id
,
'mode'
:
mode
}
)
]
)
self
.
mock_tracker
.
reset_mock
()
# Confirm the enrollment mode is verified on the new courses, and enrollment is enabled as appropriate.
# Confirm the enrollment mode is verified on the new courses, and enrollment is enabled as appropriate.
self
.
assertEquals
((
'verified'
,
False
),
CourseEnrollment
.
enrollment_mode_for_user
(
student
,
course
.
id
))
self
.
assertEquals
((
mode
,
False
),
CourseEnrollment
.
enrollment_mode_for_user
(
student
,
course
.
id
))
self
.
assertEquals
((
'verified'
,
True
),
CourseEnrollment
.
enrollment_mode_for_user
(
student
,
new_course_one
.
id
))
self
.
assertEquals
((
mode
,
True
),
CourseEnrollment
.
enrollment_mode_for_user
(
student
,
new_course_one
.
id
))
self
.
assertEquals
((
'verified'
,
True
),
CourseEnrollment
.
enrollment_mode_for_user
(
student
,
new_course_two
.
id
))
self
.
assertEquals
((
mode
,
True
),
CourseEnrollment
.
enrollment_mode_for_user
(
student
,
new_course_two
.
id
))
# Confirm the student has not be refunded.
target_certs
=
CertificateItem
.
objects
.
filter
(
course_id
=
course
.
id
,
user_id
=
student
,
status
=
'purchased'
,
mode
=
mode
)
self
.
assertTrue
(
target_certs
[
0
])
self
.
assertFalse
(
target_certs
[
0
]
.
refund_requested_time
)
self
.
assertEquals
(
target_certs
[
0
]
.
order
.
status
,
'purchased'
)
def
_create_course
(
self
,
course_location
):
def
_create_course
(
self
,
course_location
):
""" Creates a course """
""" Creates a course """
...
@@ -58,3 +129,15 @@ class TestTransferStudents(ModuleStoreTestCase):
...
@@ -58,3 +129,15 @@ class TestTransferStudents(ModuleStoreTestCase):
number
=
course_location
.
course
,
number
=
course_location
.
course
,
run
=
course_location
.
run
run
=
course_location
.
run
)
)
def
_create_and_purchase_verified
(
self
,
student
,
course_id
):
""" Creates a verified mode for the course and purchases it for the student. """
course_mode
=
CourseMode
(
course_id
=
course_id
,
mode_slug
=
"verified"
,
mode_display_name
=
"verified cert"
,
min_price
=
50
)
course_mode
.
save
()
# When there is no expiration date on a verified mode, the user can always get a refund
cart
=
Order
.
get_cart_for_user
(
user
=
student
)
CertificateItem
.
add_to_order
(
cart
,
course_id
,
50
,
'verified'
)
cart
.
purchase
()
common/djangoapps/student/models.py
View file @
fc8b2a5f
...
@@ -56,7 +56,7 @@ from ratelimitbackend import admin
...
@@ -56,7 +56,7 @@ from ratelimitbackend import admin
import
analytics
import
analytics
UNENROLL_DONE
=
Signal
(
providing_args
=
[
"course_enrollment"
])
UNENROLL_DONE
=
Signal
(
providing_args
=
[
"course_enrollment"
,
"skip_refund"
])
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
AUDIT_LOG
=
logging
.
getLogger
(
"audit"
)
AUDIT_LOG
=
logging
.
getLogger
(
"audit"
)
SessionStore
=
import_module
(
settings
.
SESSION_ENGINE
)
.
SessionStore
# pylint: disable=invalid-name
SessionStore
=
import_module
(
settings
.
SESSION_ENGINE
)
.
SessionStore
# pylint: disable=invalid-name
...
@@ -780,7 +780,7 @@ class CourseEnrollment(models.Model):
...
@@ -780,7 +780,7 @@ class CourseEnrollment(models.Model):
is_course_full
=
cls
.
num_enrolled_in
(
course
.
id
)
>=
course
.
max_student_enrollments_allowed
is_course_full
=
cls
.
num_enrolled_in
(
course
.
id
)
>=
course
.
max_student_enrollments_allowed
return
is_course_full
return
is_course_full
def
update_enrollment
(
self
,
mode
=
None
,
is_active
=
None
,
emit_unenrollment_event
=
Tru
e
):
def
update_enrollment
(
self
,
mode
=
None
,
is_active
=
None
,
skip_refund
=
Fals
e
):
"""
"""
Updates an enrollment for a user in a class. This includes options
Updates an enrollment for a user in a class. This includes options
like changing the mode, toggling is_active True/False, etc.
like changing the mode, toggling is_active True/False, etc.
...
@@ -818,8 +818,8 @@ class CourseEnrollment(models.Model):
...
@@ -818,8 +818,8 @@ class CourseEnrollment(models.Model):
u"mode:{}"
.
format
(
self
.
mode
)]
u"mode:{}"
.
format
(
self
.
mode
)]
)
)
el
if
emit_unenrollment_event
:
el
se
:
UNENROLL_DONE
.
send
(
sender
=
None
,
course_enrollment
=
self
)
UNENROLL_DONE
.
send
(
sender
=
None
,
course_enrollment
=
self
,
skip_refund
=
skip_refund
)
self
.
emit_event
(
EVENT_NAME_ENROLLMENT_DEACTIVATED
)
self
.
emit_event
(
EVENT_NAME_ENROLLMENT_DEACTIVATED
)
...
@@ -992,7 +992,7 @@ class CourseEnrollment(models.Model):
...
@@ -992,7 +992,7 @@ class CourseEnrollment(models.Model):
raise
raise
@classmethod
@classmethod
def
unenroll
(
cls
,
user
,
course_id
,
emit_unenrollment_event
=
Tru
e
):
def
unenroll
(
cls
,
user
,
course_id
,
skip_refund
=
Fals
e
):
"""
"""
Remove the user from a given course. If the relevant `CourseEnrollment`
Remove the user from a given course. If the relevant `CourseEnrollment`
object doesn't exist, we log an error but don't throw an exception.
object doesn't exist, we log an error but don't throw an exception.
...
@@ -1003,11 +1003,11 @@ class CourseEnrollment(models.Model):
...
@@ -1003,11 +1003,11 @@ class CourseEnrollment(models.Model):
`course_id` is our usual course_id string (e.g. "edX/Test101/2013_Fall)
`course_id` is our usual course_id string (e.g. "edX/Test101/2013_Fall)
`
emit_unenrollment_events` can be set to False to suppress events firing
.
`
skip_refund` can be set to True to avoid the refund process
.
"""
"""
try
:
try
:
record
=
CourseEnrollment
.
objects
.
get
(
user
=
user
,
course_id
=
course_id
)
record
=
CourseEnrollment
.
objects
.
get
(
user
=
user
,
course_id
=
course_id
)
record
.
update_enrollment
(
is_active
=
False
,
emit_unenrollment_event
=
emit_unenrollment_event
)
record
.
update_enrollment
(
is_active
=
False
,
skip_refund
=
skip_refund
)
except
cls
.
DoesNotExist
:
except
cls
.
DoesNotExist
:
err_msg
=
u"Tried to unenroll student {} from {} but they were not enrolled"
err_msg
=
u"Tried to unenroll student {} from {} but they were not enrolled"
...
...
lms/djangoapps/shoppingcart/models.py
View file @
fc8b2a5f
...
@@ -1038,7 +1038,7 @@ class CertificateItem(OrderItem):
...
@@ -1038,7 +1038,7 @@ class CertificateItem(OrderItem):
mode
=
models
.
SlugField
()
mode
=
models
.
SlugField
()
@receiver
(
UNENROLL_DONE
)
@receiver
(
UNENROLL_DONE
)
def
refund_cert_callback
(
sender
,
course_enrollment
=
None
,
**
kwargs
):
def
refund_cert_callback
(
sender
,
course_enrollment
=
None
,
skip_refund
=
False
,
**
kwargs
):
# pylint: disable=E0213,W0613
"""
"""
When a CourseEnrollment object calls its unenroll method, this function checks to see if that unenrollment
When a CourseEnrollment object calls its unenroll method, this function checks to see if that unenrollment
occurred in a verified certificate that was within the refund deadline. If so, it actually performs the
occurred in a verified certificate that was within the refund deadline. If so, it actually performs the
...
@@ -1048,7 +1048,7 @@ class CertificateItem(OrderItem):
...
@@ -1048,7 +1048,7 @@ class CertificateItem(OrderItem):
"""
"""
# Only refund verified cert unenrollments that are within bounds of the expiration date
# Only refund verified cert unenrollments that are within bounds of the expiration date
if
not
course_enrollment
.
refundable
()
:
if
(
not
course_enrollment
.
refundable
())
or
skip_refund
:
return
return
target_certs
=
CertificateItem
.
objects
.
filter
(
course_id
=
course_enrollment
.
course_id
,
user_id
=
course_enrollment
.
user
,
status
=
'purchased'
,
mode
=
'verified'
)
target_certs
=
CertificateItem
.
objects
.
filter
(
course_id
=
course_enrollment
.
course_id
,
user_id
=
course_enrollment
.
user
,
status
=
'purchased'
,
mode
=
'verified'
)
...
...
lms/djangoapps/shoppingcart/tests/test_models.py
View file @
fc8b2a5f
...
@@ -496,6 +496,24 @@ class CertificateItemTest(ModuleStoreTestCase):
...
@@ -496,6 +496,24 @@ class CertificateItemTest(ModuleStoreTestCase):
self
.
assertTrue
(
target_certs
[
0
]
.
refund_requested_time
)
self
.
assertTrue
(
target_certs
[
0
]
.
refund_requested_time
)
self
.
assertEquals
(
target_certs
[
0
]
.
order
.
status
,
'refunded'
)
self
.
assertEquals
(
target_certs
[
0
]
.
order
.
status
,
'refunded'
)
def
test_no_refund_on_cert_callback
(
self
):
# If we explicitly skip refunds, the unenroll action should not modify the purchase.
CourseEnrollment
.
enroll
(
self
.
user
,
self
.
course_key
,
'verified'
)
cart
=
Order
.
get_cart_for_user
(
user
=
self
.
user
)
CertificateItem
.
add_to_order
(
cart
,
self
.
course_key
,
self
.
cost
,
'verified'
)
cart
.
purchase
()
CourseEnrollment
.
unenroll
(
self
.
user
,
self
.
course_key
,
skip_refund
=
True
)
target_certs
=
CertificateItem
.
objects
.
filter
(
course_id
=
self
.
course_key
,
user_id
=
self
.
user
,
status
=
'purchased'
,
mode
=
'verified'
)
self
.
assertTrue
(
target_certs
[
0
])
self
.
assertFalse
(
target_certs
[
0
]
.
refund_requested_time
)
self
.
assertEquals
(
target_certs
[
0
]
.
order
.
status
,
'purchased'
)
def
test_refund_cert_callback_before_expiration
(
self
):
def
test_refund_cert_callback_before_expiration
(
self
):
# If the expiration date has not yet passed on a verified mode, the user can be refunded
# If the expiration date has not yet passed on a verified mode, the user can be refunded
many_days
=
datetime
.
timedelta
(
days
=
60
)
many_days
=
datetime
.
timedelta
(
days
=
60
)
...
...
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