def clean(self): valid_newsletters = utils.get_newsletters() for newsletter in self.newsletters: if newsletter not in valid_newsletters: msg = _("%s is not a valid newsletter") % newsletter raise ValidationError(msg) return super(ManageSubscriptionsForm, self).clean()
def newsletter_title(newsletter): """Given a newsletter's key, return its title if we can, otherwise return the key """ newsletters = utils.get_newsletters() if newsletter in newsletters and 'title' in newsletters[newsletter]: return newsletters[newsletter]['title'] return newsletter
def test_get_newsletters_fallback(self, mock_basket_get_newsletters): # if get_newsletters() cannot reach basket, it returns the # newsletters from settings mock_basket_get_newsletters.side_effect = BasketException default_value = mock.Mock() with override_settings(DEFAULT_NEWSLETTERS=default_value): return_value = get_newsletters() self.assertEqual(default_value, return_value)
def clean(self): valid_newsletters = utils.get_newsletters() for newsletter in self.newsletters: if newsletter not in valid_newsletters: msg = ftl('newsletters-is-not-a-valid-newsletter', newsletter=newsletter, ftl_files=['mozorg/newsletters']) raise ValidationError(msg) return super(ManageSubscriptionsForm, self).clean()
def clean(self): valid_newsletters = utils.get_newsletters() for newsletter in self.newsletters: if newsletter not in valid_newsletters: msg = ftl("newsletters-is-not-a-valid-newsletter", newsletter=newsletter, ftl_files=["mozorg/newsletters"]) raise ValidationError(msg) return super().clean()
def test_get_newsletters_fallback(self, mock_basket_get_newsletters, mock_glbnd): # if get_newsletters() cannot reach basket, it returns the # newsletters from cached json mock_basket_get_newsletters.side_effect = BasketException( 'network error', code=errors.BASKET_NETWORK_FAILURE, ) return_value = utils.get_newsletters() self.assertEqual(mock_glbnd.return_value, return_value)
def test_simple_get(self): # get_newsletters returns whatever it gets back from basket without # changing it at all. # Create a silly data structure to pass around test_val = {'foo': [1, 2, 3], 'bar': {'baz': 27}} with mock.patch('basket.get_newsletters') as basket_get: basket_get.return_value = test_val result = get_newsletters() self.assertEqual(test_val, result)
def test_simple_get(self): # get_newsletters returns whatever it gets back from basket without # changing it at all. # Create a silly data structure to pass around test_val = {'foo': {'zoo': 'zebra'}, 'bar': {'baz': 27}} with mock.patch('basket.get_newsletters') as basket_get: basket_get.return_value = test_val result = utils.get_newsletters() self.assertEqual(test_val, result)
def clean_newsletter(self): # We didn't want to have to look up the list of valid newsletters # until we actually had a form submitted newsletter = self.cleaned_data.get('newsletter', None) if newsletter: newsletters = newsletter.replace(' ', '').split(',') valid_newsletters = utils.get_newsletters().keys() for nl in newsletters: if nl not in valid_newsletters: raise ValidationError("%s is not a valid newsletter" % nl) return ','.join(newsletters)
def existing(request, token=None): """Manage subscriptions. If token is provided, user can manage their existing subscriptions, to subscribe, unsubscribe, change email or language preferences, etc. If no token is provided, user can fill in their email and language preferences and sign up for newsletters. @param HTTPRequest request: Django request object @param string token: A UUID that identifies this user to the backend. It's sent to users in each newsletter as part of a link to this page, so they can manage their subscriptions without needing an account somewhere with userid & password. """ locale = l10n_utils.get_locale(request) if not token: return redirect(reverse('newsletter.recovery')) if not UUID_REGEX.match(token): # Bad token messages.add_message(request, messages.ERROR, bad_token) # Redirect to the recovery page return redirect(reverse('newsletter.recovery')) if waffle.switch('newsletter-maintenance-mode'): return l10n_utils.render(request, 'newsletter/existing.html') unsub_parm = None # Example user: # # {u'lang': u'en', # u'format': u'H', # u'country': u'us', # u'newsletters': [u'firefox-tips', u'mobile'], # u'created-date': u'1/30/2013 12:46:05 PM', # u'token': u'some-uuid', # u'email': u'*****@*****.**' # } has_fxa = 'fxa' in request.GET user = None if token: try: # ask for fxa status if not passed in the URL params = None if has_fxa else {'fxa': 1} user = basket.request('get', 'user', token=token, params=params) except basket.BasketNetworkException: # Something wrong with basket backend, no point in continuing, # we'd probably fail to subscribe them anyway. log.exception("Basket timeout") messages.add_message(request, messages.ERROR, general_error) return l10n_utils.render(request, 'newsletter/existing.html') except basket.BasketException as e: log.exception("FAILED to get user from token (%s)", e.desc) if not user: # Bad or no token messages.add_message(request, messages.ERROR, bad_token) # Redirect to the recovery page return redirect(reverse('newsletter.recovery')) # if `has_fxa` not returned from basket, set it from the URL user.setdefault('has_fxa', has_fxa) # Get the newsletter data - it's a dictionary of dictionaries newsletter_data = utils.get_newsletters() # Figure out which newsletters to display, and whether to show them # as already subscribed. initial = [] for newsletter, data in newsletter_data.items(): # Only show a newsletter if it has ['active'] == True and # ['show'] == True or the user is already subscribed if not data.get('active', False): continue if (data.get('show', False) or newsletter in user['newsletters'] or (user['has_fxa'] and newsletter in settings.FXA_NEWSLETTERS and any( locale.startswith(l) for l in settings.FXA_NEWSLETTERS_LOCALES))): langs = data['languages'] nstrings = NEWSLETTER_STRINGS.get(newsletter) if nstrings: if newsletter == 'firefox-accounts-journey' and locale.startswith( 'en'): # alternate english title title = u'Firefox Account Tips' else: title = nstrings['title'] description = nstrings.get('description', u'') else: # Firefox Marketplace for Desktop/Android/Firefox OS should be # shorten in the titles title = _(data['title'].replace('Firefox Marketplace for ', '')) description = _(data['description']) form_data = { 'title': Markup(title), 'subscribed_radio': newsletter in user['newsletters'], 'subscribed_check': newsletter in user['newsletters'], 'newsletter': newsletter, 'description': Markup(description), 'english_only': len(langs) == 1 and langs[0].startswith('en'), 'indented': data.get('indent', False), } if 'order' in data: form_data['order'] = data['order'] initial.append(form_data) # Sort by 'order' field if we were given it; otherwise, by title if initial: keyfield = 'order' if 'order' in initial[0] else 'title' initial.sort(key=itemgetter(keyfield)) NewsletterFormSet = formset_factory(NewsletterForm, extra=0, max_num=len(initial)) if request.method == 'POST': form_kwargs = {} # Temporary form so we can see if they checked 'remove_all'. If # they did, no point in validating the newsletters formset and it would # look dumb to complain about it. form = ManageSubscriptionsForm(locale, data=request.POST, initial=user) remove_all = form.is_valid() and form.cleaned_data['remove_all'] formset_is_valid = False if remove_all: # We don't care about the newsletter formset formset_is_valid = True # Make an initialized one in case we fall through to the bottom formset = NewsletterFormSet(initial=initial) else: # We do need to validate the newsletter formset formset = NewsletterFormSet(request.POST, initial=initial) # Set `newsletters` to the list of newsletters they want. # After this, we don't need the formset anymore. newsletters = None if formset.is_valid(): formset_is_valid = True # What newsletters do they say they want to be subscribed to? newsletters = set([ subform.cleaned_data['newsletter'] for subform in formset if (subform.cleaned_data['subscribed_radio'] or subform.cleaned_data['subscribed_check']) ]) form_kwargs['newsletters'] = newsletters form = ManageSubscriptionsForm(locale, data=request.POST, initial=user, **form_kwargs) if formset_is_valid and form.is_valid(): data = form.cleaned_data # Update their format and locale information, if it has changed. # Also pass their updated list of newsletters they want to be # subscribed to, for basket to implement. kwargs = {} if settings.BASKET_API_KEY: kwargs['api_key'] = settings.BASKET_API_KEY for k in ['lang', 'format', 'country']: if user[k] != data[k]: kwargs[k] = data[k] if not remove_all: kwargs['newsletters'] = ",".join(newsletters) if kwargs: # always send lang so basket doesn't try to guess kwargs['lang'] = data['lang'] try: basket.update_user(token, **kwargs) except basket.BasketException: log.exception("Error updating user in basket") messages.add_message(request, messages.ERROR, general_error) return l10n_utils.render(request, 'newsletter/existing.html') # If they chose to remove all, tell basket that they've opted out if remove_all: try: basket.unsubscribe(token, user['email'], optout=True) except (basket.BasketException, requests.Timeout): log.exception("Error updating subscriptions in basket") messages.add_message(request, messages.ERROR, general_error) return l10n_utils.render(request, 'newsletter/existing.html') # We need to pass their token to the next view url = reverse('newsletter.updated') \ + "?unsub=%s&token=%s" % (UNSUB_UNSUBSCRIBED_ALL, token) return redirect(url) # We're going to redirect, so the only way to tell the next # view that we should display the welcome message in the # template is to modify the URL url = reverse('newsletter.updated') if unsub_parm: url += "?unsub=%s" % unsub_parm return redirect(url) # FALL THROUGH so page displays errors else: form = ManageSubscriptionsForm(locale, initial=user) formset = NewsletterFormSet(initial=initial) # For the template, we want a dictionary whose keys are language codes # and each value is the list of newsletter keys that are available in # that language code. newsletter_languages = defaultdict(list) for newsletter, data in newsletter_data.items(): for lang in data['languages']: newsletter_languages[lang].append(newsletter) newsletter_languages = mark_safe(json.dumps(newsletter_languages)) # We also want a list of the newsletters the user is already subscribed to already_subscribed = mark_safe(json.dumps(user['newsletters'])) context = { 'did_confirm': request.GET.get('confirm', None) == '1', 'form': form, 'formset': formset, 'newsletter_languages': newsletter_languages, 'newsletters_subscribed': already_subscribed, 'email': user['email'], } return l10n_utils.render(request, 'newsletter/existing.html', context)
def existing(request, token=None): """Manage subscriptions. If token is provided, user can manage their existing subscriptions, to subscribe, unsubscribe, change email or language preferences, etc. If no token is provided, user can fill in their email and language preferences and sign up for newsletters. @param HTTPRequest request: Django request object @param string token: A UUID that identifies this user to the backend. It's sent to users in each newsletter as part of a link to this page, so they can manage their subscriptions without needing an account somewhere with userid & password. """ locale = getattr(request, 'locale', 'en-US') if not token: return redirect(reverse('newsletter.recovery')) if not UUID_REGEX.match(token): # Bad token messages.add_message(request, messages.ERROR, bad_token) # Redirect to the recovery page return redirect(reverse('newsletter.recovery')) unsub_parm = None # Example user: # # {u'lang': u'en', # u'format': u'H', # u'country': u'us', # u'newsletters': [u'firefox-tips', u'mobile'], # u'created-date': u'1/30/2013 12:46:05 PM', # u'token': u'some-uuid', # u'email': u'*****@*****.**' # } user_exists = False if token: try: user = basket.user(token) except basket.BasketNetworkException: # Something wrong with basket backend, no point in continuing, # we'd probably fail to subscribe them anyway. log.exception("Basket timeout") messages.add_message(request, messages.ERROR, general_error) return l10n_utils.render(request, 'newsletter/existing.html') except basket.BasketException as e: log.exception("FAILED to get user from token (%s)", e.desc) else: user_exists = True if not user_exists: # Bad or no token messages.add_message(request, messages.ERROR, bad_token) # Redirect to the recovery page return redirect(reverse('newsletter.recovery')) # Get the newsletter data - it's a dictionary of dictionaries newsletter_data = utils.get_newsletters() # Figure out which newsletters to display, and whether to show them # as already subscribed. initial = [] for newsletter, data in newsletter_data.iteritems(): # Only show a newsletter if it has ['active'] == True and # ['show'] == True or the user is already subscribed if not data.get('active', False): continue if data.get('show', False) or newsletter in user['newsletters']: langs = data['languages'] nstrings = NEWSLETTER_STRINGS.get(newsletter) if nstrings: title = nstrings['title'] description = nstrings.get('description', u'') else: # Firefox Marketplace for Desktop/Android/Firefox OS should be # shorten in the titles title = _(data['title'].replace('Firefox Marketplace for ', '')) description = _(data['description']) form_data = { 'title': Markup(title), 'subscribed_radio': newsletter in user['newsletters'], 'subscribed_check': newsletter in user['newsletters'], 'newsletter': newsletter, 'description': Markup(description), 'english_only': len(langs) == 1 and langs[0].startswith('en'), } if 'order' in data: form_data['order'] = data['order'] initial.append(form_data) # Sort by 'order' field if we were given it; otherwise, by title if initial: keyfield = 'order' if 'order' in initial[0] else 'title' initial.sort(key=itemgetter(keyfield)) NewsletterFormSet = formset_factory(NewsletterForm, extra=0, max_num=len(initial)) if request.method == 'POST': form_kwargs = {} # Temporary form so we can see if they checked 'remove_all'. If # they did, no point in validating the newsletters formset and it would # look dumb to complain about it. form = ManageSubscriptionsForm(locale, data=request.POST, initial=user) remove_all = form.is_valid() and form.cleaned_data['remove_all'] formset_is_valid = False if remove_all: # We don't care about the newsletter formset formset_is_valid = True # Make an initialized one in case we fall through to the bottom formset = NewsletterFormSet(initial=initial) else: # We do need to validate the newsletter formset formset = NewsletterFormSet(request.POST, initial=initial) # Set `newsletters` to the list of newsletters they want. # After this, we don't need the formset anymore. newsletters = None if formset.is_valid(): formset_is_valid = True # What newsletters do they say they want to be subscribed to? newsletters = set([subform.cleaned_data['newsletter'] for subform in formset if (subform.cleaned_data['subscribed_radio'] or subform.cleaned_data['subscribed_check'])]) form_kwargs['newsletters'] = newsletters form = ManageSubscriptionsForm(locale, data=request.POST, initial=user, **form_kwargs) if formset_is_valid and form.is_valid(): data = form.cleaned_data # Update their format and locale information, if it has changed. # Also pass their updated list of newsletters they want to be # subscribed to, for basket to implement. kwargs = {} for k in ['lang', 'format', 'country']: if user[k] != data[k]: kwargs[k] = data[k] if not remove_all: kwargs['newsletters'] = ",".join(newsletters) if kwargs: try: basket.update_user(token, **kwargs) except basket.BasketException: log.exception("Error updating user in basket") messages.add_message( request, messages.ERROR, general_error ) return l10n_utils.render(request, 'newsletter/existing.html') # If they chose to remove all, tell basket that they've opted out if remove_all: try: basket.unsubscribe(token, user['email'], optout=True) except (basket.BasketException, requests.Timeout): log.exception("Error updating subscriptions in basket") messages.add_message( request, messages.ERROR, general_error ) return l10n_utils.render(request, 'newsletter/existing.html') # We need to pass their token to the next view url = reverse('newsletter.updated') \ + "?unsub=%s&token=%s" % (UNSUB_UNSUBSCRIBED_ALL, token) return redirect(url) # We're going to redirect, so the only way to tell the next # view that we should display the welcome message in the # template is to modify the URL url = reverse('newsletter.updated') if unsub_parm: url += "?unsub=%s" % unsub_parm return redirect(url) # FALL THROUGH so page displays errors else: form = ManageSubscriptionsForm( locale, initial=user ) formset = NewsletterFormSet(initial=initial) # For the template, we want a dictionary whose keys are language codes # and each value is the list of newsletter keys that are available in # that language code. newsletter_languages = defaultdict(list) for newsletter, data in newsletter_data.iteritems(): for lang in data['languages']: newsletter_languages[lang].append(newsletter) newsletter_languages = mark_safe(json.dumps(newsletter_languages)) # We also want a list of the newsletters the user is already subscribed # to already_subscribed = mark_safe(json.dumps(user['newsletters'])) context = { 'form': form, 'formset': formset, 'newsletter_languages': newsletter_languages, 'newsletters_subscribed': already_subscribed, 'email': user['email'], } return l10n_utils.render(request, 'newsletter/existing.html', context)
def existing(request, token=None): """Manage subscriptions. If token is provided, user can manage their existing subscriptions, to subscribe, unsubscribe, change email or language preferences, etc. If no token is provided, user can fill in their email and language preferences and sign up for newsletters. @param HTTPRequest request: Django request object @param string token: A UUID that identifies this user to the backend. It's sent to users in each newsletter as part of a link to this page, so they can manage their subscriptions without needing an account somewhere with userid & password. """ locale = getattr(request, "locale", "en-US") unsub_parm = None # Example user: # # {u'lang': u'en', # u'format': u'H', # u'country': u'us', # u'newsletters': [u'firefox-tips', u'mobile'], # u'created-date': u'1/30/2013 12:46:05 PM', # u'token': u'some-uuid', # u'email': u'*****@*****.**' # } user_exists = False if token: try: user = basket.user(token) except basket.BasketException: log.exception("FAILED to get user from token") except requests.Timeout: # Something wrong with basket backend, no point in continuing, # we'd probably fail to subscribe them anyway. log.exception("Basket timeout") messages.add_message(request, messages.ERROR, general_error) return l10n_utils.render(request, "newsletter/existing.html") else: user_exists = True if not user_exists: # Bad or no token messages.add_message(request, messages.ERROR, bad_token) # If we don't pass the template a formset, most of the page won't # be displayed. Our message still will be displayed. return l10n_utils.render(request, "newsletter/existing.html", {}) # Get the newsletter data - it's a dictionary of dictionaries newsletter_data = utils.get_newsletters() # Figure out which newsletters to display, and whether to show them # as already subscribed. initial = [] for newsletter, data in newsletter_data.iteritems(): # Only show a newsletter if it has ['show'] == True or the # user is already subscribed if data.get("show", False) or newsletter in user["newsletters"]: langs = data["languages"] initial.append( { "title": data["title"], "subscribed": newsletter in user["newsletters"], "newsletter": newsletter, "description": data["description"], "english_only": len(langs) == 1 and langs[0].startswith("en"), "order": data["order"], } ) # Sort by 'order' field if we were given it; otherwise, by title if initial: keyfield = "order" if "order" in initial[0] else "title" initial.sort(key=itemgetter(keyfield)) NewsletterFormSet = formset_factory(NewsletterForm, extra=0, max_num=len(initial)) if request.method == "POST": form_kwargs = {} # Temporary form so we can see if they checked 'remove_all'. If # they did, no point in validating the newsletters formset and it would # look dumb to complain about it. form = ManageSubscriptionsForm(locale, data=request.POST, initial=user) remove_all = form.is_valid() and form.cleaned_data["remove_all"] formset_is_valid = False if remove_all: # We don't care about the newsletter formset formset_is_valid = True # Make an initialized one in case we fall through to the bottom formset = NewsletterFormSet(initial=initial) else: # We do need to validate the newsletter formset formset = NewsletterFormSet(request.POST, initial=initial) # Set `newsletters` to the list of newsletters they want. # After this, we don't need the formset anymore. newsletters = None if formset.is_valid(): formset_is_valid = True # What newsletters do they say they want to be subscribed to? newsletters = set( [subform.cleaned_data["newsletter"] for subform in formset if subform.cleaned_data["subscribed"]] ) form_kwargs["newsletters"] = newsletters form = ManageSubscriptionsForm(locale, data=request.POST, initial=user, **form_kwargs) if formset_is_valid and form.is_valid(): data = form.cleaned_data # Update their format and locale information, if it has changed. # Also pass their updated list of newsletters they want to be # subscribed to, for basket to implement. kwargs = {} for k in ["lang", "format", "country"]: if user[k] != data[k]: kwargs[k] = data[k] if not remove_all: kwargs["newsletters"] = ",".join(newsletters) if kwargs: try: basket.update_user(token, **kwargs) except basket.BasketException: log.exception("Error updating user in basket") messages.add_message(request, messages.ERROR, general_error) return l10n_utils.render(request, "newsletter/existing.html") messages.add_message(request, messages.INFO, thank_you) # If they chose to remove all, tell basket that they've opted out if remove_all: try: basket.unsubscribe(token, user["email"], optout=True) except (basket.BasketException, requests.Timeout): log.exception("Error updating subscriptions in basket") messages.add_message(request, messages.ERROR, general_error) return l10n_utils.render(request, "newsletter/existing.html") if not kwargs: # We haven't told them thank you yet messages.add_message(request, messages.INFO, thank_you) # We need to pass their token to the next view url = reverse("newsletter.updated") + "?unsub=%s&token=%s" % (UNSUB_UNSUBSCRIBED_ALL, token) return redirect(url) # We're going to redirect, so the only way to tell the next # view that we should display the welcome message in the # template is to modify the URL url = reverse("newsletter.updated") if unsub_parm: url += "?unsub=%s" % unsub_parm return redirect(url) # FALL THROUGH so page displays errors else: form = ManageSubscriptionsForm(locale, initial=user) formset = NewsletterFormSet(initial=initial) # For the template, we want a dictionary whose keys are language codes # and each value is the list of newsletter keys that are available in # that language code. newsletter_languages = defaultdict(list) for newsletter, data in newsletter_data.iteritems(): for lang in data["languages"]: newsletter_languages[lang].append(newsletter) newsletter_languages = mark_safe(json.dumps(newsletter_languages)) # We also want a list of the newsletters the user is already subscribed # to already_subscribed = mark_safe(json.dumps(user["newsletters"])) context = { "form": form, "formset": formset, "newsletter_languages": newsletter_languages, "newsletters_subscribed": already_subscribed, "email": user["email"], } return l10n_utils.render(request, "newsletter/existing.html", context)