Skip to content

Commit

Permalink
Merge d883ea2 into 848a4ec
Browse files Browse the repository at this point in the history
  • Loading branch information
alexgibson committed May 26, 2015
2 parents 848a4ec + d883ea2 commit 8319adc
Show file tree
Hide file tree
Showing 22 changed files with 1,430 additions and 25 deletions.
76 changes: 76 additions & 0 deletions bedrock/base/templates/macros.html
Expand Up @@ -642,3 +642,79 @@ <h3>{{ cta_text }}</h3>
</div>{#-- /.container --#}
</header>{#-- /#fxfamilynav-header --#}
{%- endmacro %}

{% macro send_to_device(platform, include_title=True, title_text='', include_logo=False) %}
{% set ios_link = '#' %}
{% set android_link = settings.GOOGLE_PLAY_FIREFOX_LINK %}
<section id="send-to-device" class="{% if include_logo %}logo {% endif %}{% if include_title %}title{% else %}no-title{% endif %}">
<div class="form-container">
{% if include_title %}
<h2 class="form-heading">
{% if title_text %}
{{ title_text }}
{% else %}
{{ _('Send Firefox to your smartphone or tablet') }}
{% endif %}
</h2>
{% endif %}
<h2 class="thank-you hidden">{{ _('Your download link was sent.') }}</h2>
<form id="send-to-device-form" action="{{ secure_url('firefox.send-to-device-post') }}" method="post"{% if platform == 'select' %} class="dropdown"{% endif %}>
<ul class="error-list hidden">
<li class="sms">{{ _('Sorry. This number isn’t valid. Please enter a U.S. phone number.') }}</li>
<li class="email">{{ _('Please enter an email address.') }}</li>
<li class="platform">{{ _('Please make a selection before proceeding.') }}</li>
<li class="system">{{ _('An error occurred in our system. Please try again later.') }}</li>
</ul>
<div class="input">
<div class="platform-container">
{% if platform == 'select' %}
{# User selects OS they would like to receive link for. #}
<label for="id-platform">
{{ _('Select iOS or Android') }}
</label>
<select id="id-platform" name="platform" autocomplete="off" required>
<option value="" selected>{{ _('Choose OS') }}</option>
<option value="ios">iOS</option>
<option value="android">Android</option>
</select>
{% else %}
<input type="hidden" id="id-platform" name="platform" value="{{ platform }}">
{% endif %}
<input type="hidden" name="source-url" value="{{ secure_url() }}">
</div>
<div class="inline-field">
<label id="form-input-label" for="id-input" data-alt="{{ _('Enter your email or 10-digit phone number') }}">{{ _('Enter your email') }}</label>
<div class="form-input">
<input id="id-input" name="phone-or-email" type="text" required>
</div>
<div class="form-submit">
<button type="submit" class="button-flat">{{ _('Send') }}</button>
</div>
</div>
<p class="legal sms">
{{ _('SMS service available to U.S. phone numbers only. SMS &amp; data rates may apply.')}} {{ _('The intended recipient of the email or SMS must have consented.')}} <a href="{{ url('privacy.notices.websites') }}#campaigns" class="more">{{ _('Learn more')}}</a>
</p>
<p class="legal email">
{{ _('The intended recipient of the email must have consented.')}} <a href="{{ url('privacy.notices.websites') }}#campaigns" class="more">{{ _('Learn more')}}</a>
</p>
</div>
<div class="thank-you hidden">
<p class="sms">{{ _('Check your device for the email or text message!') }}</p>
<p class="email">{{ _('Check your device for the email!') }}</p>
<a href="#" role="button" class="more send-another">{{ _('Send to another device') }}</a>
</div>
<div class="loading-spinner"></div>
</form>
</div>
<footer>
<ul class="{{ platform }}">
<li class="app-store">
<a class="more" href="{{ ios_link }}">{{ _('Go to the App Store')}}</a>
</li>
<li class="google-play">
<a class="more" href="{{ android_link }}">{{ _('Go to Google Play')}}</a>
</li>
</ul>
</footer>
</section>
{%- endmacro %}
55 changes: 41 additions & 14 deletions bedrock/firefox/forms.py
Expand Up @@ -5,25 +5,52 @@
import re

from django import forms
from django.core.validators import EMPTY_VALUES
from django.utils.encoding import smart_text

from lib.l10n_utils.dotlang import _
from lib.l10n_utils.dotlang import _lazy as _

LANG_FILES = ['firefox/whatsnew-fx37']


class USPhoneNumberField(forms.CharField):
"""
A form field that validates input as a U.S. phone number.
"""
default_error_messages = {
'invalid': _("Sorry. This number isn't valid. Please enter a U.S. phone "
'number or <a href="%s">'
'download directly from Google Play.</a>') % 'http://mzl.la/OgZo6k',
}

def __init__(self, *args, **kwargs):
kwargs.setdefault('max_length', 14)
super(USPhoneNumberField, self).__init__(*args, **kwargs)

def clean(self, value):
super(USPhoneNumberField, self).clean(value)
if value in EMPTY_VALUES:
return ''

value = re.sub(r'\D+', '', smart_text(value))
if len(value) == 10:
value = '1' + value
elif len(value) != 11 or value[0] != '1':
raise forms.ValidationError(self.error_messages['invalid'])

return value


class SMSSendForm(forms.Form):
number = forms.CharField(max_length=14)
number = USPhoneNumberField()
optin = forms.BooleanField(required=False)

def clean_number(self):
mobile = self.cleaned_data['number']
mobile = re.sub(r'\D+', '', mobile)
if len(mobile) == 10:
mobile = '1' + mobile
elif len(mobile) != 11 or mobile[0] != '1':
raise forms.ValidationError(_(
'Sorry. This number isn\'t valid. Please enter a U.S. phone '
'number or <a href="%s">'
'download directly from Google Play.</a>'
) % ('http://mzl.la/OgZo6k'))
return mobile

class SendToDeviceWidgetForm(forms.Form):
number = USPhoneNumberField(required=False)
email = forms.EmailField(max_length=100, required=False)
platform = forms.ChoiceField(choices=(
('ios', 'ios'),
('android', 'android'),
('all', 'all'),
))
84 changes: 84 additions & 0 deletions bedrock/firefox/tests/test_views.py
Expand Up @@ -24,6 +24,90 @@
}


class TestSendToDeviceView(TestCase):
def setUp(self):
patcher = patch('bedrock.firefox.views.basket.subscribe')
self.mock_subscribe = patcher.start()
self.addCleanup(patcher.stop)

patcher = patch('bedrock.firefox.views.basket.send_sms')
self.mock_send_sms = patcher.start()
self.addCleanup(patcher.stop)

def _request(self, data, expected_status=200):
rf = RequestFactory()
resp = views.send_to_device_ajax(rf.post('/', data))
eq_(resp.status_code, expected_status)
return json.loads(resp.content)

def test_phone_or_email_required(self):
resp_data = self._request({
'platform': 'android',
})
ok_(not resp_data['success'])
ok_('phone-or-email' in resp_data['errors'])
ok_(not self.mock_send_sms.called)
ok_(not self.mock_subscribe.called)

def test_send_android_sms(self):
resp_data = self._request({
'platform': 'android',
'phone-or-email': '5558675309',
})
ok_(resp_data['success'])
self.mock_send_sms.assert_called_with('15558675309', views.SMS_MESSAGES['android'])

def test_send_android_sms_basket_error(self):
self.mock_send_sms.side_effect = views.basket.BasketException
resp_data = self._request({
'platform': 'android',
'phone-or-email': '5558675309',
}, 400)
ok_(not resp_data['success'])
ok_('system' in resp_data['errors'])

def test_send_bad_sms_number(self):
resp_data = self._request({
'platform': 'android',
'phone-or-email': '555',
})
ok_(not resp_data['success'])
ok_('number' in resp_data['errors'])
ok_(not self.mock_send_sms.called)

def test_send_android_email(self):
resp_data = self._request({
'platform': 'android',
'phone-or-email': 'dude@example.com',
'source-url': 'https://nihilism.info',
})
ok_(resp_data['success'])
self.mock_subscribe.assert_called_with('dude@example.com',
views.EMAIL_MESSAGES['android'],
source_url='https://nihilism.info',
lang='en-US')

def test_send_android_email_basket_error(self):
self.mock_subscribe.side_effect = views.basket.BasketException
resp_data = self._request({
'platform': 'android',
'phone-or-email': 'dude@example.com',
'source-url': 'https://nihilism.info',
}, 400)
ok_(not resp_data['success'])
ok_('system' in resp_data['errors'])

def test_send_android_bad_email(self):
resp_data = self._request({
'platform': 'android',
'phone-or-email': '@example.com',
'source-url': 'https://nihilism.info',
})
ok_(not resp_data['success'])
ok_('email' in resp_data['errors'])
ok_(not self.mock_subscribe.called)


class TestFirefoxNew(TestCase):
def test_frames_allow(self):
"""
Expand Down
2 changes: 2 additions & 0 deletions bedrock/firefox/urls.py
Expand Up @@ -45,6 +45,8 @@
page('firefox/android/faq', 'firefox/android/faq.html'),
page('firefox/os/faq', 'firefox/os/faq.html'),
page('firefox/products', 'firefox/family/index.html'),
url('^firefox/send-to-device-post/$', views.send_to_device_ajax,
name='firefox.send-to-device-post'),
url('^firefox/sms/$', views.sms_send, name='firefox.sms'),
page('firefox/sms/sent', 'firefox/android/sms-thankyou.html'),
page('firefox/sync', 'firefox/sync.html'),
Expand Down
66 changes: 64 additions & 2 deletions bedrock/firefox/views.py
Expand Up @@ -12,6 +12,7 @@
from django.http import HttpResponsePermanentRedirect, HttpResponseRedirect
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.views.decorators.http import require_POST
from django.views.decorators.vary import vary_on_headers
from django.views.generic.base import TemplateView

Expand All @@ -24,7 +25,7 @@

from bedrock.base.geo import get_country_from_request
from bedrock.firefox.firefox_details import firefox_desktop
from bedrock.firefox.forms import SMSSendForm
from bedrock.firefox.forms import SMSSendForm, SendToDeviceWidgetForm
from bedrock.mozorg.context_processors import funnelcake_param
from bedrock.mozorg.views import process_partnership_form
from bedrock.mozorg.util import HttpResponseJSON
Expand Down Expand Up @@ -100,6 +101,16 @@
# 'nightly', # soon
]

SMS_MESSAGES = {
'android': 'SMS_Android',
}

EMAIL_MESSAGES = {
'android': 'download-firefox-android',
'ios': 'download-firefox-ios',
'all': 'download-firefox-mobile',
}


def get_js_bundle_files(bundle):
"""
Expand Down Expand Up @@ -145,7 +156,7 @@ def sms_send(request):
if form.is_valid():
try:
basket.send_sms(form.cleaned_data['number'],
'SMS_Android',
SMS_MESSAGES['android'],
form.cleaned_data['optin'])
except basket.BasketException:
error = error_msg
Expand Down Expand Up @@ -179,6 +190,57 @@ def sms_send(request):
{'sms_form': form})


@require_POST
@csrf_exempt
def send_to_device_ajax(request):
locale = l10n_utils.get_locale(request)
phone_or_email = request.POST.get('phone-or-email')
if not phone_or_email:
return HttpResponseJSON({'success': False, 'errors': ['phone-or-email']})

data = {
'platform': request.POST.get('platform'),
}

data_type = 'email' if '@' in phone_or_email else 'number'
data[data_type] = phone_or_email
form = SendToDeviceWidgetForm(data)
if form.is_valid():
phone_or_email = form.cleaned_data.get(data_type)
platform = form.cleaned_data.get('platform')
if data_type == 'number':
if platform in SMS_MESSAGES:
try:
basket.send_sms(phone_or_email, SMS_MESSAGES[platform])
except basket.BasketException:
return HttpResponseJSON({'success': False, 'errors': ['system']},
status=400)
else:
# TODO define all platforms in SMS_MESSAGES
return HttpResponseJSON({'success': False, 'errors': ['platform']})
else: # email
if platform in EMAIL_MESSAGES:
try:
basket.subscribe(phone_or_email, EMAIL_MESSAGES[platform],
source_url=request.POST.get('source-url'),
lang=locale)
except basket.BasketException:
return HttpResponseJSON({'success': False, 'errors': ['system']},
status=400)
else:
# TODO define all platforms in EMAIL_MESSAGES
return HttpResponseJSON({'success': False, 'errors': ['platform']})

resp_data = {'success': True}
else:
resp_data = {
'success': False,
'errors': form.errors.keys(),
}

return HttpResponseJSON(resp_data)


def windows_billboards(req):
major_version = req.GET.get('majorVersion')
minor_version = req.GET.get('minorVersion')
Expand Down
5 changes: 5 additions & 0 deletions bedrock/settings/base.py
Expand Up @@ -701,6 +701,11 @@ def facebook_tab_url_lazy():
urlquote('utm_source=mozilla&utm_medium=Referral&'
'utm_campaign=mozilla-org'))

# Locales that should display the 'Send to Device' widget
SEND_TO_DEVICE_LOCALES = ['de', 'en-GB', 'en-US', 'en-ZA',
'es-AR', 'es-CL', 'es-ES', 'es-MX',
'fr', 'hu', 'id', 'pl', 'pt-BR', 'ru']

# Use bedrock Gruntfile.js for live reload
USE_GRUNT_LIVERELOAD = False

Expand Down
14 changes: 14 additions & 0 deletions bedrock/settings/static_media.py
Expand Up @@ -805,6 +805,13 @@
),
'output_filename': 'css/styleguide-docs-mozilla-pager-bundle.css',
},
'styleguide-docs-send-to-device': {
'source_filenames': (
'css/sandstone/sandstone-resp.less',
'css/base/send-to-device.less',
),
'output_filename': 'css/styleguide-docs-send-to-device-bundle.css',
},
'tabzilla': {
'source_filenames': (
'css/tabzilla/tabzilla.less',
Expand Down Expand Up @@ -1565,6 +1572,13 @@
),
'output_filename': 'js/styleguide-docs-mozilla-pager-bundle.js',
},
'styleguide-docs-send-to-device': {
'source_filenames': (
'js/base/send-to-device.js',
'js/styleguide/docs/send-to-device.js',
),
'output_filename': 'js/styleguide-docs-send-to-device-bundle.js',
},
'video': {
'source_filenames': (
'js/base/mozilla-video-tools.js',
Expand Down

0 comments on commit 8319adc

Please sign in to comment.