def handle(self, *args, **options): # Mozillians has some settings that must be set, or it'll just skip talking to Basket. # The tasks module looks these up at import time, and might get BASKET_API_KEY # from the environment rather than settings if it's there, so look directly at # what values the tasks module ended up with. required_settings = ['BASKET_API_KEY', 'BASKET_NDA_NEWSLETTER', 'BASKET_URL', 'BASKET_VOUCHED_NEWSLETTER'] if not all([getattr(tasks, setting, False) for setting in required_settings]): # At least one is missing. Show what's set and what's missing: for setting in required_settings: val = getattr(tasks, setting, False) if not val: self.stdout.write('** %s is not set and must be **\n' % setting) else: self.stdout.write('%s=%s\n' % (setting, val)) raise CommandError('ERROR: Basket is not enabled with current settings') email = '*****@*****.**' try: lookup_user(email=email) except BasketException as exception: if exception.code != BASKET_UNKNOWN_EMAIL: raise CommandError('ERROR: Error querying basket: %s' % exception) # basket.lookup_user will have queried Exact Target for the user's subscriptions, # or whether the user exists, and failed if it couldn't get to Exact Target. Since we got # this far, we know things are okay all the way through to Exact Target. self.stdout.write('Basket is working okay.\n')
def test_lookup_user_email(self, mock_request): """Calling lookup_user with email and api key should succeed.""" api_key = 'There is only XUL!' email = '*****@*****.**' lookup_user(email=email, api_key=api_key) mock_request.assert_called_with('get', 'lookup-user', params={'email': email}, headers={'x-api-key': api_key})
def test_lookup_user_email_setting(self, mock_request): """Calling lookup_user with email and api key setting should succeed.""" api_key = 'There is only XUL!' email = '*****@*****.**' with patch('basket.base.BASKET_API_KEY', api_key): lookup_user(email=email) mock_request.assert_called_with('get', 'lookup-user', params={'email': email}, headers={'x-api-key': api_key})
def unsubscribe_from_basket_task(email, basket_token): """Remove from Basket Task. This task unsubscribes a user from the Mozillians newsletter. The task retries on failure at most BASKET_TASK_MAX_RETRIES times and if it finally doesn't complete successfully, it emails the settings.BASKET_MANAGERS with details. """ # IMPLEMENTATION NOTE: # # This task might run AFTER the User has been deleted, so it can't # look anything up about the user locally. It has to make do # with the email and token passed in. if not BASKET_ENABLED: return try: if not basket_token: # We don't have this user's token yet, and we need it to # unsubscribe. Ask basket for it basket_token = basket.lookup_user(email=email)['token'] basket.unsubscribe(basket_token, email, newsletters=settings.BASKET_NEWSLETTER) except (requests.exceptions.RequestException, basket.BasketException) as exception: try: unsubscribe_from_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('unsubscribe', email, exception.message)
def update_basket_token_task(instance_id): """Update basket token task This task looks up user email in basket and deletes the current basket_token if email doesn't exist in basket or updates it if it exists but it's not the same. """ from models import UserProfile try: instance = UserProfile.objects.get(pk=instance_id) except UserProfile.DoesNotExist: instance = None if not BASKET_ENABLED or not instance or not waffle.switch_is_active( 'BASKET_SWITCH_ENABLED'): return try: response = basket.lookup_user(email=instance.email) token = response['token'] except basket.BasketException as exception: if exception.code == basket.errors.BASKET_UNKNOWN_EMAIL: UserProfile.objects.filter(pk=instance.pk).update(basket_token='') return update_basket_token_task.retry() except MaxRetriesExceededError: _email_basket_managers('update token', instance.email, exception.message) except requests.exceptions.RequestException: update_basket_token_task.retry() if not token: token = '' UserProfile.objects.filter(pk=instance.pk).update(basket_token=token)
def update_basket_token_task(instance_id): """Update basket token task This task looks up user email in basket and deletes the current basket_token if email doesn't exist in basket or updates it if it exists but it's not the same. """ from models import UserProfile try: instance = UserProfile.objects.get(pk=instance_id) except UserProfile.DoesNotExist: instance = None if not BASKET_ENABLED or not instance or not waffle.switch_is_active('BASKET_SWITCH_ENABLED'): return try: token = basket.lookup_user(email=instance.email)['token'] UserProfile.objects.filter(pk=instance.pk).update(basket_token=token) except basket.BasketException as exception: if exception.code == basket.errors.BASKET_UNKNOWN_EMAIL: UserProfile.objects.filter(pk=instance.pk).update(basket_token='') return update_basket_token_task.retry() except MaxRetriesExceededError: _email_basket_managers('update token', instance.email, exception.message) except requests.exceptions.RequestException: update_basket_token_task.retry()
def get_subscription_details(email): subscription_details = None try: subscription_details = basket.lookup_user(email=email, api_key=constance.config.BASKET_API_KEY) except BasketException, e: if e.code == BASKET_UNKNOWN_EMAIL: # pass - unknown email is just a new subscriber pass
def get_subscription_details(email): subscription_details = None try: subscription_details = basket.lookup_user( email=email, api_key=constance.config.BASKET_API_KEY) except BasketException, e: if e.code == BASKET_UNKNOWN_EMAIL: # pass - unknown email is just a new subscriber pass
def lookup_user_task(self, email): """Task responsible for getting information about a user in basket.""" # We need to return always a dictionary for the next task result = {} try: result = basket.lookup_user(email=email) except MaxRetriesExceededError as exc: raise exc except basket.BasketException as exc: if not exc[0] == u'User not found': raise self.retry(exc=exc) result = exc.result return result
def lookup_basket_token(self): """ Query Basket for this user's token. If Basket doesn't find the user, returns None. If Basket does find the token, returns it. Otherwise, there must have been some error from the network or basket, and this method just lets that exception propagate so the caller can decide how best to handle it. (Does not update the token field on the UserProfile.) """ try: result = basket.lookup_user(email=self.user.email) except basket.BasketException as exception: if exception.code == basket.errors.BASKET_UNKNOWN_EMAIL: return None raise return result['token']
def sync_user_with_basket(user): """Syncronize a user with basket. Returns the user data in case of a successful sync. Returns `None` in case of an unsuccessful sync. This can happen if the user does not exist in basket yet. This raises an exception all other errors. """ try: data = basket.lookup_user(user.email) user.update(basket_token=data['token']) return data except Exception as exc: acceptable_errors = (basket.errors.BASKET_INVALID_EMAIL, basket.errors.BASKET_UNKNOWN_EMAIL) if getattr(exc, 'code', None) in acceptable_errors: return None else: raise
def sync_user_with_basket(user): """Syncronize a user with basket. Returns the user data in case of a successful sync. Returns `None` in case of an unsuccessful sync. This can happen if the user does not exist in basket yet. This raises an exception all other errors. """ try: data = basket.lookup_user(user.email) user.update(basket_token=data['token']) return data except Exception as exc: acceptable_errors = ( basket.errors.BASKET_INVALID_EMAIL, basket.errors.BASKET_UNKNOWN_EMAIL) if getattr(exc, 'code', None) in acceptable_errors: return None else: raise
def test_lookup_user_no_api_key(self, mock_request): """Calling lookup_user with email and no api key raises an exception.""" with self.assertRaises(BasketException): lookup_user(email='*****@*****.**') self.assertFalse(mock_request.called)
def update_basket_task(instance_id): """Update Basket Task. This task subscribes a user to Basket, if not already subscribed and then updates his data on the Phonebook DataExtension. The task retries on failure at most BASKET_TASK_MAX_RETRIES times and if it finally doesn't complete successfully, it emails the settings.BASKET_MANAGERS with details. """ # This task is triggered by a post-save signal on UserProfile, so # we can't save() on UserProfile again while in here - if we were # running with CELERY_EAGER, we'd enter an infinite recursion until # Python died. from models import UserProfile instance = UserProfile.objects.get(pk=instance_id) if not BASKET_ENABLED or not instance.is_vouched: return email = instance.user.email token = instance.basket_token if not token: # no token yet, they're probably not subscribed, so subscribe them. # pass sync='Y' so we wait for it to complete and get back the token. try: retval = basket.subscribe(email, [settings.BASKET_NEWSLETTER], sync="Y", trigger_welcome="N") except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers("subscribe", instance.user.email, exception.message) return # Remember the token instance.basket_token = retval["token"] token = retval["token"] # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update(basket_token=token) else: # They were already subscribed. See what email address they # have in exact target. If it has changed, we'll need to # unsubscribe the old address and subscribe the new one, # and save the new token. # This'll also return their subscriptions, so we can transfer them # to the new address if we need to. try: result = basket.lookup_user(token=token) except basket.BasketException as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): msg = exception.message _email_basket_managers("update_phonebook", token, msg) return old_email = result["email"] if old_email != email: try: # We do the new subscribe first, then the unsubscribe, so we don't # risk losing their subscriptions if the subscribe fails. # Subscribe to all the same newsletters. # Pass sync='Y' so we get back the new token right away subscribe_result = basket.subscribe(email, [settings.BASKET_NEWSLETTER], sync="Y", trigger_welcome="N") # unsub all from the old token basket.unsubscribe(token=token, email=old_email, newsletters=[settings.BASKET_NEWSLETTER], optout="Y") except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers("subscribe", email, exception.message) return # FIXME: We should also remove their previous phonebook record from Exact Target, but # basket doesn't have a custom API to do that. (basket never deletes anything.) # That was all successful. Update the token. instance.basket_token = subscribe_result["token"] token = subscribe_result["token"] # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update(basket_token=token) GroupMembership = get_model("groups", "GroupMembership") Group = get_model("groups", "Group") data = {} # What groups is the user in? user_group_pks = instance.groups.filter(groupmembership__status=GroupMembership.MEMBER).values_list("pk", flat=True) for group in Group.objects.filter(functional_area=True): name = group.name.upper().replace(" ", "_") data[name] = "Y" if group.id in user_group_pks else "N" # User location if known if instance.geo_country: data["country"] = instance.geo_country.code if instance.geo_city: data["city"] = instance.geo_city.name # We have a token, proceed with the update try: basket.request("post", "custom_update_phonebook", token=token, data=data) except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers("update_phonebook", email, exception.message)
def test_lookup_user_no_args(self, mock_request): """Calling lookup_user with no email or token raises an exception.""" with self.assertRaises(BasketException): lookup_user() self.assertFalse(mock_request.called)
def update_basket_task(instance_id, newsletters=[]): """Update Basket Task. This task subscribes a user to Basket, if not already subscribed and then updates his data on the Phonebook DataExtension. The task retries on failure at most BASKET_TASK_MAX_RETRIES times and if it finally doesn't complete successfully, it emails the settings.BASKET_MANAGERS with details. """ # This task is triggered by a post-save signal on UserProfile, so # we can't save() on UserProfile again while in here - if we were # running with CELERY_EAGER, we'd enter an infinite recursion until # Python died. from models import UserProfile try: instance = UserProfile.objects.get(pk=instance_id) except UserProfile.DoesNotExist: instance = None if (not BASKET_ENABLED or not instance or not newsletters or not waffle.switch_is_active('BASKET_SWITCH_ENABLED')): return email = instance.user.email token = instance.basket_token newsletters_to_subscribe = [] if token: # They were already subscribed. See what email address they # have in exact target. If it has changed, we'll need to # unsubscribe the old address and subscribe the new one, # and save the new token. # This'll also return their subscriptions, so we can transfer them # to the new address if we need to. try: result = basket.lookup_user(token=token) except basket.BasketException as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): msg = exception.message _email_basket_managers('update_phonebook', token, msg) return # Check if the users needs to be subscribed to additional newsletters. newsletters_to_subscribe = [nl for nl in newsletters if nl not in result['newsletters']] old_email = result['email'] if old_email != email: try: # We do the new subscribe first, then the unsubscribe, so we don't # risk losing their subscriptions if the subscribe fails. # Subscribe to all the same newsletters. # Pass sync='Y' so we get back the new token right away subscribe_result = basket.subscribe( email, result['newsletters'], sync='Y', trigger_welcome='N', ) # unsubscribe all from the old token basket.unsubscribe(token=token, email=old_email, optout=True) except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('subscribe', email, exception.message) return # That was all successful. Update the token. token = subscribe_result['token'] # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update(basket_token=token) if not token or newsletters_to_subscribe: # There is no token or the user is missing newsletters, # try to subscribe the user to basket. subscribe_to = newsletters_to_subscribe or newsletters try: retval = basket.subscribe( email, subscribe_to, sync='Y', trigger_welcome='N' ) except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('subscribe', instance.user.email, exception.message) return # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update(basket_token=retval['token'])
def test_lookup_user_token(self, mock_request): """Calling lookup_user with a token should not require an API key.""" lookup_user(token='TOKEN') mock_request.assert_called_with('get', 'lookup-user', params={'token': 'TOKEN'})
def update_basket_task(instance_id): """Update Basket Task. This task subscribes a user to Basket, if not already subscribed and then updates his data on the Phonebook DataExtension. The task retries on failure at most BASKET_TASK_MAX_RETRIES times and if it finally doesn't complete successfully, it emails the settings.BASKET_MANAGERS with details. """ # This task is triggered by a post-save signal on UserProfile, so # we can't save() on UserProfile again while in here - if we were # running with CELERY_EAGER, we'd enter an infinite recursion until # Python died. from models import UserProfile try: instance = UserProfile.objects.get(pk=instance_id) except UserProfile.DoesNotExist: instance = None if not BASKET_ENABLED or not instance or not instance.is_vouched: return email = instance.user.email token = instance.basket_token if not token: # no token yet, they're probably not subscribed, so subscribe them. # pass sync='Y' so we wait for it to complete and get back the token. try: retval = basket.subscribe( email, [settings.BASKET_NEWSLETTER], sync='Y', trigger_welcome='N' ) except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('subscribe', instance.user.email, exception.message) return # Remember the token instance.basket_token = retval['token'] token = retval['token'] # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update(basket_token=token) else: # They were already subscribed. See what email address they # have in exact target. If it has changed, we'll need to # unsubscribe the old address and subscribe the new one, # and save the new token. # This'll also return their subscriptions, so we can transfer them # to the new address if we need to. try: result = basket.lookup_user(token=token) except basket.BasketException as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): msg = exception.message _email_basket_managers('update_phonebook', token, msg) return old_email = result['email'] if old_email != email: try: # We do the new subscribe first, then the unsubscribe, so we don't # risk losing their subscriptions if the subscribe fails. # Subscribe to all the same newsletters. # Pass sync='Y' so we get back the new token right away subscribe_result = basket.subscribe( email, [settings.BASKET_NEWSLETTER], sync='Y', trigger_welcome='N', ) # unsub all from the old token basket.unsubscribe(token=token, email=old_email, newsletters=[settings.BASKET_NEWSLETTER], optout='Y') except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('subscribe', email, exception.message) return # FIXME: We should also remove their previous phonebook record from Exact Target, but # basket doesn't have a custom API to do that. (basket never deletes anything.) # That was all successful. Update the token. instance.basket_token = subscribe_result['token'] token = subscribe_result['token'] # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update(basket_token=token)
def update_basket_task(instance_id): """Update Basket Task. This task subscribes a user to Basket, if not already subscribed and then updates his data on the Phonebook DataExtension. The task retries on failure at most BASKET_TASK_MAX_RETRIES times and if it finally doesn't complete successfully, it emails the settings.BASKET_MANAGERS with details. """ # This task is triggered by a post-save signal on UserProfile, so # we can't save() on UserProfile again while in here - if we were # running with CELERY_EAGER, we'd enter an infinite recursion until # Python died. from models import UserProfile instance = UserProfile.objects.get(pk=instance_id) if not BASKET_ENABLED or not instance.is_vouched: return email = instance.user.email token = instance.basket_token if not token: # no token yet, they're probably not subscribed, so subscribe them. # pass sync='Y' so we wait for it to complete and get back the token. try: retval = basket.subscribe(email, [settings.BASKET_NEWSLETTER], sync='Y', trigger_welcome='N') except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('subscribe', instance.user.email, exception.message) return # Remember the token instance.basket_token = retval['token'] token = retval['token'] # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update(basket_token=token) else: # They were already subscribed. See what email address they # have in exact target. If it has changed, we'll need to # unsubscribe the old address and subscribe the new one, # and save the new token. # This'll also return their subscriptions, so we can transfer them # to the new address if we need to. try: result = basket.lookup_user(token=token) except basket.BasketException as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): msg = exception.message _email_basket_managers('update_phonebook', token, msg) return old_email = result['email'] if old_email != email: try: # We do the new subscribe first, then the unsubscribe, so we don't # risk losing their subscriptions if the subscribe fails. # Subscribe to all the same newsletters. # Pass sync='Y' so we get back the new token right away subscribe_result = basket.subscribe( email, [settings.BASKET_NEWSLETTER], sync='Y', trigger_welcome='N', ) # unsub all from the old token basket.unsubscribe(token=token, email=old_email, newsletters=[settings.BASKET_NEWSLETTER], optout='Y') except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('subscribe', email, exception.message) return # FIXME: We should also remove their previous phonebook record from Exact Target, but # basket doesn't have a custom API to do that. (basket never deletes anything.) # That was all successful. Update the token. instance.basket_token = subscribe_result['token'] token = subscribe_result['token'] # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update( basket_token=token) GroupMembership = get_model('groups', 'GroupMembership') Group = get_model('groups', 'Group') data = {} # What groups is the user in? user_group_pks = (instance.groups.filter( groupmembership__status=GroupMembership.MEMBER).values_list('pk', flat=True)) for group in Group.objects.filter(functional_area=True): name = group.name.upper().replace(' ', '_') data[name] = 'Y' if group.id in user_group_pks else 'N' # User location if known if instance.geo_country: data['country'] = instance.geo_country.code if instance.geo_city: data['city'] = instance.geo_city.name # We have a token, proceed with the update try: basket.request('post', 'custom_update_phonebook', token=token, data=data) except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('update_phonebook', email, exception.message)
def update_basket_task(instance_id, newsletters=[]): """Update Basket Task. This task subscribes a user to Basket, if not already subscribed and then updates his data on the Phonebook DataExtension. The task retries on failure at most BASKET_TASK_MAX_RETRIES times and if it finally doesn't complete successfully, it emails the settings.BASKET_MANAGERS with details. """ # This task is triggered by a post-save signal on UserProfile, so # we can't save() on UserProfile again while in here - if we were # running with CELERY_EAGER, we'd enter an infinite recursion until # Python died. from models import UserProfile try: instance = UserProfile.objects.get(pk=instance_id) except UserProfile.DoesNotExist: instance = None if (not BASKET_ENABLED or not instance or not newsletters or not waffle.switch_is_active('BASKET_SWITCH_ENABLED')): return email = instance.user.email token = instance.basket_token newsletters_to_subscribe = [] if token: # They were already subscribed. See what email address they # have in exact target. If it has changed, we'll need to # unsubscribe the old address and subscribe the new one, # and save the new token. # This'll also return their subscriptions, so we can transfer them # to the new address if we need to. try: result = basket.lookup_user(token=token) except basket.BasketException as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): msg = exception.message _email_basket_managers('update_phonebook', token, msg) return # Check if the users needs to be subscribed to additional newsletters. newsletters_to_subscribe = [ nl for nl in newsletters if nl not in result['newsletters'] ] old_email = result['email'] if old_email != email: try: # We do the new subscribe first, then the unsubscribe, so we don't # risk losing their subscriptions if the subscribe fails. # Subscribe to all the same newsletters. # Pass sync='Y' so we get back the new token right away subscribe_result = basket.subscribe( email, result['newsletters'], sync='Y', trigger_welcome='N', ) # unsubscribe all from the old token basket.unsubscribe(token=token, email=old_email, optout=True) except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('subscribe', email, exception.message) return # That was all successful. Update the token. token = subscribe_result['token'] # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update( basket_token=token) if not token or newsletters_to_subscribe: # There is no token or the user is missing newsletters, # try to subscribe the user to basket. subscribe_to = newsletters_to_subscribe or newsletters try: retval = basket.subscribe(email, subscribe_to, sync='Y', trigger_welcome='N') except (requests.exceptions.RequestException, basket.BasketException) as exception: try: update_basket_task.retry() except (MaxRetriesExceededError, basket.BasketException): _email_basket_managers('subscribe', instance.user.email, exception.message) return # Don't call .save() on a userprofile from here, it would invoke us again # via the post-save signal, which would be pointless. UserProfile.objects.filter(pk=instance.pk).update( basket_token=retval['token'])