def unsubscribe(request, token): data = request.POST.dict() data['token'] = token if data.get('optout', 'N') == 'Y': data['optout'] = True data['newsletters'] = ','.join(newsletter_slugs()) return update_user_task(request, UNSUBSCRIBE, data)
def unsubscribe(request, token): token = str(token) data = request.POST.dict() data["token"] = token if data.get("optout", "N") == "Y": data["optout"] = True data["newsletters"] = ",".join(newsletter_slugs()) return update_user_task(request, UNSUBSCRIBE, data)
def update_user_task(request, api_call_type, data=None, optin=False, sync=False): """Call the update_user task async with the right parameters. If sync==True, be sure to include the token in the response. Otherwise, basket can just do everything in the background. """ data = data or request.POST.dict() newsletters = parse_newsletters_csv(data.get("newsletters")) if newsletters: if api_call_type == SUBSCRIBE: all_newsletters = (newsletter_and_group_slugs() + get_transactional_message_ids()) else: all_newsletters = newsletter_slugs() private_newsletters = newsletter_private_slugs() for nl in newsletters: if nl not in all_newsletters: return HttpResponseJSON( { "status": "error", "desc": "invalid newsletter", "code": errors.BASKET_INVALID_NEWSLETTER, }, 400, ) if api_call_type != UNSUBSCRIBE and nl in private_newsletters: if not is_authorized(request, data.get("email")): return HttpResponseJSON( { "status": "error", "desc": "private newsletter subscription requires a valid API key or OAuth", "code": errors.BASKET_AUTH_ERROR, }, 401, ) if "lang" in data: if not language_code_is_valid(data["lang"]): data["lang"] = "en" elif "accept_lang" in data: lang = get_best_language(get_accept_languages(data["accept_lang"])) if lang: data["lang"] = lang del data["accept_lang"] # if lang not provided get the best one from the accept-language header else: lang = get_best_request_lang(request) if lang: data["lang"] = lang # now ensure that if we do have a lang that it's a supported one if "lang" in data: data["lang"] = get_best_supported_lang(data["lang"]) email = data.get("email") token = data.get("token") if not (email or token): return HttpResponseJSON( { "status": "error", "desc": MSG_EMAIL_OR_TOKEN_REQUIRED, "code": errors.BASKET_USAGE_ERROR, }, 400, ) if optin: data["optin"] = True if api_call_type == SUBSCRIBE and email and data.get("newsletters"): # only rate limit here so we don't rate limit errors. if is_ratelimited( request, group="basket.news.views.update_user_task.subscribe", key=lambda x, y: "%s-%s" % (data["newsletters"], email), rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True, ): raise Ratelimited() if api_call_type == SET and token and data.get("newsletters"): # only rate limit here so we don't rate limit errors. if is_ratelimited( request, group="basket.news.views.update_user_task.set", key=lambda x, y: "%s-%s" % (data["newsletters"], token), rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True, ): raise Ratelimited() if sync: statsd.incr("news.views.subscribe.sync") if settings.MAINTENANCE_MODE and not settings.MAINTENANCE_READ_ONLY: # save what we can upsert_user.delay(api_call_type, data, start_time=time()) # have to error since we can't return a token return HttpResponseJSON( { "status": "error", "desc": "sync is not available in maintenance mode", "code": errors.BASKET_NETWORK_FAILURE, }, 400, ) try: user_data = get_user_data(email=email, token=token) except NewsletterException as e: return newsletter_exception_response(e) if not user_data: if not email: # must have email to create a user return HttpResponseJSON( { "status": "error", "desc": MSG_EMAIL_OR_TOKEN_REQUIRED, "code": errors.BASKET_USAGE_ERROR, }, 400, ) token, created = upsert_contact(api_call_type, data, user_data) return HttpResponseJSON({ "status": "ok", "token": token, "created": created }) else: upsert_user.delay(api_call_type, data, start_time=time()) return HttpResponseJSON({"status": "ok"})
def test_newsletter_slugs(self): self.assertEqual( set(newsletters.newsletter_slugs()), {"bowling", "surfing", "extorting", "papers"}, )
def update_user_task(request, api_call_type, data=None, optin=False, sync=False): """Call the update_user task async with the right parameters. If sync==True, be sure to include the token in the response. Otherwise, basket can just do everything in the background. """ data = data or request.POST.dict() newsletters = parse_newsletters_csv(data.get('newsletters')) if newsletters: if api_call_type == SUBSCRIBE: all_newsletters = newsletter_and_group_slugs() + get_transactional_message_ids() else: all_newsletters = newsletter_slugs() private_newsletters = newsletter_private_slugs() for nl in newsletters: if nl not in all_newsletters: return HttpResponseJSON({ 'status': 'error', 'desc': 'invalid newsletter', 'code': errors.BASKET_INVALID_NEWSLETTER, }, 400) if api_call_type != UNSUBSCRIBE and nl in private_newsletters: if not has_valid_api_key(request): return HttpResponseJSON({ 'status': 'error', 'desc': 'private newsletter subscription requires a valid API key', 'code': errors.BASKET_AUTH_ERROR, }, 401) if 'lang' in data: if not language_code_is_valid(data['lang']): data['lang'] = 'en' elif 'accept_lang' in data: lang = get_best_language(get_accept_languages(data['accept_lang'])) if lang: data['lang'] = lang del data['accept_lang'] else: data['lang'] = 'en' # if lang not provided get the best one from the accept-language header else: data['lang'] = get_best_request_lang(request) or 'en' email = data.get('email') token = data.get('token') if not (email or token): return HttpResponseJSON({ 'status': 'error', 'desc': MSG_EMAIL_OR_TOKEN_REQUIRED, 'code': errors.BASKET_USAGE_ERROR, }, 400) if optin: data['optin'] = True if api_call_type == SUBSCRIBE and email and data.get('newsletters'): # only rate limit here so we don't rate limit errors. if is_ratelimited(request, group='basket.news.views.update_user_task.subscribe', key=lambda x, y: '%s-%s' % (data['newsletters'], email), rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True): raise Ratelimited() if api_call_type == SET and token and data.get('newsletters'): # only rate limit here so we don't rate limit errors. if is_ratelimited(request, group='basket.news.views.update_user_task.set', key=lambda x, y: '%s-%s' % (data['newsletters'], token), rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True): raise Ratelimited() if sync: statsd.incr('news.views.subscribe.sync') if settings.MAINTENANCE_MODE and not settings.MAINTENANCE_READ_ONLY: # save what we can upsert_user.delay(api_call_type, data, start_time=time()) # have to error since we can't return a token return HttpResponseJSON({ 'status': 'error', 'desc': 'sync is not available in maintenance mode', 'code': errors.BASKET_NETWORK_FAILURE, }, 400) try: user_data = get_user_data(email=email, token=token) except NewsletterException as e: return newsletter_exception_response(e) if not user_data: if not email: # must have email to create a user return HttpResponseJSON({ 'status': 'error', 'desc': MSG_EMAIL_OR_TOKEN_REQUIRED, 'code': errors.BASKET_USAGE_ERROR, }, 400) token, created = upsert_contact(api_call_type, data, user_data) return HttpResponseJSON({ 'status': 'ok', 'token': token, 'created': created, }) else: upsert_user.delay(api_call_type, data, start_time=time()) return HttpResponseJSON({ 'status': 'ok', })
def update_user_task(request, api_call_type, data=None, optin=False, sync=False): """Call the update_user task async with the right parameters. If sync==True, be sure to include the token in the response. Otherwise, basket can just do everything in the background. """ data = data or request.POST.dict() newsletters = parse_newsletters_csv(data.get('newsletters')) if newsletters: if api_call_type == SUBSCRIBE: all_newsletters = newsletter_and_group_slugs() + get_transactional_message_ids() else: all_newsletters = newsletter_slugs() private_newsletters = newsletter_private_slugs() for nl in newsletters: if nl not in all_newsletters: return HttpResponseJSON({ 'status': 'error', 'desc': 'invalid newsletter', 'code': errors.BASKET_INVALID_NEWSLETTER, }, 400) if api_call_type != UNSUBSCRIBE and nl in private_newsletters: if not is_authorized(request, data.get('email')): return HttpResponseJSON({ 'status': 'error', 'desc': 'private newsletter subscription requires a valid API key or OAuth', 'code': errors.BASKET_AUTH_ERROR, }, 401) if 'lang' in data: if not language_code_is_valid(data['lang']): data['lang'] = 'en' elif 'accept_lang' in data: lang = get_best_language(get_accept_languages(data['accept_lang'])) if lang: data['lang'] = lang del data['accept_lang'] # if lang not provided get the best one from the accept-language header else: lang = get_best_request_lang(request) if lang: data['lang'] = lang email = data.get('email') token = data.get('token') if not (email or token): return HttpResponseJSON({ 'status': 'error', 'desc': MSG_EMAIL_OR_TOKEN_REQUIRED, 'code': errors.BASKET_USAGE_ERROR, }, 400) if optin: data['optin'] = True if api_call_type == SUBSCRIBE and email and data.get('newsletters'): # only rate limit here so we don't rate limit errors. if is_ratelimited(request, group='basket.news.views.update_user_task.subscribe', key=lambda x, y: '%s-%s' % (data['newsletters'], email), rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True): raise Ratelimited() if api_call_type == SET and token and data.get('newsletters'): # only rate limit here so we don't rate limit errors. if is_ratelimited(request, group='basket.news.views.update_user_task.set', key=lambda x, y: '%s-%s' % (data['newsletters'], token), rate=EMAIL_SUBSCRIBE_RATE_LIMIT, increment=True): raise Ratelimited() if sync: statsd.incr('news.views.subscribe.sync') if settings.MAINTENANCE_MODE and not settings.MAINTENANCE_READ_ONLY: # save what we can upsert_user.delay(api_call_type, data, start_time=time()) # have to error since we can't return a token return HttpResponseJSON({ 'status': 'error', 'desc': 'sync is not available in maintenance mode', 'code': errors.BASKET_NETWORK_FAILURE, }, 400) try: user_data = get_user_data(email=email, token=token) except NewsletterException as e: return newsletter_exception_response(e) if not user_data: if not email: # must have email to create a user return HttpResponseJSON({ 'status': 'error', 'desc': MSG_EMAIL_OR_TOKEN_REQUIRED, 'code': errors.BASKET_USAGE_ERROR, }, 400) token, created = upsert_contact(api_call_type, data, user_data) return HttpResponseJSON({ 'status': 'ok', 'token': token, 'created': created, }) else: upsert_user.delay(api_call_type, data, start_time=time()) return HttpResponseJSON({ 'status': 'ok', })
def to_vendor(data, existing_data=None): """ Transform basket key-value data and convert to CTMS nested data Differences from SFDC.to_vendor: * No equivalent to Subscriber__c, UAT_Test_Data__c * Doesn't convert SFDC values Browser_Locale__c, FSA_*, CV_*, MailingCity * CTMS API handles boolean conversion * Allows setting a value to an empty string or None, if existing is set. @params data: data to update, basket format @params existing_data: existing user data, basket format @return: dict in CTMS format """ ctms_data = {} amo_deleted = False newsletters = None newsletter_subscription_default = {} existing_data = existing_data or {} if "lang" in existing_data: default_lang = process_lang(existing_data["lang"]) newsletter_subscription_default["lang"] = default_lang if "format" in existing_data: newsletter_subscription_default["format"] = existing_data["format"] for name, raw_value in data.items(): # Pre-process raw_value, which may remove it. processor = TO_VENDOR_PROCESSORS.get(name) if processor: try: value = processor(raw_value) except ValueError: continue # Skip invalid values else: value = raw_value # Strip whitespace try: value = value.strip() except AttributeError: pass # Skip empty values if new record or also unset in existing data if (value is None or value == "") and not existing_data.get(name): continue # Place in CTMS contact structure if name in BASKET_TO_CTMS_NAMES: group_name, key = BASKET_TO_CTMS_NAMES[name] ctms_data.setdefault(group_name, {})[key] = value if name in {"lang", "format"}: newsletter_subscription_default[name] = value elif name == "source_url": newsletter_subscription_default["source"] = value elif name == "newsletters": # Process newsletters after gathering all newsletter keys newsletters = value elif name == "amo_deleted": amo_deleted = bool(value) elif name not in DISCARD_BASKET_NAMES: # TODO: SFDC ignores unknown fields, maybe this should as well raise CTMSUnknownKeyError(name) # Process the newsletters, which may include extra data from the email group if newsletters: valid_slugs = newsletter_slugs() output = [] if isinstance(newsletters, dict): # Detect unsubscribe all optout = data.get("optout", False) or False if (optout and (not any(newsletters.values())) and (set(valid_slugs) == set(newsletters.keys()))): # When unsubscribe all is requested, let CTMS unsubscribe from all output = "UNSUBSCRIBE" else: # Dictionary of slugs to sub/unsub flags for slug, subscribed in newsletters.items(): if slug in valid_slugs: if subscribed: nl_sub = newsletter_subscription_default.copy() nl_sub.update({"name": slug, "subscribed": True}) else: nl_sub = {"name": slug, "subscribed": False} output.append(nl_sub) else: # List of slugs for subscriptions, which may include a source for slug in newsletters: if slug in valid_slugs: nl_sub = newsletter_subscription_default.copy() nl_sub.update({"name": slug, "subscribed": True}) output.append(nl_sub) if output: ctms_data["newsletters"] = output # When an AMO account is deleted, reset data to defaults if amo_deleted: ctms_data["amo"] = "DELETE" return ctms_data
def test_newsletter_slugs(self): self.assertEqual(set(newsletters.newsletter_slugs()), {'bowling', 'surfing', 'extorting', 'papers'})