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()), {})
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()), {} )
def email_marketing_user_field_changed(sender, user=None, table=None, setting=None, old_value=None, new_value=None, **kwargs): # pylint: disable=unused-argument """ Update a single user/profile field Args: sender: Not used user: The user object for the user being changed table: The name of the table being updated setting: The name of the setting being updated old_value: Prior value new_value: New value kwargs: Not used """ # ignore anonymous users if user.is_anonymous(): return # ignore anything but User, Profile or UserPreference tables if table not in { 'auth_user', 'auth_userprofile', 'user_api_userpreference' }: return # ignore anything not in list of fields to handle if setting in CHANGED_FIELDNAMES: # skip if not enabled # the check has to be here rather than at the start of the method to avoid # accessing the config during migration 0001_date__add_ecommerce_service_user email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # set the activation flag when the user is marked as activated update_user.delay(_create_sailthru_user_vars(user, user.profile), user.email, site=_get_current_site(), new_user=False, activation=(setting == 'is_active') and new_value is True) elif setting == 'email': # email update is special case email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return update_user_email.delay(user.email, old_value)
def email_marketing_user_field_changed(sender, user=None, table=None, setting=None, old_value=None, new_value=None, **kwargs): # pylint: disable=unused-argument """ Update a single user/profile field Args: sender: Not used user: The user object for the user being changed table: The name of the table being updated setting: The name of the setting being updated old_value: Prior value new_value: New value kwargs: Not used """ # ignore anonymous users if user.is_anonymous(): return # ignore anything but User or Profile table if table != 'auth_user' and table != 'auth_userprofile': return # ignore anything not in list of fields to handle if setting in CHANGED_FIELDNAMES: # skip if not enabled # the check has to be here rather than at the start of the method to avoid # accessing the config during migration 0001_date__add_ecommerce_service_user email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # perform update asynchronously, flag if activation update_user.delay(user.username, new_user=False, activation=(setting == 'is_active') and new_value is True) elif setting == 'email': # email update is special case email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return update_user_email.delay(user.username, old_value)
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 enroll/unenroll/purchase events """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled or not event or not user or not mode or not course_id: return 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 update_user(self, sailthru_vars, email, site=None, new_user=False, activation=False): """ Adds/updates Sailthru profile information for a user. Args: sailthru_vars(dict): User profile information to pass as 'vars' to Sailthru email(str): User email address new_user(boolean): True if new registration activation(boolean): True if activation request Returns: None """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) try: sailthru_response = sailthru_client.api_post("user", _create_email_user_param(sailthru_vars, sailthru_client, email, new_user, email_config, site=site)) except SailthruClientError as exc: log.error("Exception attempting to add/update user %s in Sailthru - %s", email, unicode(exc)) raise self.retry(exc=exc, countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) if not sailthru_response.is_ok(): error = sailthru_response.get_error() log.error("Error attempting to add/update user in Sailthru: %s", error.get_message()) if _retryable_sailthru_error(error): raise self.retry(countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) return # if activating user, send welcome email if activation and email_config.sailthru_activation_template: scheduled_datetime = datetime.utcnow() + timedelta(seconds=email_config.welcome_email_send_delay) try: sailthru_response = sailthru_client.api_post( "send", { "email": email, "template": email_config.sailthru_activation_template, "schedule_time": scheduled_datetime.strftime('%Y-%m-%dT%H:%M:%SZ') } ) except SailthruClientError as exc: log.error("Exception attempting to send welcome email to user %s in Sailthru - %s", email, unicode(exc)) raise self.retry(exc=exc, countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) if not sailthru_response.is_ok(): error = sailthru_response.get_error() log.error("Error attempting to send welcome email to user in Sailthru: %s", error.get_message()) if _retryable_sailthru_error(error): raise self.retry(countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries)
def add_email_marketing_cookies(sender, response=None, user=None, **kwargs): # pylint: disable=unused-argument """ Signal function for adding any cookies needed for email marketing Args: response: http response object user: The user object for the user being changed Returns: response: http response object with cookie added """ email_config = EmailMarketingConfiguration.current() 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 anonymous_interest cookie to capture usage before logon request = crum.get_current_request() if request: sailthru_content = request.COOKIES.get('anonymous_interest') if sailthru_content: post_parms['cookies'] = {'anonymous_interest': sailthru_content} try: sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) log.info( 'Sending to Sailthru the user interest cookie [%s] for user [%s]', post_parms.get('cookies', ''), user.email ) sailthru_response = \ 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 if sailthru_response.is_ok(): if 'keys' in sailthru_response.json and 'cookie' in sailthru_response.json['keys']: cookie = sailthru_response.json['keys']['cookie'] response.set_cookie( 'sailthru_hid', cookie, max_age=365 * 24 * 60 * 60, # set for 1 year domain=settings.SESSION_COOKIE_DOMAIN, path='/', ) else: log.error("No cookie returned attempting to obtain cookie from Sailthru for %s", user.email) else: error = sailthru_response.get_error() # generally invalid email address log.info("Error attempting to obtain cookie from Sailthru: %s", error.get_message()) return response
def email_marketing_register_user(sender, user, registration, **kwargs): # pylint: disable=unused-argument """ Called after user created and saved Args: sender: Not used user: The user object for the user being changed registration: The user registration profile to activate user account kwargs: Not used """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # ignore anonymous users if user.is_anonymous(): return # perform update asynchronously update_user.delay(_create_sailthru_user_vars(user, user.profile, registration=registration), user.email, site=_get_current_site(), new_user=True)
def update_user_email(self, new_email, old_email): """ Adds/updates Sailthru when a user email address is changed Args: username(str): A string representation of user identifier old_email(str): Original email address Returns: None """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # ignore if email not changed if new_email == old_email: return sailthru_parms = {"id": old_email, "key": "email", "keysconflict": "merge", "keys": {"email": new_email}} try: sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) sailthru_response = sailthru_client.api_post("user", sailthru_parms) except SailthruClientError as exc: log.error("Exception attempting to update email for %s in Sailthru - %s", old_email, unicode(exc)) raise self.retry(exc=exc, countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) if not sailthru_response.is_ok(): error = sailthru_response.get_error() log.error("Error attempting to update user email address in Sailthru: %s", error.get_message()) if _retryable_sailthru_error(error): raise self.retry(countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries)
def update_course_enrollment(self, email, course_key, mode): """Adds/updates Sailthru when a user adds to cart/purchases/upgrades a course Args: user: current user course_key: course key of course Returns: None """ course_url = build_course_url(course_key) config = EmailMarketingConfiguration.current() try: sailthru_client = SailthruClient(config.sailthru_key, config.sailthru_secret) except: return send_template = config.sailthru_enroll_template cost_in_cents = 0 if not update_unenrolled_list(sailthru_client, email, course_url, False): schedule_retry(self, config) course_data = _get_course_content(course_key, course_url, sailthru_client, config) item = _build_purchase_item(course_key, course_url, cost_in_cents, mode, course_data) options = {} if send_template: options['send_template'] = send_template if not _record_purchase(sailthru_client, email, item, options): schedule_retry(self, config)
def handle_enroll_status_change(sender, event=None, user=None, mode=None, course_id=None, **kwargs): # pylint: disable=unused-argument """ Signal receiver for enroll/unenroll/purchase events """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled or not event or not user or not mode or not course_id: return # skip tracking (un)enrolls if simulated cost=0 if email_config.sailthru_enroll_cost == 0: return request = crum.get_current_request() if not request: return # get string course_id serializable to send through celery course_id_string = unicode(course_id) # figure out course url course_url = _build_course_url(request, course_id_string, email_config) # pass event to email_marketing.tasks update_course_enrollment.delay( user.email, course_url, event, mode, course_id=course_id_string, message_id=request.COOKIES.get('sailthru_bid'))
def handle_enroll_status_change(sender, event=None, user=None, mode=None, course_id=None, **kwargs): # pylint: disable=unused-argument """ Signal receiver for enroll/unenroll/purchase events """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled or not event or not user or not mode or not course_id: return # skip tracking (un)enrolls if simulated cost=0 if email_config.sailthru_enroll_cost == 0: return request = crum.get_current_request() if not request: return # get string course_id serializable to send through celery course_id_string = unicode(course_id) # figure out course url course_url = _build_course_url(request, course_id_string, email_config) # pass event to email_marketing.tasks update_course_enrollment.delay(user.email, course_url, event, mode, course_id=course_id_string, message_id=request.COOKIES.get('sailthru_bid'))
def add_email_marketing_cookies(sender, response=None, user=None, **kwargs): # pylint: disable=unused-argument """ Signal function for adding any cookies needed for email marketing Args: response: http response object user: The user object for the user being changed Returns: response: http response object with cookie added """ email_config = EmailMarketingConfiguration.current() 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 anonymous_interest cookie to capture usage before logon request = crum.get_current_request() if request: sailthru_content = request.COOKIES.get('anonymous_interest') if sailthru_content: post_parms['cookies'] = {'anonymous_interest': sailthru_content} time_before_call = datetime.datetime.now() sailthru_response = get_email_cookies_via_sailthru.delay(user.email, post_parms) try: # synchronous call to get result of an asynchronous celery task, with timeout sailthru_response.get(timeout=email_config.user_registration_cookie_timeout_delay, propagate=True) cookie = sailthru_response.result _log_sailthru_api_call_time(time_before_call) except TimeoutError as exc: log.error("Timeout error while attempting to obtain cookie from Sailthru: %s", unicode(exc)) return response except SailthruClientError as exc: log.error("Exception attempting to obtain cookie from Sailthru: %s", unicode(exc)) return response if not cookie: log.error("No cookie returned attempting to obtain cookie from Sailthru for %s", user.email) return response else: response.set_cookie( 'sailthru_hid', cookie, max_age=365 * 24 * 60 * 60, # set for 1 year domain=settings.SESSION_COOKIE_DOMAIN, path='/', ) log.info("sailthru_hid cookie:%s successfully retrieved for user %s", cookie, user.email) return response
def add_email_marketing_cookies(sender, response=None, user=None, **kwargs): # pylint: disable=unused-argument """ Signal function for adding any cookies needed for email marketing Args: response: http response object user: The user object for the user being changed Returns: response: http response object with cookie added """ email_config = EmailMarketingConfiguration.current() 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 anonymous_interest cookie to capture usage before logon request = crum.get_current_request() if request: sailthru_content = request.COOKIES.get('anonymous_interest') if sailthru_content: post_parms['cookies'] = {'anonymous_interest': sailthru_content} time_before_call = datetime.datetime.now() sailthru_response = get_email_cookies_via_sailthru.delay(user.email, post_parms) try: # synchronous call to get result of an asynchronous celery task, with timeout sailthru_response.get(timeout=email_config.user_registration_cookie_timeout_delay, propagate=True) cookie = sailthru_response.result except TimeoutError as exc: log.error("Timeout error while attempting to obtain cookie from Sailthru: %s", unicode(exc)) return response except SailthruClientError as exc: log.error("Exception attempting to obtain cookie from Sailthru: %s", unicode(exc)) return response if not cookie: log.error("No cookie returned attempting to obtain cookie from Sailthru for %s", user.email) return response else: response.set_cookie( 'sailthru_hid', cookie, max_age=365 * 24 * 60 * 60, # set for 1 year domain=settings.SESSION_COOKIE_DOMAIN, path='/', ) _log_sailthru_api_call_time(time_before_call) return response
def handle_unenroll_done(sender, course_enrollment=None, skip_refund=False, **kwargs): # pylint: disable=unused-argument """ Signal receiver for unenrollments """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return
def update_user(self, username, new_user=False, activation=False): """ Adds/updates Sailthru profile information for a user. Args: username(str): A string representation of user identifier Returns: None """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # get user user = User.objects.select_related('profile').get(username=username) if not user: 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: sailthru_response = sailthru_client.api_post("user", _create_sailthru_user_parm(user, profile, new_user, email_config)) except SailthruClientError as exc: log.error("Exception attempting to add/update user %s in Sailthru - %s", username, unicode(exc)) raise self.retry(exc=exc, countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) if not sailthru_response.is_ok(): error = sailthru_response.get_error() # put out error and schedule retry log.error("Error attempting to add/update user in Sailthru: %s", error.get_message()) raise self.retry(countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) # if activating user, send welcome email if activation and email_config.sailthru_activation_template: try: sailthru_response = sailthru_client.api_post("send", {"email": user.email, "template": email_config.sailthru_activation_template}) except SailthruClientError as exc: log.error("Exception attempting to send welcome email to user %s in Sailthru - %s", username, unicode(exc)) raise self.retry(exc=exc, countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) if not sailthru_response.is_ok(): error = sailthru_response.get_error() # probably an invalid template name, just put out error log.error("Error attempting to send welcome email to user in Sailthru: %s", error.get_message())
def force_unsubscribe_all(sender, **kwargs): # pylint: disable=unused-argument """ Synchronously(!) unsubscribes the given user from all Sailthru email lists. In the future this could be moved to a Celery task, however this is currently only used as part of user retirement, where we need a very reliable indication of success or failure. Args: email: Email address to unsubscribe new_email (optional): Email address to change 3rd party services to for this user (used in retirement to clear personal information from the service) Returns: None """ email = kwargs.get('email', None) new_email = kwargs.get('new_email', None) if not email: raise TypeError( 'Expected an email address to unsubscribe, but received None.') email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return sailthru_parms = { "id": email, "optout_email": "all", "fields": { "optout_email": 1 } } # If we have a new email address to change to, do that as well if new_email: sailthru_parms["keys"] = {"email": new_email} sailthru_parms["fields"]["keys"] = 1 sailthru_parms["keysconflict"] = "merge" try: sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) sailthru_response = sailthru_client.api_post("user", sailthru_parms) except SailthruClientError as exc: error_msg = "Exception attempting to opt-out user {} from Sailthru - {}".format( email, text_type(exc)) log.error(error_msg) raise Exception(error_msg) if not sailthru_response.is_ok(): error = sailthru_response.get_error() error_msg = "Error attempting to opt-out user {} from Sailthru - {}".format( email, error.get_message()) log.error(error_msg) raise Exception(error_msg)
def email_marketing_user_field_changed(sender, user=None, table=None, setting=None, old_value=None, new_value=None, **kwargs): # pylint: disable=unused-argument """ Update a single user/profile field Args: sender: Not used user: The user object for the user being changed table: The name of the table being updated setting: The name of the setting being updated old_value: Prior value new_value: New value kwargs: Not used """ # ignore anonymous users if user.is_anonymous(): return # ignore anything but User or Profile table if table != 'auth_user' and table != 'auth_userprofile': return # ignore anything not in list of fields to handle if setting in CHANGED_FIELDNAMES: # skip if not enabled # the check has to be here rather than at the start of the method to avoid # accessing the config during migration 0001_date__add_ecommerce_service_user email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # perform update asynchronously, flag if activation update_user.delay(_create_sailthru_user_vars(user, user.profile), user.email, site=_get_current_site(), new_user=False, activation=(setting == 'is_active') and new_value is True) elif setting == 'email': # email update is special case email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return update_user_email.delay(user.email, old_value)
def force_unsubscribe_all(sender, **kwargs): # pylint: disable=unused-argument """ Synchronously(!) unsubscribes the given user from all Sailthru email lists. In the future this could be moved to a Celery task, however this is currently only used as part of user retirement, where we need a very reliable indication of success or failure. Args: email: Email address to unsubscribe new_email (optional): Email address to change 3rd party services to for this user (used in retirement to clear personal information from the service) Returns: None """ email = kwargs.get('email', None) new_email = kwargs.get('new_email', None) if not email: raise TypeError('Expected an email address to unsubscribe, but received None.') email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return sailthru_parms = { "id": email, "optout_email": "all", "fields": {"optout_email": 1} } # If we have a new email address to change to, do that as well if new_email: sailthru_parms["keys"] = { "email": new_email } sailthru_parms["fields"]["keys"] = 1 sailthru_parms["keysconflict"] = "merge" try: sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) sailthru_response = sailthru_client.api_post("user", sailthru_parms) except SailthruClientError as exc: error_msg = "Exception attempting to opt-out user {} from Sailthru - {}".format(email, text_type(exc)) log.error(error_msg) raise Exception(error_msg) if not sailthru_response.is_ok(): error = sailthru_response.get_error() error_msg = "Error attempting to opt-out user {} from Sailthru - {}".format(email, error.get_message()) log.error(error_msg) raise Exception(error_msg)
def add_email_marketing_cookies(sender, response=None, user=None, **kwargs): # pylint: disable=unused-argument """ Signal function for adding any cookies needed for email marketing Args: response: http response object user: The user object for the user being changed Returns: response: http response object with cookie added """ email_config = EmailMarketingConfiguration.current() 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", post_parms) except SailthruClientError as exc: log.error("Exception attempting to obtain cookie from Sailthru: %s", unicode(exc)) return response if sailthru_response.is_ok(): if 'keys' in sailthru_response.json and 'cookie' in sailthru_response.json['keys']: cookie = sailthru_response.json['keys']['cookie'] response.set_cookie( 'sailthru_hid', cookie, max_age=365 * 24 * 60 * 60 # set for 1 year ) else: log.error("No cookie returned attempting to obtain cookie from Sailthru for %s", user.email) else: error = sailthru_response.get_error() log.error("Error attempting to obtain cookie from Sailthru: %s", error.get_message()) return response
def update_user(self, sailthru_vars, email, new_user=False, activation=False): """ Adds/updates Sailthru profile information for a user. Args: sailthru_vars(dict): User profile information to pass as 'vars' to Sailthru email(str): User email address new_user(boolean): True if new registration activation(boolean): True if activation request Returns: None """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) try: sailthru_response = sailthru_client.api_post("user", _create_sailthru_user_parm(sailthru_vars, email, new_user, email_config)) except SailthruClientError as exc: log.error("Exception attempting to add/update user %s in Sailthru - %s", email, unicode(exc)) raise self.retry(exc=exc, countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) if not sailthru_response.is_ok(): error = sailthru_response.get_error() # put out error and schedule retry log.error("Error attempting to add/update user in Sailthru: %s", error.get_message()) raise self.retry(countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) # if activating user, send welcome email if activation and email_config.sailthru_activation_template: try: sailthru_response = sailthru_client.api_post("send", {"email": email, "template": email_config.sailthru_activation_template}) except SailthruClientError as exc: log.error("Exception attempting to send welcome email to user %s in Sailthru - %s", email, unicode(exc)) raise self.retry(exc=exc, countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) if not sailthru_response.is_ok(): error = sailthru_response.get_error() # probably a disabled template, just put out error message log.error("Error attempting to send welcome email to user in Sailthru: %s", error.get_message())
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 enroll/unenroll/purchase events """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled or not event or not user or not mode or not course_id: return 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 email_marketing_register_user(sender, user=None, profile=None, **kwargs): # pylint: disable=unused-argument """ Called after user created and saved Args: sender: Not used user: The user object for the user being changed profile: The user profile for the user being changed kwargs: Not used """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # ignore anonymous users if user.is_anonymous(): return # perform update asynchronously update_user.delay(user.username, new_user=True)
def get_email_cookies_via_sailthru(self, user_email, post_parms): """ Adds/updates Sailthru cookie information for a new user. Args: post_parms(dict): User profile information to pass as 'vars' to Sailthru Returns: cookie(str): cookie fetched from Sailthru """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return None try: sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) log.info( u'Sending to Sailthru the user interest cookie [%s] for user [%s]', post_parms.get('cookies', ''), user_email) sailthru_response = sailthru_client.api_post("user", post_parms) except SailthruClientError as exc: log.error(u"Exception attempting to obtain cookie from Sailthru: %s", six.text_type(exc)) raise SailthruClientError if sailthru_response.is_ok(): if 'keys' in sailthru_response.json and 'cookie' in sailthru_response.json[ 'keys']: cookie = sailthru_response.json['keys']['cookie'] return cookie else: log.error( u"No cookie returned attempting to obtain cookie from Sailthru for %s", user_email) else: error = sailthru_response.get_error() # generally invalid email address log.info(u"Error attempting to obtain cookie from Sailthru: %s", error.get_message()) return None
def add_email_marketing_cookies(sender, response=None, user=None, **kwargs): # pylint: disable=unused-argument """ Signal function for adding any cookies needed for email marketing Args: response: http response object user: The user object for the user being changed Returns: response: http response object with cookie added """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return response 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")}}) except SailthruClientError as exc: log.error("Exception attempting to obtain cookie from Sailthru: %s", unicode(exc)) return response if sailthru_response.is_ok(): if 'keys' in sailthru_response.json and 'cookie' in sailthru_response.json['keys']: cookie = sailthru_response.json['keys']['cookie'] response.set_cookie( 'sailthru_hid', cookie, max_age=365 * 24 * 60 * 60 # set for 1 year ) else: log.error("No cookie returned attempting to obtain cookie from Sailthru for %s", user.email) else: error = sailthru_response.get_error() log.error("Error attempting to obtain cookie from Sailthru: %s", error.get_message()) return response
def update_course_enrollment(self, email, course_key, mode, site=None): """Adds/updates Sailthru when a user adds to cart/purchases/upgrades a course Args: email: email address of enrolled user course_key: course key of course mode: mode user is enrolled in site: site where user enrolled Returns: None """ # do not add user if registered at a white label site if not is_default_site(site): return course_url = build_course_url(course_key) config = EmailMarketingConfiguration.current() try: sailthru_client = SailthruClient(config.sailthru_key, config.sailthru_secret) except: return send_template = config.sailthru_enroll_template cost_in_cents = 0 if not update_unenrolled_list(sailthru_client, email, course_url, False): schedule_retry(self, config) course_data = _get_course_content(course_key, course_url, sailthru_client, config) item = _build_purchase_item(course_key, course_url, cost_in_cents, mode, course_data) options = {} if send_template: options['send_template'] = send_template if not _record_purchase(sailthru_client, email, item, options): schedule_retry(self, config)
def email_marketing_register_user(sender, user=None, profile=None, **kwargs): # pylint: disable=unused-argument """ Called after user created and saved Args: sender: Not used user: The user object for the user being changed profile: The user profile for the user being changed kwargs: Not used """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # ignore anonymous users if user.is_anonymous(): return # perform update asynchronously update_user.delay(_create_sailthru_user_vars(user, user.profile), user.email, new_user=True)
def force_unsubscribe_all(sender, **kwargs): # pylint: disable=unused-argument """ Synchronously(!) unsubscribes the given user from all Sailthru email lists. In the future this could be moved to a Celery task, however this is currently only used as part of user retirement, where we need a very reliable indication of success or failure. Args: user(User): Django model of type returned from get_user_model() Returns: None """ user = kwargs.get('user', None) if not user: raise TypeError('Expected a User type, but received None.') email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return sailthru_parms = {"id": user.email, "keys": {"optout_email": "all"}} try: sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) sailthru_response = sailthru_client.api_post("user", sailthru_parms) except SailthruClientError as exc: error_msg = "Exception attempting to opt-out user %s from Sailthru - %s" % ( user.email, text_type(exc)) log.error(error_msg) raise Exception(error_msg) if not sailthru_response.is_ok(): error = sailthru_response.get_error() error_msg = "Error attempting to opt-out user %s from Sailthru - %s" % ( user.email, error.get_message()) log.error(error_msg) raise Exception(error_msg)
def email_marketing_register_user(sender, user, registration, **kwargs): # pylint: disable=unused-argument """ Called after user created and saved Args: sender: Not used user: The user object for the user being changed registration: The user registration profile to activate user account kwargs: Not used """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # ignore anonymous users if user.is_anonymous: return # perform update asynchronously update_user.delay(_create_sailthru_user_vars(user, user.profile, registration=registration), user.email, site=_get_current_site(), new_user=True)
def get_email_cookies_via_sailthru(self, user_email, post_parms): """ Adds/updates Sailthru cookie information for a new user. Args: post_parms(dict): User profile information to pass as 'vars' to Sailthru Returns: cookie(str): cookie fetched from Sailthru """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return None try: sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) log.info( 'Sending to Sailthru the user interest cookie [%s] for user [%s]', post_parms.get('cookies', ''), user_email ) sailthru_response = sailthru_client.api_post("user", post_parms) except SailthruClientError as exc: log.error("Exception attempting to obtain cookie from Sailthru: %s", unicode(exc)) raise SailthruClientError if sailthru_response.is_ok(): if 'keys' in sailthru_response.json and 'cookie' in sailthru_response.json['keys']: cookie = sailthru_response.json['keys']['cookie'] return cookie else: log.error("No cookie returned attempting to obtain cookie from Sailthru for %s", user_email) else: error = sailthru_response.get_error() # generally invalid email address log.info("Error attempting to obtain cookie from Sailthru: %s", error.get_message()) return None
def update_course_enrollment(self, email, course_url, event, mode, course_id=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(str): enroll mode (audit, verification, ...) unit_cost: cost if purchase event course_id(str): course run 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 or honor) EnrollStatusChange.unenroll An unenroll EnrollStatusChange.upgrade_start A paid upgrade added to cart - ignored EnrollStatusChange.upgrade_complete A paid upgrade purchase complete - ignored EnrollStatusChange.paid_start A non-free course added to cart - ignored EnrollStatusChange.paid_complete A non-free course purchase complete - ignored """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # Use event type to figure out processing required unenroll = False send_template = None cost_in_cents = 0 if event == EnrollStatusChange.enroll: send_template = email_config.sailthru_enroll_template # set cost 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 else: # All purchase events should be handled by ecommerce, so ignore return sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) # update the "unenrolled" course array in the user record on Sailthru 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 course_data = {} if email_config.sailthru_get_tags_from_sailthru: course_data = _get_course_content(course_url, sailthru_client, email_config) # build item description item = _build_purchase_item(course_id, course_url, cost_in_cents, mode, course_data) # build purchase api options list options = {} # add appropriate send template if send_template: options['send_template'] = send_template if not _record_purchase(sailthru_client, email, item, message_id, options): raise self.retry(countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries)
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 update_user(self, sailthru_vars, email, new_user=False, activation=False): """ Adds/updates Sailthru profile information for a user. Args: sailthru_vars(dict): User profile information to pass as 'vars' to Sailthru email(str): User email address new_user(boolean): True if new registration activation(boolean): True if activation request Returns: None """ email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return sailthru_client = SailthruClient(email_config.sailthru_key, email_config.sailthru_secret) try: sailthru_response = sailthru_client.api_post( "user", _create_sailthru_user_parm(sailthru_vars, email, new_user, email_config)) except SailthruClientError as exc: log.error( "Exception attempting to add/update user %s in Sailthru - %s", email, unicode(exc)) raise self.retry(exc=exc, countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) if not sailthru_response.is_ok(): error = sailthru_response.get_error() # put out error and schedule retry log.error("Error attempting to add/update user in Sailthru: %s", error.get_message()) raise self.retry(countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) # if activating user, send welcome email if activation and email_config.sailthru_activation_template: try: sailthru_response = sailthru_client.api_post( "send", { "email": email, "template": email_config.sailthru_activation_template }) except SailthruClientError as exc: log.error( "Exception attempting to send welcome email to user %s in Sailthru - %s", email, unicode(exc)) raise self.retry(exc=exc, countdown=email_config.sailthru_retry_interval, max_retries=email_config.sailthru_max_retries) if not sailthru_response.is_ok(): error = sailthru_response.get_error() # probably a disabled template, just put out error message log.error( "Error attempting to send welcome email to user in Sailthru: %s", error.get_message())
def email_marketing_user_field_changed(sender, user=None, table=None, setting=None, old_value=None, new_value=None, **kwargs): # pylint: disable=unused-argument """ Update a single user/profile field Args: sender: Not used user: The user object for the user being changed table: The name of the table being updated setting: The name of the setting being updated old_value: Prior value new_value: New value kwargs: Not used """ # ignore anonymous users if user.is_anonymous: return # ignore anything but User, Profile or UserPreference tables if table not in {'auth_user', 'auth_userprofile', 'user_api_userpreference'}: return # ignore anything not in list of fields to handle if setting in CHANGED_FIELDNAMES: # skip if not enabled # the check has to be here rather than at the start of the method to avoid # accessing the config during migration 0001_date__add_ecommerce_service_user email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # Is the status of the user account changing to active? is_activation = (setting == 'is_active') and new_value is True # Is this change in the context of an SSO-initiated registration? third_party_provider = None if third_party_auth.is_enabled(): running_pipeline = third_party_auth.pipeline.get(crum.get_current_request()) if running_pipeline: third_party_provider = third_party_auth.provider.Registry.get_from_pipeline(running_pipeline) # Send a welcome email if the user account is being activated # and we are not in a SSO registration flow whose associated # identity provider is configured to allow for the sending # of a welcome email. send_welcome_email = is_activation and ( third_party_provider is None or third_party_provider.send_welcome_email ) # set the activation flag when the user is marked as activated update_user.delay(_create_sailthru_user_vars(user, user.profile), user.email, site=_get_current_site(), new_user=False, activation=send_welcome_email) elif setting == 'email': # email update is special case email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return update_user_email.delay(user.email, old_value)
def email_marketing_user_field_changed(sender, user=None, table=None, setting=None, old_value=None, new_value=None, **kwargs): # pylint: disable=unused-argument """ Update a single user/profile field Args: sender: Not used user: The user object for the user being changed table: The name of the table being updated setting: The name of the setting being updated old_value: Prior value new_value: New value kwargs: Not used """ # ignore anonymous users if user.is_anonymous: return # ignore anything but User, Profile or UserPreference tables if table not in { 'auth_user', 'auth_userprofile', 'user_api_userpreference' }: return # ignore anything not in list of fields to handle if setting in CHANGED_FIELDNAMES: # skip if not enabled # the check has to be here rather than at the start of the method to avoid # accessing the config during migration 0001_date__add_ecommerce_service_user email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return # Is the status of the user account changing to active? is_activation = (setting == 'is_active') and new_value is True # Is this change in the context of an SSO-initiated registration? third_party_provider = None if third_party_auth.is_enabled(): running_pipeline = third_party_auth.pipeline.get( crum.get_current_request()) if running_pipeline: third_party_provider = third_party_auth.provider.Registry.get_from_pipeline( running_pipeline) # Send a welcome email if the user account is being activated # and we are not in a SSO registration flow whose associated # identity provider is configured to allow for the sending # of a welcome email. send_welcome_email = is_activation and ( third_party_provider is None or third_party_provider.send_welcome_email) # set the activation flag when the user is marked as activated update_user.delay(_create_sailthru_user_vars(user, user.profile), user.email, site=_get_current_site(), new_user=False, activation=send_welcome_email) elif setting == 'email': # email update is special case email_config = EmailMarketingConfiguration.current() if not email_config.enabled: return update_user_email.delay(user.email, old_value)