Pwattenberger/sailthru enroll (#12816)
* Partial changes for purchase tracking * Continued changes for purchase tracking * Clean up code quality issues * Clean up code quality issues * Responses to code review * Fix code quality flaged issues * Fix code quality flaged issues * Fix code quality flaged issues * Fix problem processing sailthru_content cookie
This commit is contained in:
@@ -61,10 +61,32 @@ from util.milestones_helpers import is_entrance_exams_enabled
|
||||
|
||||
|
||||
UNENROLL_DONE = Signal(providing_args=["course_enrollment", "skip_refund"])
|
||||
ENROLL_STATUS_CHANGE = Signal(providing_args=["event", "user", "course_id", "mode", "cost", "currency"])
|
||||
log = logging.getLogger(__name__)
|
||||
AUDIT_LOG = logging.getLogger("audit")
|
||||
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore # pylint: disable=invalid-name
|
||||
|
||||
# enroll status changed events - signaled to email_marketing. See email_marketing.tasks for more info
|
||||
|
||||
|
||||
# ENROLL signal used for free enrollment only
|
||||
class EnrollStatusChange(object):
|
||||
"""
|
||||
Possible event types for ENROLL_STATUS_CHANGE signal
|
||||
"""
|
||||
# enroll for a course
|
||||
enroll = 'enroll'
|
||||
# unenroll for a course
|
||||
unenroll = 'unenroll'
|
||||
# add an upgrade to cart
|
||||
upgrade_start = 'upgrade_start'
|
||||
# complete an upgrade purchase
|
||||
upgrade_complete = 'upgrade_complete'
|
||||
# add a paid course to the cart
|
||||
paid_start = 'paid_start'
|
||||
# complete a paid course purchase
|
||||
paid_complete = 'paid_complete'
|
||||
|
||||
UNENROLLED_TO_ALLOWEDTOENROLL = 'from unenrolled to allowed to enroll'
|
||||
ALLOWEDTOENROLL_TO_ENROLLED = 'from allowed to enroll to enrolled'
|
||||
ENROLLED_TO_ENROLLED = 'from enrolled to enrolled'
|
||||
@@ -1113,6 +1135,7 @@ class CourseEnrollment(models.Model):
|
||||
UNENROLL_DONE.send(sender=None, course_enrollment=self, skip_refund=skip_refund)
|
||||
|
||||
self.emit_event(EVENT_NAME_ENROLLMENT_DEACTIVATED)
|
||||
self.send_signal(EnrollStatusChange.unenroll)
|
||||
|
||||
dog_stats_api.increment(
|
||||
"common.student.unenrollment",
|
||||
@@ -1125,6 +1148,24 @@ class CourseEnrollment(models.Model):
|
||||
# mode has changed from its previous setting
|
||||
self.emit_event(EVENT_NAME_ENROLLMENT_MODE_CHANGED)
|
||||
|
||||
def send_signal(self, event, cost=None, currency=None):
|
||||
"""
|
||||
Sends a signal announcing changes in course enrollment status.
|
||||
"""
|
||||
ENROLL_STATUS_CHANGE.send(sender=None, event=event, user=self.user,
|
||||
mode=self.mode, course_id=self.course_id,
|
||||
cost=cost, currency=currency)
|
||||
|
||||
@classmethod
|
||||
def send_signal_full(cls, event, user=user, mode=mode, course_id=course_id, cost=None, currency=None):
|
||||
"""
|
||||
Sends a signal announcing changes in course enrollment status.
|
||||
This version should be used if you don't already have a CourseEnrollment object
|
||||
"""
|
||||
ENROLL_STATUS_CHANGE.send(sender=None, event=event, user=user,
|
||||
mode=mode, course_id=course_id,
|
||||
cost=cost, currency=currency)
|
||||
|
||||
def emit_event(self, event_name):
|
||||
"""
|
||||
Emits an event to explicitly track course enrollment and unenrollment.
|
||||
|
||||
@@ -112,7 +112,7 @@ from student.helpers import (
|
||||
DISABLE_UNENROLL_CERT_STATES,
|
||||
)
|
||||
from student.cookies import set_logged_in_cookies, delete_logged_in_cookies
|
||||
from student.models import anonymous_id_for_user, UserAttribute
|
||||
from student.models import anonymous_id_for_user, UserAttribute, EnrollStatusChange
|
||||
from shoppingcart.models import DonationConfiguration, CourseRegistrationCode
|
||||
|
||||
from embargo import api as embargo_api
|
||||
@@ -1065,7 +1065,8 @@ def change_enrollment(request, check_access=True):
|
||||
try:
|
||||
enroll_mode = CourseMode.auto_enroll_mode(course_id, available_modes)
|
||||
if enroll_mode:
|
||||
CourseEnrollment.enroll(user, course_id, check_access=check_access, mode=enroll_mode)
|
||||
enrollment = CourseEnrollment.enroll(user, course_id, check_access=check_access, mode=enroll_mode)
|
||||
enrollment.send_signal(EnrollStatusChange.enroll)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return HttpResponseBadRequest(_("Could not enroll"))
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('email_marketing', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_abandoned_cart_delay',
|
||||
field=models.IntegerField(default=60, help_text='Sailthru minutes to wait before sending abandoned cart message.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_abandoned_cart_template',
|
||||
field=models.CharField(help_text='Sailthru template to use on abandoned cart reminder. ', max_length=20, blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_content_cache_age',
|
||||
field=models.IntegerField(default=3600, help_text='Number of seconds to cache course content retrieved from Sailthru.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_enroll_cost',
|
||||
field=models.IntegerField(default=100, help_text='Cost in cents to report to Sailthru for enrolls.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_enroll_template',
|
||||
field=models.CharField(help_text='Sailthru send template to use on enrolling for audit. ', max_length=20, blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_get_tags_from_sailthru',
|
||||
field=models.BooleanField(default=True, help_text='Use the Sailthru content API to fetch course tags.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_purchase_template',
|
||||
field=models.CharField(help_text='Sailthru send template to use on purchasing a course seat. ', max_length=20, blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_upgrade_template',
|
||||
field=models.CharField(help_text='Sailthru send template to use on upgrading a course. ', max_length=20, blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='emailmarketingconfiguration',
|
||||
name='sailthru_activation_template',
|
||||
field=models.CharField(help_text='Sailthru template to use on activation send. ', max_length=20, blank=True),
|
||||
),
|
||||
]
|
||||
@@ -50,11 +50,75 @@ class EmailMarketingConfiguration(ConfigurationModel):
|
||||
|
||||
sailthru_activation_template = models.fields.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Sailthru template to use on activation send. "
|
||||
)
|
||||
)
|
||||
|
||||
sailthru_abandoned_cart_template = models.fields.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Sailthru template to use on abandoned cart reminder. "
|
||||
)
|
||||
)
|
||||
|
||||
sailthru_abandoned_cart_delay = models.fields.IntegerField(
|
||||
default=60,
|
||||
help_text=_(
|
||||
"Sailthru minutes to wait before sending abandoned cart message."
|
||||
)
|
||||
)
|
||||
|
||||
sailthru_enroll_template = models.fields.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Sailthru send template to use on enrolling for audit. "
|
||||
)
|
||||
)
|
||||
|
||||
sailthru_upgrade_template = models.fields.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Sailthru send template to use on upgrading a course. "
|
||||
)
|
||||
)
|
||||
|
||||
sailthru_purchase_template = models.fields.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
help_text=_(
|
||||
"Sailthru send template to use on purchasing a course seat. "
|
||||
)
|
||||
)
|
||||
|
||||
# Sailthru purchases can be tagged with interest tags to provide information about the types of courses
|
||||
# users are interested in. The easiest way to get the tags currently is the Sailthru content API which
|
||||
# looks in the content library (the content library is populated daily with a script that pulls the data
|
||||
# from the course discovery API) This option should normally be on, but it does add overhead to processing
|
||||
# purchases and enrolls.
|
||||
sailthru_get_tags_from_sailthru = models.BooleanField(
|
||||
default=True,
|
||||
help_text=_('Use the Sailthru content API to fetch course tags.')
|
||||
)
|
||||
|
||||
sailthru_content_cache_age = models.fields.IntegerField(
|
||||
default=3600,
|
||||
help_text=_(
|
||||
"Number of seconds to cache course content retrieved from Sailthru."
|
||||
)
|
||||
)
|
||||
|
||||
sailthru_enroll_cost = models.fields.IntegerField(
|
||||
default=100,
|
||||
help_text=_(
|
||||
"Cost in cents to report to Sailthru for enrolls."
|
||||
)
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return u"Email marketing configuration: New user list %s, Activation template: %s" % \
|
||||
(self.sailthru_new_user_list, self.sailthru_activation_template)
|
||||
|
||||
@@ -3,15 +3,16 @@ This module contains signals needed for email integration
|
||||
"""
|
||||
import logging
|
||||
import datetime
|
||||
import crum
|
||||
|
||||
from django.dispatch import receiver
|
||||
|
||||
from student.models import UNENROLL_DONE
|
||||
from student.models import ENROLL_STATUS_CHANGE
|
||||
from student.cookies import CREATE_LOGON_COOKIE
|
||||
from student.views import REGISTER_USER
|
||||
from email_marketing.models import EmailMarketingConfiguration
|
||||
from util.model_utils import USER_FIELD_CHANGED
|
||||
from lms.djangoapps.email_marketing.tasks import update_user, update_user_email
|
||||
from lms.djangoapps.email_marketing.tasks import update_user, update_user_email, update_course_enrollment
|
||||
|
||||
from sailthru.sailthru_client import SailthruClient
|
||||
from sailthru.sailthru_error import SailthruClientError
|
||||
@@ -24,17 +25,45 @@ CHANGED_FIELDNAMES = ['username', 'is_active', 'name', 'gender', 'education',
|
||||
'country']
|
||||
|
||||
|
||||
@receiver(UNENROLL_DONE)
|
||||
def handle_unenroll_done(sender, course_enrollment=None, skip_refund=False,
|
||||
**kwargs): # pylint: disable=unused-argument
|
||||
@receiver(ENROLL_STATUS_CHANGE)
|
||||
def handle_enroll_status_change(sender, event=None, user=None, mode=None, course_id=None, cost=None, currency=None,
|
||||
**kwargs): # pylint: disable=unused-argument
|
||||
"""
|
||||
Signal receiver for unenrollments
|
||||
Signal receiver for enroll/unenroll/purchase events
|
||||
"""
|
||||
email_config = EmailMarketingConfiguration.current()
|
||||
if not email_config.enabled:
|
||||
if not email_config.enabled or not event or not user or not mode or not course_id:
|
||||
return
|
||||
|
||||
# TBD
|
||||
request = crum.get_current_request()
|
||||
if not request:
|
||||
return
|
||||
|
||||
# figure out course url
|
||||
course_url = _build_course_url(request, course_id.to_deprecated_string())
|
||||
|
||||
# pass event to email_marketing.tasks
|
||||
update_course_enrollment.delay(user.email, course_url, event, mode,
|
||||
unit_cost=cost, course_id=course_id, currency=currency,
|
||||
message_id=request.COOKIES.get('sailthru_bid'))
|
||||
|
||||
|
||||
def _build_course_url(request, course_id):
|
||||
"""
|
||||
Build a course url from a course id and the host from the current request
|
||||
:param request:
|
||||
:param course_id:
|
||||
:return:
|
||||
"""
|
||||
host = request.get_host()
|
||||
# hack for integration testing since Sailthru rejects urls with localhost
|
||||
if host.startswith('localhost'):
|
||||
host = 'courses.edx.org'
|
||||
return '{scheme}://{host}/courses/{course}/info'.format(
|
||||
scheme=request.scheme,
|
||||
host=host,
|
||||
course=course_id
|
||||
)
|
||||
|
||||
|
||||
@receiver(CREATE_LOGON_COOKIE)
|
||||
@@ -54,12 +83,23 @@ def add_email_marketing_cookies(sender, response=None, user=None,
|
||||
if not email_config.enabled:
|
||||
return response
|
||||
|
||||
post_parms = {
|
||||
'id': user.email,
|
||||
'fields': {'keys': 1},
|
||||
'vars': {'last_login_date': datetime.datetime.now().strftime("%Y-%m-%d")}
|
||||
}
|
||||
|
||||
# get sailthru_content cookie to capture usage before logon
|
||||
request = crum.get_current_request()
|
||||
if request:
|
||||
sailthru_content = request.COOKIES.get('sailthru_content')
|
||||
if sailthru_content:
|
||||
post_parms['cookies'] = {'sailthru_content': sailthru_content}
|
||||
|
||||
try:
|
||||
sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret)
|
||||
sailthru_response = \
|
||||
sailthru_client.api_post("user", {'id': user.email, 'fields': {'keys': 1},
|
||||
'vars': {'last_login_date':
|
||||
datetime.datetime.now().strftime("%Y-%m-%d")}})
|
||||
sailthru_client.api_post("user", post_parms)
|
||||
except SailthruClientError as exc:
|
||||
log.error("Exception attempting to obtain cookie from Sailthru: %s", unicode(exc))
|
||||
return response
|
||||
@@ -93,7 +133,6 @@ def email_marketing_register_user(sender, user=None, profile=None,
|
||||
profile: The user profile for the user being changed
|
||||
kwargs: Not used
|
||||
"""
|
||||
log.info("Receiving REGISTER_USER")
|
||||
email_config = EmailMarketingConfiguration.current()
|
||||
if not email_config.enabled:
|
||||
return
|
||||
|
||||
@@ -6,8 +6,13 @@ import time
|
||||
|
||||
from celery import task
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import Http404
|
||||
from django.core.cache import cache
|
||||
|
||||
from email_marketing.models import EmailMarketingConfiguration
|
||||
from course_modes.models import CourseMode
|
||||
from courseware.courses import get_course_by_id
|
||||
from student.models import EnrollStatusChange
|
||||
|
||||
from sailthru.sailthru_client import SailthruClient
|
||||
from sailthru.sailthru_error import SailthruClientError
|
||||
@@ -30,16 +35,14 @@ def update_user(self, username, new_user=False, activation=False):
|
||||
return
|
||||
|
||||
# get user
|
||||
user = User.objects.select_related('profile').get(username=username)
|
||||
if not user:
|
||||
try:
|
||||
user = User.objects.select_related('profile').get(username=username)
|
||||
except User.DoesNotExist:
|
||||
log.error("User not found during Sailthru update %s", username)
|
||||
return
|
||||
|
||||
# get profile
|
||||
profile = user.profile
|
||||
if not profile:
|
||||
log.error("User profile not found during Sailthru update %s", username)
|
||||
return
|
||||
|
||||
sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret)
|
||||
try:
|
||||
@@ -92,8 +95,9 @@ def update_user_email(self, username, old_email):
|
||||
return
|
||||
|
||||
# get user
|
||||
user = User.objects.get(username=username)
|
||||
if not user:
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
log.error("User not found duing Sailthru update %s", username)
|
||||
return
|
||||
|
||||
@@ -145,3 +149,269 @@ def _create_sailthru_user_parm(user, profile, new_user, email_config):
|
||||
sailthru_user['lists'] = {email_config.sailthru_new_user_list: 1}
|
||||
|
||||
return sailthru_user
|
||||
|
||||
|
||||
# pylint: disable=not-callable
|
||||
@task(bind=True, default_retry_delay=3600, max_retries=24)
|
||||
def update_course_enrollment(self, email, course_url, event, mode,
|
||||
unit_cost=None, course_id=None,
|
||||
currency=None, message_id=None): # pylint: disable=unused-argument
|
||||
"""
|
||||
Adds/updates Sailthru when a user enrolls/unenrolls/adds to cart/purchases/upgrades a course
|
||||
Args:
|
||||
email(str): The user's email address
|
||||
course_url(str): Course home page url
|
||||
event(str): event type
|
||||
mode(object): enroll mode (audit, verification, ...)
|
||||
unit_cost: cost if purchase event
|
||||
course_id(CourseKey): course id
|
||||
currency(str): currency if purchase event - currently ignored since Sailthru only supports USD
|
||||
Returns:
|
||||
None
|
||||
|
||||
|
||||
The event can be one of the following:
|
||||
EnrollStatusChange.enroll
|
||||
A free enroll (mode=audit)
|
||||
EnrollStatusChange.unenroll
|
||||
An unenroll
|
||||
EnrollStatusChange.upgrade_start
|
||||
A paid upgrade added to cart
|
||||
EnrollStatusChange.upgrade_complete
|
||||
A paid upgrade purchase complete
|
||||
EnrollStatusChange.paid_start
|
||||
A non-free course added to cart
|
||||
EnrollStatusChange.paid_complete
|
||||
A non-free course purchase complete
|
||||
"""
|
||||
|
||||
email_config = EmailMarketingConfiguration.current()
|
||||
if not email_config.enabled:
|
||||
return
|
||||
|
||||
course_id_string = course_id.to_deprecated_string()
|
||||
|
||||
# Use event type to figure out processing required
|
||||
new_enroll = unenroll = fetch_tags = False
|
||||
incomplete = send_template = None
|
||||
if unit_cost:
|
||||
cost_in_cents = unit_cost * 100
|
||||
|
||||
if event == EnrollStatusChange.enroll:
|
||||
# new enroll for audit (no cost)
|
||||
new_enroll = True
|
||||
fetch_tags = True
|
||||
send_template = email_config.sailthru_enroll_template
|
||||
# set cost of $1 so that Sailthru recognizes the event
|
||||
cost_in_cents = email_config.sailthru_enroll_cost
|
||||
|
||||
elif event == EnrollStatusChange.unenroll:
|
||||
# unenroll - need to update list of unenrolled courses for user in Sailthru
|
||||
unenroll = True
|
||||
|
||||
elif event == EnrollStatusChange.upgrade_start:
|
||||
# add upgrade to cart
|
||||
incomplete = 1
|
||||
|
||||
elif event == EnrollStatusChange.paid_start:
|
||||
# add course purchase (probably 'honor') to cart
|
||||
incomplete = 1
|
||||
|
||||
elif event == EnrollStatusChange.upgrade_complete:
|
||||
# upgrade complete
|
||||
fetch_tags = True
|
||||
send_template = email_config.sailthru_upgrade_template
|
||||
|
||||
elif event == EnrollStatusChange.paid_complete:
|
||||
# paid course purchase complete
|
||||
new_enroll = True
|
||||
fetch_tags = True
|
||||
send_template = email_config.sailthru_purchase_template
|
||||
|
||||
sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret)
|
||||
|
||||
# update the "unenrolled" course array in the user record on Sailthru if new enroll or unenroll
|
||||
if new_enroll or unenroll:
|
||||
if not _update_unenrolled_list(sailthru_client, email, course_url, unenroll):
|
||||
raise self.retry(countdown=email_config.sailthru_retry_interval,
|
||||
max_retries=email_config.sailthru_max_retries)
|
||||
|
||||
# if there is a cost, call Sailthru purchase api to record
|
||||
if cost_in_cents:
|
||||
|
||||
# get course information if configured and appropriate event
|
||||
if fetch_tags and email_config.sailthru_get_tags_from_sailthru:
|
||||
course_data = _get_course_content(course_url, sailthru_client, email_config)
|
||||
else:
|
||||
course_data = {}
|
||||
|
||||
# build item description
|
||||
item = _build_purchase_item(course_id_string, course_url, cost_in_cents, mode, course_data, course_id)
|
||||
|
||||
# build purchase api options list
|
||||
options = {}
|
||||
if incomplete and email_config.sailthru_abandoned_cart_template:
|
||||
options['reminder_template'] = email_config.sailthru_abandoned_cart_template
|
||||
options['reminder_time'] = "+{} minutes".format(email_config.sailthru_abandoned_cart_delay)
|
||||
|
||||
# add appropriate send template
|
||||
if send_template:
|
||||
options['send_template'] = send_template
|
||||
|
||||
if not _record_purchase(sailthru_client, email, item, incomplete, message_id, options):
|
||||
raise self.retry(countdown=email_config.sailthru_retry_interval,
|
||||
max_retries=email_config.sailthru_max_retries)
|
||||
|
||||
|
||||
def _build_purchase_item(course_id_string, course_url, cost_in_cents, mode, course_data, course_id):
|
||||
"""
|
||||
Build Sailthru purchase item object
|
||||
:return: item
|
||||
"""
|
||||
|
||||
# build item description
|
||||
item = {
|
||||
'id': "{}-{}".format(course_id_string, mode),
|
||||
'url': course_url,
|
||||
'price': cost_in_cents,
|
||||
'qty': 1,
|
||||
}
|
||||
|
||||
# get title from course info if we don't already have it from Sailthru
|
||||
if 'title' in course_data:
|
||||
item['title'] = course_data['title']
|
||||
else:
|
||||
try:
|
||||
course = get_course_by_id(course_id)
|
||||
item['title'] = course.display_name
|
||||
except Http404:
|
||||
# can't find, just invent title
|
||||
item['title'] = 'Course {} mode: {}'.format(course_id_string, mode)
|
||||
|
||||
if 'tags' in course_data:
|
||||
item['tags'] = course_data['tags']
|
||||
|
||||
# add vars to item
|
||||
sailthru_vars = {}
|
||||
if 'vars' in course_data:
|
||||
sailthru_vars = course_data['vars']
|
||||
sailthru_vars['mode'] = mode
|
||||
sailthru_vars['course_run_id'] = course_id_string
|
||||
item['vars'] = sailthru_vars
|
||||
|
||||
# get list of modes for course and add upgrade deadlines for verified modes
|
||||
for mode_entry in CourseMode.modes_for_course(course_id):
|
||||
if mode_entry.expiration_datetime is not None and CourseMode.is_verified_slug(mode_entry.slug):
|
||||
sailthru_vars['upgrade_deadline_{}'.format(mode_entry.slug)] = \
|
||||
mode_entry.expiration_datetime.strftime("%Y-%m-%d")
|
||||
|
||||
return item
|
||||
|
||||
|
||||
def _record_purchase(sailthru_client, email, item, incomplete, message_id, options):
|
||||
"""
|
||||
Record a purchase in Sailthru
|
||||
:param sailthru_client:
|
||||
:param email:
|
||||
:param item:
|
||||
:param incomplete:
|
||||
:param message_id:
|
||||
:param options:
|
||||
:return: False it retryable error
|
||||
"""
|
||||
try:
|
||||
sailthru_response = sailthru_client.purchase(email, [item],
|
||||
incomplete=incomplete, message_id=message_id,
|
||||
options=options)
|
||||
|
||||
if not sailthru_response.is_ok():
|
||||
error = sailthru_response.get_error()
|
||||
log.error("Error attempting to record purchase in Sailthru: %s", error.get_message())
|
||||
return False
|
||||
|
||||
except SailthruClientError as exc:
|
||||
log.error("Exception attempting to record purchase for %s in Sailthru - %s", email, unicode(exc))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _get_course_content(course_url, sailthru_client, email_config):
|
||||
"""
|
||||
Get course information using the Sailthru content api.
|
||||
|
||||
If there is an error, just return with an empty response.
|
||||
:param course_url:
|
||||
:param sailthru_client:
|
||||
:return: dict with course information
|
||||
"""
|
||||
# check cache first
|
||||
response = cache.get(course_url)
|
||||
if not response:
|
||||
try:
|
||||
sailthru_response = sailthru_client.api_get("content", {"id": course_url})
|
||||
|
||||
if not sailthru_response.is_ok():
|
||||
return {}
|
||||
|
||||
response = sailthru_response.json
|
||||
cache.set(course_url, response, email_config.sailthru_content_cache_age)
|
||||
|
||||
except SailthruClientError:
|
||||
response = {}
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def _update_unenrolled_list(sailthru_client, email, course_url, unenroll):
|
||||
"""
|
||||
Maintain a list of courses the user has unenrolled from in the Sailthru user record
|
||||
:param sailthru_client:
|
||||
:param email:
|
||||
:param email_config:
|
||||
:param course_url:
|
||||
:param unenroll:
|
||||
:return: False if retryable error, else True
|
||||
"""
|
||||
try:
|
||||
# get the user 'vars' values from sailthru
|
||||
sailthru_response = sailthru_client.api_get("user", {"id": email, "fields": {"vars": 1}})
|
||||
if not sailthru_response.is_ok():
|
||||
error = sailthru_response.get_error()
|
||||
log.error("Error attempting to read user record from Sailthru: %s", error.get_message())
|
||||
return False
|
||||
|
||||
response_json = sailthru_response.json
|
||||
|
||||
unenroll_list = []
|
||||
if response_json and "vars" in response_json and "unenrolled" in response_json["vars"]:
|
||||
unenroll_list = response_json["vars"]["unenrolled"]
|
||||
|
||||
changed = False
|
||||
# if unenrolling, add course to unenroll list
|
||||
if unenroll:
|
||||
if course_url not in unenroll_list:
|
||||
unenroll_list.append(course_url)
|
||||
changed = True
|
||||
|
||||
# if enrolling, remove course from unenroll list
|
||||
elif course_url in unenroll_list:
|
||||
unenroll_list.remove(course_url)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
# write user record back
|
||||
sailthru_response = sailthru_client.api_post(
|
||||
"user", {'id': email, 'key': 'email', "vars": {"unenrolled": unenroll_list}})
|
||||
|
||||
if not sailthru_response.is_ok():
|
||||
error = sailthru_response.get_error()
|
||||
log.error("Error attempting to update user record in Sailthru: %s", error.get_message())
|
||||
return False
|
||||
|
||||
# everything worked
|
||||
return True
|
||||
|
||||
except SailthruClientError as exc:
|
||||
log.error("Exception attempting to update user record for %s in Sailthru - %s", email, unicode(exc))
|
||||
return False
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
"""Tests of email marketing signal handlers."""
|
||||
import logging
|
||||
import ddt
|
||||
import datetime
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from mock import patch
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from mock import patch, ANY
|
||||
from util.json_request import JsonResponse
|
||||
from django.http import Http404
|
||||
|
||||
from email_marketing.signals import handle_unenroll_done, \
|
||||
from email_marketing.signals import handle_enroll_status_change, \
|
||||
email_marketing_register_user, \
|
||||
email_marketing_user_field_changed, \
|
||||
add_email_marketing_cookies
|
||||
from email_marketing.tasks import update_user, update_user_email
|
||||
from email_marketing.tasks import update_user, update_user_email, update_course_enrollment, \
|
||||
_get_course_content, _update_unenrolled_list
|
||||
from email_marketing.models import EmailMarketingConfiguration
|
||||
from django.test.client import RequestFactory
|
||||
from student.tests.factories import UserFactory, UserProfileFactory
|
||||
from request_cache.middleware import RequestCache
|
||||
from student.models import EnrollStatusChange
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from course_modes.models import CourseMode
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from sailthru.sailthru_client import SailthruClient
|
||||
from sailthru.sailthru_response import SailthruResponse
|
||||
@@ -35,7 +43,12 @@ def update_email_marketing_config(enabled=False, key='badkey', secret='badsecret
|
||||
sailthru_key=key,
|
||||
sailthru_secret=secret,
|
||||
sailthru_new_user_list=new_user_list,
|
||||
sailthru_activation_template=template
|
||||
sailthru_activation_template=template,
|
||||
sailthru_enroll_template='enroll_template',
|
||||
sailthru_upgrade_template='upgrade_template',
|
||||
sailthru_purchase_template='purchase_template',
|
||||
sailthru_abandoned_cart_template='abandoned_template',
|
||||
sailthru_get_tags_from_sailthru=False
|
||||
)
|
||||
|
||||
|
||||
@@ -51,10 +64,16 @@ class EmailMarketingTests(TestCase):
|
||||
self.profile = self.user.profile
|
||||
self.request = self.request_factory.get("foo")
|
||||
update_email_marketing_config(enabled=True)
|
||||
|
||||
# create some test course objects
|
||||
self.course_id_string = 'edX/toy/2012_Fall'
|
||||
self.course_id = CourseKey.from_string(self.course_id_string)
|
||||
self.course_url = 'http://testserver/courses/edX/toy/2012_Fall/info'
|
||||
super(EmailMarketingTests, self).setUp()
|
||||
|
||||
@patch('email_marketing.signals.crum.get_current_request')
|
||||
@patch('email_marketing.signals.SailthruClient.api_post')
|
||||
def test_drop_cookie(self, mock_sailthru):
|
||||
def test_drop_cookie(self, mock_sailthru, mock_get_current_request):
|
||||
"""
|
||||
Test add_email_marketing_cookies
|
||||
"""
|
||||
@@ -62,13 +81,16 @@ class EmailMarketingTests(TestCase):
|
||||
"success": True,
|
||||
"redirect_url": 'test.com/test',
|
||||
})
|
||||
self.request.COOKIES['sailthru_content'] = 'cookie_content'
|
||||
mock_get_current_request.return_value = self.request
|
||||
mock_sailthru.return_value = SailthruResponse(JsonResponse({'keys': {'cookie': 'test_cookie'}}))
|
||||
add_email_marketing_cookies(None, response=response, user=self.user)
|
||||
mock_sailthru.assert_called_with('user',
|
||||
{'fields': {'keys': 1},
|
||||
'cookies': {'sailthru_content': 'cookie_content'},
|
||||
'id': TEST_EMAIL,
|
||||
'vars': {'last_login_date': ANY}})
|
||||
self.assertTrue('sailthru_hid' in response.cookies)
|
||||
self.assertEquals(mock_sailthru.call_args[0][0], "user")
|
||||
userparms = mock_sailthru.call_args[0][1]
|
||||
self.assertEquals(userparms['fields']['keys'], 1)
|
||||
self.assertEquals(userparms['id'], TEST_EMAIL)
|
||||
self.assertEquals(response.cookies['sailthru_hid'].value, "test_cookie")
|
||||
|
||||
@patch('email_marketing.signals.SailthruClient.api_post')
|
||||
@@ -111,7 +133,7 @@ class EmailMarketingTests(TestCase):
|
||||
self.assertEquals(userparms['lists']['new list'], 1)
|
||||
|
||||
@patch('email_marketing.tasks.SailthruClient.api_post')
|
||||
def test_activation(self, mock_sailthru):
|
||||
def test_user_activation(self, mock_sailthru):
|
||||
"""
|
||||
test send of activation template
|
||||
"""
|
||||
@@ -125,7 +147,7 @@ class EmailMarketingTests(TestCase):
|
||||
|
||||
@patch('email_marketing.tasks.log.error')
|
||||
@patch('email_marketing.tasks.SailthruClient.api_post')
|
||||
def test_error_logging(self, mock_sailthru, mock_log_error):
|
||||
def test_update_user_error_logging(self, mock_sailthru, mock_log_error):
|
||||
"""
|
||||
Ensure that error returned from Sailthru api is logged
|
||||
"""
|
||||
@@ -133,28 +155,121 @@ class EmailMarketingTests(TestCase):
|
||||
update_user.delay(self.user.username)
|
||||
self.assertTrue(mock_log_error.called)
|
||||
|
||||
# force Sailthru API exception
|
||||
mock_sailthru.side_effect = SailthruClientError
|
||||
update_user.delay(self.user.username)
|
||||
self.assertTrue(mock_log_error.called)
|
||||
|
||||
# force Sailthru API exception on 2nd call
|
||||
mock_sailthru.side_effect = [None, SailthruClientError]
|
||||
mock_sailthru.return_value = SailthruResponse(JsonResponse({'ok': True}))
|
||||
update_user.delay(self.user.username, new_user=True)
|
||||
self.assertTrue(mock_log_error.called)
|
||||
|
||||
# force Sailthru API error return on 2nd call
|
||||
mock_sailthru.side_effect = None
|
||||
mock_sailthru.return_value = [SailthruResponse(JsonResponse({'ok': True})),
|
||||
SailthruResponse(JsonResponse({'error': 100, 'errormsg': 'Got an error'}))]
|
||||
update_user.delay(self.user.username, new_user=True)
|
||||
self.assertTrue(mock_log_error.called)
|
||||
|
||||
@patch('email_marketing.tasks.log.error')
|
||||
@patch('email_marketing.tasks.SailthruClient.api_post')
|
||||
def test_just_return(self, mock_sailthru, mock_log_error):
|
||||
def test_update_user_error_logging_bad_user(self, mock_sailthru, mock_log_error):
|
||||
"""
|
||||
Test update_user with invalid user
|
||||
"""
|
||||
update_user.delay('baduser')
|
||||
self.assertTrue(mock_log_error.called)
|
||||
self.assertFalse(mock_sailthru.called)
|
||||
|
||||
update_user_email.delay('baduser', 'aa@bb.com')
|
||||
self.assertTrue(mock_log_error.called)
|
||||
self.assertFalse(mock_sailthru.called)
|
||||
|
||||
@patch('email_marketing.tasks.log.error')
|
||||
@patch('email_marketing.tasks.SailthruClient.api_post')
|
||||
def test_just_return_tasks(self, mock_sailthru, mock_log_error):
|
||||
"""
|
||||
Ensure that disabling Sailthru just returns
|
||||
"""
|
||||
update_email_marketing_config(enabled=False)
|
||||
|
||||
update_user.delay(self.user.username)
|
||||
self.assertFalse(mock_log_error.called)
|
||||
self.assertFalse(mock_sailthru.called)
|
||||
|
||||
update_user_email.delay(self.user.username, "newemail2@test.com")
|
||||
self.assertFalse(mock_log_error.called)
|
||||
self.assertFalse(mock_sailthru.called)
|
||||
|
||||
update_course_enrollment.delay(self.user.username, TEST_EMAIL, 'http://course',
|
||||
EnrollStatusChange.enroll, 'audit')
|
||||
self.assertFalse(mock_log_error.called)
|
||||
self.assertFalse(mock_sailthru.called)
|
||||
|
||||
update_email_marketing_config(enabled=True)
|
||||
|
||||
@patch('email_marketing.signals.log.error')
|
||||
def test_just_return_signals(self, mock_log_error):
|
||||
"""
|
||||
Ensure that disabling Sailthru just returns
|
||||
"""
|
||||
update_email_marketing_config(enabled=False)
|
||||
|
||||
handle_enroll_status_change(None)
|
||||
self.assertFalse(mock_log_error.called)
|
||||
|
||||
add_email_marketing_cookies(None)
|
||||
self.assertFalse(mock_log_error.called)
|
||||
|
||||
email_marketing_register_user(None)
|
||||
self.assertFalse(mock_log_error.called)
|
||||
|
||||
update_email_marketing_config(enabled=True)
|
||||
|
||||
# test anonymous users
|
||||
anon = AnonymousUser()
|
||||
email_marketing_register_user(None, user=anon)
|
||||
self.assertFalse(mock_log_error.called)
|
||||
|
||||
email_marketing_user_field_changed(None, user=anon)
|
||||
self.assertFalse(mock_log_error.called)
|
||||
|
||||
@patch('email_marketing.signals.crum.get_current_request')
|
||||
@patch('lms.djangoapps.email_marketing.tasks.update_course_enrollment.delay')
|
||||
def test_handle_enroll_status_change(self, mock_update_course_enrollment, mock_get_current_request):
|
||||
"""
|
||||
Test that the enroll status change signal handler properly calls the task routine
|
||||
"""
|
||||
# should just return if no current request found
|
||||
mock_get_current_request.return_value = None
|
||||
handle_enroll_status_change(None)
|
||||
self.assertFalse(mock_update_course_enrollment.called)
|
||||
|
||||
# now test with current request
|
||||
mock_get_current_request.return_value = self.request
|
||||
self.request.COOKIES['sailthru_bid'] = 'cookie_bid'
|
||||
handle_enroll_status_change(None, event=EnrollStatusChange.enroll,
|
||||
user=self.user,
|
||||
mode='audit', course_id=self.course_id,
|
||||
cost=None, currency=None)
|
||||
self.assertTrue(mock_update_course_enrollment.called)
|
||||
mock_update_course_enrollment.assert_called_with(TEST_EMAIL,
|
||||
self.course_url,
|
||||
EnrollStatusChange.enroll,
|
||||
'audit',
|
||||
course_id=self.course_id,
|
||||
currency=None,
|
||||
message_id='cookie_bid',
|
||||
unit_cost=None)
|
||||
|
||||
@patch('email_marketing.tasks.SailthruClient.api_post')
|
||||
def test_change_email(self, mock_sailthru):
|
||||
"""
|
||||
test async method in task that changes email in Sailthru
|
||||
"""
|
||||
mock_sailthru.return_value = SailthruResponse(JsonResponse({'ok': True}))
|
||||
#self.user.email = "newemail@test.com"
|
||||
update_user_email.delay(self.user.username, "old@edx.org")
|
||||
self.assertEquals(mock_sailthru.call_args[0][0], "user")
|
||||
userparms = mock_sailthru.call_args[0][1]
|
||||
@@ -162,6 +277,229 @@ class EmailMarketingTests(TestCase):
|
||||
self.assertEquals(userparms['id'], "old@edx.org")
|
||||
self.assertEquals(userparms['keys']['email'], TEST_EMAIL)
|
||||
|
||||
@patch('email_marketing.tasks.log.error')
|
||||
@patch('email_marketing.tasks.SailthruClient.purchase')
|
||||
@patch('email_marketing.tasks.SailthruClient.api_get')
|
||||
@patch('email_marketing.tasks.SailthruClient.api_post')
|
||||
@patch('email_marketing.tasks.get_course_by_id')
|
||||
def test_update_course_enrollment(self, mock_get_course, mock_sailthru_api_post,
|
||||
mock_sailthru_api_get, mock_sailthru_purchase, mock_log_error):
|
||||
"""
|
||||
test async method in task posts enrolls and purchases
|
||||
"""
|
||||
|
||||
CourseMode.objects.create(
|
||||
course_id=self.course_id,
|
||||
mode_slug=CourseMode.AUDIT,
|
||||
mode_display_name=CourseMode.AUDIT
|
||||
)
|
||||
CourseMode.objects.create(
|
||||
course_id=self.course_id,
|
||||
mode_slug=CourseMode.VERIFIED,
|
||||
mode_display_name=CourseMode.VERIFIED,
|
||||
min_price=49,
|
||||
expiration_datetime=datetime.date(2020, 3, 12)
|
||||
)
|
||||
mock_get_course.return_value = {'display_name': "Test Title"}
|
||||
mock_sailthru_api_post.return_value = SailthruResponse(JsonResponse({'ok': True}))
|
||||
mock_sailthru_api_get.return_value = SailthruResponse(JsonResponse({'vars': {'unenrolled': ['course_u1']}}))
|
||||
mock_sailthru_purchase.return_value = SailthruResponse(JsonResponse({'ok': True}))
|
||||
|
||||
# test enroll
|
||||
mock_get_course.side_effect = Http404
|
||||
update_course_enrollment.delay(TEST_EMAIL,
|
||||
self.course_url,
|
||||
EnrollStatusChange.enroll,
|
||||
'audit',
|
||||
course_id=self.course_id,
|
||||
currency='USD',
|
||||
message_id='cookie_bid',
|
||||
unit_cost=0)
|
||||
mock_sailthru_purchase.assert_called_with(TEST_EMAIL, [{'vars': {'course_run_id': self.course_id_string, 'mode': 'audit',
|
||||
'upgrade_deadline_verified': '2020-03-12'},
|
||||
'title': 'Course ' + self.course_id_string + ' mode: audit',
|
||||
'url': self.course_url,
|
||||
'price': 100, 'qty': 1, 'id': self.course_id_string + '-audit'}],
|
||||
options={'send_template': 'enroll_template'},
|
||||
incomplete=None, message_id='cookie_bid')
|
||||
|
||||
# test unenroll
|
||||
update_course_enrollment.delay(TEST_EMAIL,
|
||||
self.course_url,
|
||||
EnrollStatusChange.unenroll,
|
||||
'audit',
|
||||
course_id=self.course_id,
|
||||
currency='USD',
|
||||
message_id='cookie_bid',
|
||||
unit_cost=0)
|
||||
mock_sailthru_purchase.assert_called_with(TEST_EMAIL, [{'vars': {'course_run_id': self.course_id_string, 'mode': 'audit',
|
||||
'upgrade_deadline_verified': '2020-03-12'},
|
||||
'title': 'Course ' + self.course_id_string + ' mode: audit',
|
||||
'url': self.course_url,
|
||||
'price': 100, 'qty': 1, 'id': self.course_id_string + '-audit'}],
|
||||
options={'send_template': 'enroll_template'},
|
||||
incomplete=None, message_id='cookie_bid')
|
||||
|
||||
# test add upgrade to cart
|
||||
update_course_enrollment.delay(TEST_EMAIL,
|
||||
self.course_url,
|
||||
EnrollStatusChange.upgrade_start,
|
||||
'verified',
|
||||
course_id=self.course_id,
|
||||
currency='USD',
|
||||
message_id='cookie_bid',
|
||||
unit_cost=49)
|
||||
mock_sailthru_purchase.assert_called_with(TEST_EMAIL, [{'vars': {'course_run_id': self.course_id_string, 'mode': 'verified',
|
||||
'upgrade_deadline_verified': '2020-03-12'},
|
||||
'title': 'Course ' + self.course_id_string + ' mode: verified',
|
||||
'url': self.course_url,
|
||||
'price': 4900, 'qty': 1, 'id': self.course_id_string + '-verified'}],
|
||||
options={'reminder_template': 'abandoned_template', 'reminder_time': '+60 minutes'},
|
||||
incomplete=1, message_id='cookie_bid')
|
||||
|
||||
# test add purchase to cart
|
||||
update_course_enrollment.delay(TEST_EMAIL,
|
||||
self.course_url,
|
||||
EnrollStatusChange.paid_start,
|
||||
'honor',
|
||||
course_id=self.course_id,
|
||||
currency='USD',
|
||||
message_id='cookie_bid',
|
||||
unit_cost=49)
|
||||
mock_sailthru_purchase.assert_called_with(TEST_EMAIL, [{'vars': {'course_run_id': self.course_id_string, 'mode': 'honor',
|
||||
'upgrade_deadline_verified': '2020-03-12'},
|
||||
'title': 'Course ' + self.course_id_string + ' mode: honor',
|
||||
'url': self.course_url,
|
||||
'price': 4900, 'qty': 1, 'id': self.course_id_string + '-honor'}],
|
||||
options={'reminder_template': 'abandoned_template', 'reminder_time': '+60 minutes'},
|
||||
incomplete=1, message_id='cookie_bid')
|
||||
|
||||
# test purchase complete
|
||||
update_course_enrollment.delay(TEST_EMAIL,
|
||||
self.course_url,
|
||||
EnrollStatusChange.paid_complete,
|
||||
'honor',
|
||||
course_id=self.course_id,
|
||||
currency='USD',
|
||||
message_id='cookie_bid',
|
||||
unit_cost=99)
|
||||
mock_sailthru_purchase.assert_called_with(TEST_EMAIL, [{'vars': {'course_run_id': self.course_id_string, 'mode': 'honor',
|
||||
'upgrade_deadline_verified': '2020-03-12'},
|
||||
'title': 'Course ' + self.course_id_string + ' mode: honor',
|
||||
'url': self.course_url,
|
||||
'price': 9900, 'qty': 1, 'id': self.course_id_string + '-honor'}],
|
||||
options={'send_template': 'purchase_template'},
|
||||
incomplete=None, message_id='cookie_bid')
|
||||
|
||||
# test upgrade complete
|
||||
update_course_enrollment.delay(TEST_EMAIL,
|
||||
self.course_url,
|
||||
EnrollStatusChange.upgrade_complete,
|
||||
'verified',
|
||||
course_id=self.course_id,
|
||||
currency='USD',
|
||||
message_id='cookie_bid',
|
||||
unit_cost=99)
|
||||
mock_sailthru_purchase.assert_called_with(TEST_EMAIL, [{'vars': {'course_run_id': self.course_id_string, 'mode': 'verified',
|
||||
'upgrade_deadline_verified': '2020-03-12'},
|
||||
'title': 'Course ' + self.course_id_string + ' mode: verified',
|
||||
'url': self.course_url,
|
||||
'price': 9900, 'qty': 1, 'id': self.course_id_string + '-verified'}],
|
||||
options={'send_template': 'upgrade_template'},
|
||||
incomplete=None, message_id='cookie_bid')
|
||||
|
||||
# test purchase API error
|
||||
mock_sailthru_purchase.return_value = SailthruResponse(JsonResponse({'error': 100, 'errormsg': 'Got an error'}))
|
||||
update_course_enrollment.delay(TEST_EMAIL,
|
||||
self.course_url,
|
||||
EnrollStatusChange.upgrade_complete,
|
||||
'verified',
|
||||
course_id=self.course_id,
|
||||
currency='USD',
|
||||
message_id='cookie_bid',
|
||||
unit_cost=99)
|
||||
self.assertTrue(mock_log_error.called)
|
||||
|
||||
# test purchase API exception
|
||||
mock_sailthru_purchase.side_effect = SailthruClientError
|
||||
update_course_enrollment.delay(TEST_EMAIL,
|
||||
self.course_url,
|
||||
EnrollStatusChange.upgrade_complete,
|
||||
'verified',
|
||||
course_id=self.course_id,
|
||||
currency='USD',
|
||||
message_id='cookie_bid',
|
||||
unit_cost=99)
|
||||
self.assertTrue(mock_log_error.called)
|
||||
|
||||
@patch('email_marketing.tasks.SailthruClient')
|
||||
def test_get_course_content(self, mock_sailthru_client):
|
||||
"""
|
||||
test routine which fetches data from Sailthru content api
|
||||
"""
|
||||
mock_sailthru_client.api_get.return_value = SailthruResponse(JsonResponse({"title": "The title"}))
|
||||
response_json = _get_course_content('course:123', mock_sailthru_client, EmailMarketingConfiguration.current())
|
||||
self.assertEquals(response_json, {"title": "The title"})
|
||||
mock_sailthru_client.api_get.assert_called_with('content', {'id': 'course:123'})
|
||||
|
||||
# test second call uses cache
|
||||
response_json = _get_course_content('course:123', mock_sailthru_client, EmailMarketingConfiguration.current())
|
||||
self.assertEquals(response_json, {"title": "The title"})
|
||||
mock_sailthru_client.api_get.assert_not_called()
|
||||
|
||||
# test error from Sailthru
|
||||
mock_sailthru_client.api_get.return_value = \
|
||||
SailthruResponse(JsonResponse({'error': 100, 'errormsg': 'Got an error'}))
|
||||
self.assertEquals(_get_course_content('course:124', mock_sailthru_client, EmailMarketingConfiguration.current()), {})
|
||||
|
||||
# test exception
|
||||
mock_sailthru_client.api_get.side_effect = SailthruClientError
|
||||
self.assertEquals(_get_course_content('course:125', mock_sailthru_client, EmailMarketingConfiguration.current()), {})
|
||||
|
||||
@patch('email_marketing.tasks.SailthruClient')
|
||||
def test_update_unenrolled_list(self, mock_sailthru_client):
|
||||
"""
|
||||
test routine which updates the unenrolled list in Sailthru
|
||||
"""
|
||||
|
||||
# test a new unenroll
|
||||
mock_sailthru_client.api_get.return_value = \
|
||||
SailthruResponse(JsonResponse({'vars': {'unenrolled': ['course_u1']}}))
|
||||
self.assertTrue(_update_unenrolled_list(mock_sailthru_client, TEST_EMAIL,
|
||||
self.course_url, True))
|
||||
mock_sailthru_client.api_get.assert_called_with("user", {"id": TEST_EMAIL, "fields": {"vars": 1}})
|
||||
mock_sailthru_client.api_post.assert_called_with('user',
|
||||
{'vars': {'unenrolled': ['course_u1', self.course_url]},
|
||||
'id': TEST_EMAIL, 'key': 'email'})
|
||||
|
||||
# test an enroll of a previously unenrolled course
|
||||
mock_sailthru_client.api_get.return_value = \
|
||||
SailthruResponse(JsonResponse({'vars': {'unenrolled': [self.course_url]}}))
|
||||
self.assertTrue(_update_unenrolled_list(mock_sailthru_client, TEST_EMAIL,
|
||||
self.course_url, False))
|
||||
mock_sailthru_client.api_post.assert_called_with('user',
|
||||
{'vars': {'unenrolled': []},
|
||||
'id': TEST_EMAIL, 'key': 'email'})
|
||||
|
||||
# test get error from Sailthru
|
||||
mock_sailthru_client.api_get.return_value = \
|
||||
SailthruResponse(JsonResponse({'error': 100, 'errormsg': 'Got an error'}))
|
||||
self.assertFalse(_update_unenrolled_list(mock_sailthru_client, TEST_EMAIL,
|
||||
self.course_url, False))
|
||||
|
||||
# test post error from Sailthru
|
||||
mock_sailthru_client.api_post.return_value = \
|
||||
SailthruResponse(JsonResponse({'error': 100, 'errormsg': 'Got an error'}))
|
||||
mock_sailthru_client.api_get.return_value = \
|
||||
SailthruResponse(JsonResponse({'vars': {'unenrolled': [self.course_url]}}))
|
||||
self.assertFalse(_update_unenrolled_list(mock_sailthru_client, TEST_EMAIL,
|
||||
self.course_url, False))
|
||||
|
||||
# test exception
|
||||
mock_sailthru_client.api_get.side_effect = SailthruClientError
|
||||
self.assertFalse(_update_unenrolled_list(mock_sailthru_client, TEST_EMAIL,
|
||||
self.course_url, False))
|
||||
|
||||
@patch('email_marketing.tasks.log.error')
|
||||
@patch('email_marketing.tasks.SailthruClient.api_post')
|
||||
def test_error_logging1(self, mock_sailthru, mock_log_error):
|
||||
@@ -172,6 +510,10 @@ class EmailMarketingTests(TestCase):
|
||||
update_user_email.delay(self.user.username, "newemail2@test.com")
|
||||
self.assertTrue(mock_log_error.called)
|
||||
|
||||
mock_sailthru.side_effect = SailthruClientError
|
||||
update_user_email.delay(self.user.username, "newemail2@test.com")
|
||||
self.assertTrue(mock_log_error.called)
|
||||
|
||||
@patch('lms.djangoapps.email_marketing.tasks.update_user.delay')
|
||||
def test_register_user(self, mock_update_user):
|
||||
"""
|
||||
|
||||
@@ -38,7 +38,7 @@ from courseware.courses import get_course_by_id
|
||||
from config_models.models import ConfigurationModel
|
||||
from course_modes.models import CourseMode
|
||||
from edxmako.shortcuts import render_to_string
|
||||
from student.models import CourseEnrollment, UNENROLL_DONE
|
||||
from student.models import CourseEnrollment, UNENROLL_DONE, EnrollStatusChange
|
||||
from util.query import use_read_replica_if_available
|
||||
from xmodule_django.models import CourseKeyField
|
||||
from .exceptions import (
|
||||
@@ -1587,6 +1587,10 @@ class PaidCourseRegistration(OrderItem):
|
||||
item.save()
|
||||
log.info("User {} added course registration {} to cart: order {}"
|
||||
.format(order.user.email, course_id, order.id))
|
||||
|
||||
CourseEnrollment.send_signal_full(EnrollStatusChange.paid_start,
|
||||
user=order.user, mode=item.mode, course_id=course_id,
|
||||
cost=cost, currency=currency)
|
||||
return item
|
||||
|
||||
def purchased_callback(self):
|
||||
@@ -1607,6 +1611,8 @@ class PaidCourseRegistration(OrderItem):
|
||||
|
||||
log.info("Enrolled {0} in paid course {1}, paid ${2}"
|
||||
.format(self.user.email, self.course_id, self.line_cost))
|
||||
self.course_enrollment.send_signal(EnrollStatusChange.paid_complete,
|
||||
cost=self.line_cost, currency=self.currency)
|
||||
|
||||
def generate_receipt_instructions(self):
|
||||
"""
|
||||
@@ -1977,6 +1983,9 @@ class CertificateItem(OrderItem):
|
||||
order.currency = currency
|
||||
order.save()
|
||||
item.save()
|
||||
|
||||
# signal course added to cart
|
||||
course_enrollment.send_signal(EnrollStatusChange.paid_start, cost=cost, currency=currency)
|
||||
return item
|
||||
|
||||
def purchased_callback(self):
|
||||
@@ -1985,6 +1994,8 @@ class CertificateItem(OrderItem):
|
||||
"""
|
||||
self.course_enrollment.change_mode(self.mode)
|
||||
self.course_enrollment.activate()
|
||||
self.course_enrollment.send_signal(EnrollStatusChange.upgrade_complete,
|
||||
cost=self.unit_cost, currency=self.currency)
|
||||
|
||||
def additional_instruction_text(self):
|
||||
verification_reminder = ""
|
||||
|
||||
Reference in New Issue
Block a user