tasks.py 9.16 KB
Newer Older
1 2 3 4 5
"""
This file contains celery tasks for email marketing signal handler.
"""
import logging
import time
6
from datetime import datetime, timedelta
7 8

from celery import task
9
from django.core.cache import cache
10
from django.conf import settings
11 12 13 14 15 16

from email_marketing.models import EmailMarketingConfiguration
from sailthru.sailthru_client import SailthruClient
from sailthru.sailthru_error import SailthruClientError

log = logging.getLogger(__name__)
17
SAILTHRU_LIST_CACHE_KEY = "email.marketing.cache"
18 19 20 21


# pylint: disable=not-callable
@task(bind=True, default_retry_delay=3600, max_retries=24)
22
def update_user(self, sailthru_vars, email, site=None, new_user=False, activation=False):
23 24 25
    """
    Adds/updates Sailthru profile information for a user.
     Args:
26 27 28 29
        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
30 31 32 33 34 35 36 37 38 39
    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",
40 41 42
                                                     _create_email_user_param(sailthru_vars, sailthru_client,
                                                                              email, new_user, email_config,
                                                                              site=site))
43

44
    except SailthruClientError as exc:
45
        log.error("Exception attempting to add/update user %s in Sailthru - %s", email, unicode(exc))
46 47 48 49 50 51 52
        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())
53 54 55 56
        if _retryable_sailthru_error(error):
            raise self.retry(countdown=email_config.sailthru_retry_interval,
                             max_retries=email_config.sailthru_max_retries)
        return
57 58 59

    # if activating user, send welcome email
    if activation and email_config.sailthru_activation_template:
60
        scheduled_datetime = datetime.utcnow() + timedelta(seconds=email_config.welcome_email_send_delay)
61
        try:
62 63 64 65 66 67 68 69
            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')
                }
            )
70
        except SailthruClientError as exc:
71
            log.error("Exception attempting to send welcome email to user %s in Sailthru - %s", email, unicode(exc))
72 73 74 75 76 77 78
            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())
79 80 81
            if _retryable_sailthru_error(error):
                raise self.retry(countdown=email_config.sailthru_retry_interval,
                                 max_retries=email_config.sailthru_max_retries)
82 83 84 85


# pylint: disable=not-callable
@task(bind=True, default_retry_delay=3600, max_retries=24)
86
def update_user_email(self, new_email, old_email):
87 88 89 90 91 92 93 94 95 96 97 98 99
    """
    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
100
    if new_email == old_email:
101 102
        return

103
    sailthru_parms = {"id": old_email, "key": "email", "keysconflict": "merge", "keys": {"email": new_email}}
104 105 106 107 108

    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:
109
        log.error("Exception attempting to update email for %s in Sailthru - %s", old_email, unicode(exc))
110 111 112 113 114 115 116
        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())
117 118 119
        if _retryable_sailthru_error(error):
            raise self.retry(countdown=email_config.sailthru_retry_interval,
                             max_retries=email_config.sailthru_max_retries)
120 121


122
def _create_email_user_param(sailthru_vars, sailthru_client, email, new_user, email_config, site=None):
123
    """
124
    Create sailthru user create/update parms
125
    """
126 127
    sailthru_user = {'id': email, 'key': 'email'}
    sailthru_user['vars'] = dict(sailthru_vars, last_changed_time=int(time.time()))
128 129

    # if new user add to list
130 131 132 133 134
    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}
135 136

    return sailthru_user
137 138


139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
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


232 233 234 235 236 237 238 239 240 241 242
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