Files
edx-platform/lms/djangoapps/email_marketing/tasks.py

243 lines
9.2 KiB
Python

"""
This file contains celery tasks for email marketing signal handler.
"""
import logging
import time
from datetime import datetime, timedelta
from celery import task
from django.core.cache import cache
from django.conf import settings
from email_marketing.models import EmailMarketingConfiguration
from sailthru.sailthru_client import SailthruClient
from sailthru.sailthru_error import SailthruClientError
log = logging.getLogger(__name__)
SAILTHRU_LIST_CACHE_KEY = "email.marketing.cache"
# pylint: disable=not-callable
@task(bind=True, default_retry_delay=3600, max_retries=24)
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)
# pylint: disable=not-callable
@task(bind=True, default_retry_delay=3600, max_retries=24)
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 _create_email_user_param(sailthru_vars, sailthru_client, email, new_user, email_config, site=None):
"""
Create sailthru user create/update parms
"""
sailthru_user = {'id': email, 'key': 'email'}
sailthru_user['vars'] = dict(sailthru_vars, last_changed_time=int(time.time()))
# if new user add to list
if new_user:
list_name = _get_or_create_user_list_for_site(
sailthru_client, site=site, default_list_name=email_config.sailthru_new_user_list
)
sailthru_user['lists'] = {list_name: 1} if list_name else {email_config.sailthru_new_user_list: 1}
return sailthru_user
def _get_or_create_user_list_for_site(sailthru_client, site=None, default_list_name=None):
"""
Get the user list name from cache if exists else create one and return the name,
callers of this function should perform the enabled check of email config.
:param: sailthru_client
:param: site
:param: default_list_name
:return: list name if exists or created else return None
"""
if site and site.get('id') != settings.SITE_ID:
list_name = site.get('domain', '').replace(".", "_") + "_user_list"
else:
list_name = default_list_name
sailthru_list = _get_or_create_user_list(sailthru_client, list_name)
return list_name if sailthru_list else default_list_name
def _get_or_create_user_list(sailthru_client, list_name):
"""
Get list from sailthru and return if list_name exists else create a new one
and return list data for all lists.
:param sailthru_client
:param list_name
:return sailthru list
"""
sailthru_list_cache = cache.get(SAILTHRU_LIST_CACHE_KEY)
is_cache_updated = False
if not sailthru_list_cache:
sailthru_list_cache = _get_list_from_email_marketing_provider(sailthru_client)
is_cache_updated = True
sailthru_list = sailthru_list_cache.get(list_name)
if not sailthru_list:
is_created = _create_user_list(sailthru_client, list_name)
if is_created:
sailthru_list_cache = _get_list_from_email_marketing_provider(sailthru_client)
is_cache_updated = True
sailthru_list = sailthru_list_cache.get(list_name)
if is_cache_updated:
cache.set(SAILTHRU_LIST_CACHE_KEY, sailthru_list_cache)
return sailthru_list
def _get_list_from_email_marketing_provider(sailthru_client):
"""
Get sailthru list
:param sailthru_client
:return dict of sailthru lists mapped by list name
"""
try:
sailthru_get_response = sailthru_client.api_get("list", {})
except SailthruClientError as exc:
log.error("Exception attempting to get list from Sailthru - %s", unicode(exc))
return {}
if not sailthru_get_response.is_ok():
error = sailthru_get_response.get_error()
log.info("Error attempting to read list record from Sailthru: %s", error.get_message())
return {}
list_map = dict()
for user_list in sailthru_get_response.json['lists']:
list_map[user_list.get('name')] = user_list
return list_map
def _create_user_list(sailthru_client, list_name):
"""
Create list in Sailthru
:param sailthru_client
:param list_name
:return boolean
"""
list_params = {'list': list_name, 'primary': 0, 'public_name': list_name}
try:
sailthru_response = sailthru_client.api_post("list", list_params)
except SailthruClientError as exc:
log.error("Exception attempting to list record for key %s in Sailthru - %s", list_name, unicode(exc))
return False
if not sailthru_response.is_ok():
error = sailthru_response.get_error()
log.error("Error attempting to create list in Sailthru: %s", error.get_message())
return False
return True
def _retryable_sailthru_error(error):
""" Return True if error should be retried.
9: Retryable internal error
43: Rate limiting response
others: Not retryable
See: https://getstarted.sailthru.com/new-for-developers-overview/api/api-response-errors/
"""
code = error.get_error_code()
return code == 9 or code == 43