def to_python(self, value): value = super(NewslettersField, self).to_python(value) full_list = [] for v in value: full_list.extend(parse_newsletters_csv(v)) return full_list
def test_values(self): self.assertEqual(parse_newsletters_csv('dude,walter'), ['dude', 'walter']) self.assertEqual(parse_newsletters_csv(' dude, walter '), ['dude', 'walter']) self.assertEqual(parse_newsletters_csv(', dude, ,walter, '), ['dude', 'walter']) self.assertEqual(parse_newsletters_csv(False), []) self.assertEqual(parse_newsletters_csv(None), []) self.assertEqual(parse_newsletters_csv(['dude', 'donny']), ['dude', 'donny'])
def test_values(self): self.assertEqual(parse_newsletters_csv("dude,walter"), ["dude", "walter"]) self.assertEqual(parse_newsletters_csv(" dude, walter "), ["dude", "walter"]) self.assertEqual(parse_newsletters_csv(", dude, ,walter, "), ["dude", "walter"]) self.assertEqual(parse_newsletters_csv(False), []) self.assertEqual(parse_newsletters_csv(None), []) self.assertEqual(parse_newsletters_csv(["dude", "donny"]), ["dude", "donny"])
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 upsert_contact(api_call_type, data, user_data): """ Update or insert (upsert) a contact record in SFDC @param int api_call_type: What kind of API call it was. Could be SUBSCRIBE, UNSUBSCRIBE, or SET. @param dict data: POST data from the form submission @param dict user_data: existing contact data from SFDC @return: token, created """ update_data = data.copy() forced_optin = data.pop('optin', False) if 'format' in data: update_data['format'] = 'T' if data['format'].upper().startswith( 'T') else 'H' newsletters = parse_newsletters_csv(data.get('newsletters')) if user_data: cur_newsletters = user_data.get('newsletters', None) else: cur_newsletters = None # check for and remove transactional newsletters if api_call_type == SUBSCRIBE: all_transactionals = set(get_transactional_message_ids()) newsletters_set = set(newsletters) transactionals = newsletters_set & all_transactionals if transactionals: newsletters = list(newsletters_set - transactionals) send_transactional_messages(update_data, user_data, list(transactionals)) if not newsletters: # no regular newsletters return None, None # Set the newsletter flags in the record by comparing to their # current subscriptions. update_data['newsletters'] = parse_newsletters(api_call_type, newsletters, cur_newsletters) if api_call_type != UNSUBSCRIBE: # Are they subscribing to any newsletters that don't require confirmation? # When including any newsletter that does not # require confirmation, user gets a pass on confirming and goes straight # to confirmed. to_subscribe = [ nl for nl, sub in update_data['newsletters'].items() if sub ] if to_subscribe and not (forced_optin or (user_data and user_data.get('optin'))): exempt_from_confirmation = Newsletter.objects \ .filter(slug__in=to_subscribe, requires_double_optin=False) \ .exists() if exempt_from_confirmation: update_data['optin'] = True # record source URL nl_map = newsletter_map() source_url = update_data.get('source_url') email = update_data.get('email') if not email: email = user_data.get('email') if user_data else None if email: # send all newsletters whether already subscribed or not # bug 1308971 # if api_call_type == SET this is pref center, so only send new subscriptions nl_list = newsletters if api_call_type == SUBSCRIBE else to_subscribe for nlid in nl_list: if nlid in nl_map: record_source_url.delay(email, source_url, nl_map[nlid]) if user_data is None: # no user found. create new one. update_data['token'] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data) else: # don't catch exceptions here. SalesforceError subclasses will retry. sfdc.add(update_data) return update_data['token'], True if forced_optin and not user_data.get('optin'): update_data['optin'] = True # they opted out of email before, but are subscribing again # clear the optout flag if api_call_type != UNSUBSCRIBE and user_data.get('optout'): update_data['optout'] = False # update record if user_data and user_data.get('token'): token = user_data['token'] else: token = update_data['token'] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data, user_data) else: sfdc.update(user_data, update_data) return token, False
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 upsert_contact(api_call_type, data, user_data): """ Update or insert (upsert) a contact record in SFDC @param int api_call_type: What kind of API call it was. Could be SUBSCRIBE, UNSUBSCRIBE, or SET. @param dict data: POST data from the form submission @param dict user_data: existing contact data from SFDC @return: token, created """ update_data = data.copy() forced_optin = data.pop("optin", False) if "format" in data: update_data["format"] = "T" if data["format"].upper().startswith("T") else "H" newsletters = parse_newsletters_csv(data.get("newsletters")) if user_data: cur_newsletters = user_data.get("newsletters", None) else: cur_newsletters = None # check for and remove transactional newsletters if api_call_type == SUBSCRIBE: all_transactionals = set(get_transactional_message_ids()) newsletters_set = set(newsletters) transactionals = newsletters_set & all_transactionals if transactionals: newsletters = list(newsletters_set - transactionals) send_acoustic_tx_messages( data["email"], data.get("lang", "en-US"), list(transactionals), ) if not newsletters: # no regular newsletters return None, None # Set the newsletter flags in the record by comparing to their # current subscriptions. update_data["newsletters"] = parse_newsletters( api_call_type, newsletters, cur_newsletters, ) send_confirm = False if api_call_type != UNSUBSCRIBE: # Check for newsletter-specific user updates to_subscribe_slugs = [ nl for nl, sub in update_data["newsletters"].items() if sub ] check_optin = not (forced_optin or (user_data and user_data.get("optin"))) check_mofo = not (user_data and user_data.get("mofo_relevant")) if to_subscribe_slugs and (check_optin or check_mofo): to_subscribe = Newsletter.objects.filter(slug__in=to_subscribe_slugs) # Are they subscribing to any newsletters that require confirmation? # If none require confirmation, user goes straight to confirmed (optin) # Otherwise, prepare to send a fx or moz confirmation if check_optin: exempt_from_confirmation = any( [not o.requires_double_optin for o in to_subscribe], ) if exempt_from_confirmation: update_data["optin"] = True else: send_fx_confirm = all([o.firefox_confirm for o in to_subscribe]) send_confirm = "fx" if send_fx_confirm else "moz" # Update a user to MoFo-relevant if they subscribed to a MoFo newsletters if check_mofo: if any([ns.is_mofo for ns in to_subscribe]): update_data["mofo_relevant"] = True if user_data is None: # no user found. create new one. token = update_data["token"] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data) else: ctms_data = update_data.copy() ctms_contact = ctms.add(ctms_data) if ctms_contact: # Successfully added to CTMS, send email_id to SFDC update_data["email_id"] = ctms_contact["email"]["email_id"] # don't catch exceptions here. SalesforceError subclasses will retry. sfdc.add(update_data) if send_confirm and settings.SEND_CONFIRM_MESSAGES: send_confirm_message.delay( data["email"], token, data.get("lang", "en-US"), send_confirm, ) return token, True if forced_optin and not user_data.get("optin"): update_data["optin"] = True # they opted out of email before, but are subscribing again # clear the optout flag if api_call_type != UNSUBSCRIBE and user_data.get("optout"): update_data["optout"] = False # update record if user_data and user_data.get("token"): token = user_data["token"] else: token = update_data["token"] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data, user_data) else: sfdc.update(user_data, update_data) ctms.update(user_data, update_data) if send_confirm and settings.SEND_CONFIRM_MESSAGES: send_confirm_message.delay( user_data["email"], token, update_data.get("lang", user_data.get("lang", "en-US")), send_confirm, ) return token, False
def upsert_contact(api_call_type, data, user_data): """ Update or insert (upsert) a contact record in SFDC @param int api_call_type: What kind of API call it was. Could be SUBSCRIBE, UNSUBSCRIBE, or SET. @param dict data: POST data from the form submission @param dict user_data: existing contact data from SFDC @return: token, created """ update_data = data.copy() forced_optin = data.pop('optin', False) if 'format' in data: update_data['format'] = 'T' if data['format'].upper().startswith('T') else 'H' newsletters = parse_newsletters_csv(data.get('newsletters')) if user_data: cur_newsletters = user_data.get('newsletters', None) else: cur_newsletters = None # check for and remove transactional newsletters if api_call_type == SUBSCRIBE: all_transactionals = set(get_transactional_message_ids()) newsletters_set = set(newsletters) transactionals = newsletters_set & all_transactionals if transactionals: newsletters = list(newsletters_set - transactionals) send_transactional_messages(update_data, user_data, list(transactionals)) if not newsletters: # no regular newsletters return None, None # Set the newsletter flags in the record by comparing to their # current subscriptions. update_data['newsletters'] = parse_newsletters(api_call_type, newsletters, cur_newsletters) if api_call_type != UNSUBSCRIBE: # Are they subscribing to any newsletters that don't require confirmation? # When including any newsletter that does not # require confirmation, user gets a pass on confirming and goes straight # to confirmed. to_subscribe = [nl for nl, sub in update_data['newsletters'].iteritems() if sub] if to_subscribe and not (forced_optin or (user_data and user_data.get('optin'))): exempt_from_confirmation = Newsletter.objects \ .filter(slug__in=to_subscribe, requires_double_optin=False) \ .exists() if exempt_from_confirmation: update_data['optin'] = True # record source URL nl_map = newsletter_map() source_url = update_data.get('source_url') email = update_data.get('email') if not email: email = user_data.get('email') if user_data else None if email: # send all newsletters whether already subscribed or not # bug 1308971 # if api_call_type == SET this is pref center, so only send new subscriptions nl_list = newsletters if api_call_type == SUBSCRIBE else to_subscribe for nlid in nl_list: if nlid in nl_map: record_source_url.delay(email, source_url, nl_map[nlid]) if user_data is None: # no user found. create new one. update_data['token'] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data) else: # don't catch exceptions here. SalesforceError subclasses will retry. sfdc.add(update_data) return update_data['token'], True if forced_optin and not user_data.get('optin'): update_data['optin'] = True # they opted out of email before, but are subscribing again # clear the optout flag if api_call_type != UNSUBSCRIBE and user_data.get('optout'): update_data['optout'] = False # update record if user_data and user_data.get('token'): token = user_data['token'] else: token = update_data['token'] = generate_token() if settings.MAINTENANCE_MODE: sfdc_add_update.delay(update_data, user_data) else: sfdc.update(user_data, update_data) return token, False