This commit is contained in:
Rob Hudson 2021-09-30 09:44:54 -07:00 коммит произвёл Paul McLanahan
Родитель e8325a7944
Коммит 31943fea75
454 изменённых файлов: 21017 добавлений и 22948 удалений

Просмотреть файл

@ -7,6 +7,7 @@ class SimpleDictCache(LocMemCache):
Only for use with simple immutable data structures that can be
inserted into a dict.
"""
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
key = self.make_key(key, version=version)
self.validate_key(key)

Просмотреть файл

@ -5,9 +5,11 @@ from everett.manager import (
)
config = ConfigManager([
# first check for environment variables
ConfigOSEnv(),
# then look in the .env file
ConfigEnvFileEnv('.env'),
])
config = ConfigManager(
[
# first check for environment variables
ConfigOSEnv(),
# then look in the .env file
ConfigEnvFileEnv(".env"),
]
)

Просмотреть файл

@ -5,20 +5,19 @@ from lib.l10n_utils import translation
def geo(request):
return {'country_code': get_country_from_request(request)}
return {"country_code": get_country_from_request(request)}
def i18n(request):
return {
'LANGUAGES': settings.LANGUAGES,
'LANG': (settings.LANGUAGE_URL_MAP.get(
translation.get_language()) or translation.get_language()),
'DIR': 'rtl' if translation.get_language_bidi() else 'ltr',
"LANGUAGES": settings.LANGUAGES,
"LANG": (settings.LANGUAGE_URL_MAP.get(translation.get_language()) or translation.get_language()),
"DIR": "rtl" if translation.get_language_bidi() else "ltr",
}
def globals(request):
return {
'request': request,
'settings': settings,
"request": request,
"settings": settings,
}

Просмотреть файл

@ -8,20 +8,20 @@ from product_details import product_details
def valid_country_code(country):
codes = product_details.get_regions('en-US').keys()
codes = product_details.get_regions("en-US").keys()
if country and country.lower() in codes:
return country.upper()
def get_country_from_param(request):
is_prod = request.get_host() == 'www.mozilla.org'
country_code = valid_country_code(request.GET.get('geo'))
is_prod = request.get_host() == "www.mozilla.org"
country_code = valid_country_code(request.GET.get("geo"))
return country_code if not is_prod else None
def get_country_from_header(request):
"""Return an uppercase 2 letter country code retrieved from the request header."""
country_code = valid_country_code(request.META.get('HTTP_CF_IPCOUNTRY'))
country_code = valid_country_code(request.META.get("HTTP_CF_IPCOUNTRY"))
if not country_code and settings.DEV:
country_code = settings.DEV_GEO_COUNTRY_CODE

Просмотреть файл

@ -14,89 +14,86 @@ class NullHandler(logging.Handler):
pass
base_fmt = ('%(name)s:%(levelname)s %(message)s '
':%(pathname)s:%(lineno)s')
base_fmt = "%(name)s:%(levelname)s %(message)s :%(pathname)s:%(lineno)s"
use_syslog = settings.HAS_SYSLOG and not settings.DEBUG
if use_syslog:
hostname = socket.gethostname()
else:
hostname = 'localhost'
hostname = "localhost"
cfg = {
'version': 1,
'filters': {},
'formatters': {
'debug': {
'()': commonware.log.Formatter,
'datefmt': '%H:%M:%s',
'format': '%(asctime)s ' + base_fmt,
"version": 1,
"filters": {},
"formatters": {
"debug": {
"()": commonware.log.Formatter,
"datefmt": "%H:%M:%s",
"format": "%(asctime)s " + base_fmt,
},
'prod': {
'()': commonware.log.Formatter,
'datefmt': '%H:%M:%s',
'format': '%s %s: [%%(REMOTE_ADDR)s] %s' % (hostname,
settings.SYSLOG_TAG,
base_fmt),
"prod": {
"()": commonware.log.Formatter,
"datefmt": "%H:%M:%s",
"format": "%s %s: [%%(REMOTE_ADDR)s] %s" % (hostname, settings.SYSLOG_TAG, base_fmt),
},
'cef': {
'()': cef.SysLogFormatter,
'datefmt': '%H:%M:%s',
"cef": {
"()": cef.SysLogFormatter,
"datefmt": "%H:%M:%s",
},
},
'handlers': {
'console': {
'()': logging.StreamHandler,
'formatter': 'debug',
"handlers": {
"console": {
"()": logging.StreamHandler,
"formatter": "debug",
},
'syslog': {
'()': logging.handlers.SysLogHandler,
'facility': logging.handlers.SysLogHandler.LOG_LOCAL7,
'formatter': 'prod',
"syslog": {
"()": logging.handlers.SysLogHandler,
"facility": logging.handlers.SysLogHandler.LOG_LOCAL7,
"formatter": "prod",
},
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
"mail_admins": {
"level": "ERROR",
"class": "django.utils.log.AdminEmailHandler",
},
'cef_syslog': {
'()': logging.handlers.SysLogHandler,
'facility': logging.handlers.SysLogHandler.LOG_LOCAL4,
'formatter': 'cef',
"cef_syslog": {
"()": logging.handlers.SysLogHandler,
"facility": logging.handlers.SysLogHandler.LOG_LOCAL4,
"formatter": "cef",
},
'cef_console': {
'()': logging.StreamHandler,
'formatter': 'cef',
"cef_console": {
"()": logging.StreamHandler,
"formatter": "cef",
},
"null": {
"()": NullHandler,
},
'null': {
'()': NullHandler,
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False,
"loggers": {
"django.request": {
"handlers": ["mail_admins"],
"level": "ERROR",
"propagate": False,
},
"cef": {
"handlers": ["cef_syslog" if use_syslog else "cef_console"],
},
'cef': {
'handlers': ['cef_syslog' if use_syslog else 'cef_console'],
}
},
'root': {},
"root": {},
}
for key, value in settings.LOGGING.items():
if hasattr(cfg[key], 'update'):
if hasattr(cfg[key], "update"):
cfg[key].update(value)
else:
cfg[key] = value
# Set the level and handlers for all loggers.
for logger in list(cfg['loggers'].values()) + [cfg['root']]:
if 'handlers' not in logger:
logger['handlers'] = ['syslog' if use_syslog else 'console']
if 'level' not in logger:
logger['level'] = settings.LOG_LEVEL
if logger is not cfg['root'] and 'propagate' not in logger:
logger['propagate'] = False
for logger in list(cfg["loggers"].values()) + [cfg["root"]]:
if "handlers" not in logger:
logger["handlers"] = ["syslog" if use_syslog else "console"]
if "level" not in logger:
logger["level"] = settings.LOG_LEVEL
if logger is not cfg["root"] and "propagate" not in logger:
logger["propagate"] = False
dictconfig.dictConfig(cfg)

Просмотреть файл

@ -1,4 +1,3 @@
import os
from django.conf import settings
@ -11,8 +10,8 @@ from bedrock.utils.git import GitRepo
def get_config_file_name(app_name=None):
app_name = app_name or settings.APP_NAME or 'bedrock-dev'
return os.path.join(settings.WWW_CONFIG_PATH, 'waffle_configs', '%s.env' % app_name)
app_name = app_name or settings.APP_NAME or "bedrock-dev"
return os.path.join(settings.WWW_CONFIG_PATH, "waffle_configs", "%s.env" % app_name)
def get_config_values():
@ -36,34 +35,31 @@ def refresh_db_values():
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False,
help='If no error occurs, swallow all output.'),
parser.add_argument('-f', '--force', action='store_true', dest='force', default=False,
help='Load the data even if nothing new from git.'),
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="If no error occurs, swallow all output."),
parser.add_argument("-f", "--force", action="store_true", dest="force", default=False, help="Load the data even if nothing new from git."),
def output(self, msg):
if not self.quiet:
print(msg)
def handle(self, *args, **options):
self.quiet = options['quiet']
repo = GitRepo(settings.WWW_CONFIG_PATH, settings.WWW_CONFIG_REPO,
branch_name=settings.WWW_CONFIG_BRANCH, name='WWW Config')
self.output('Updating git repo')
self.quiet = options["quiet"]
repo = GitRepo(settings.WWW_CONFIG_PATH, settings.WWW_CONFIG_REPO, branch_name=settings.WWW_CONFIG_BRANCH, name="WWW Config")
self.output("Updating git repo")
repo.update()
if not (options['force'] or repo.has_changes()):
self.output('No config updates')
if not (options["force"] or repo.has_changes()):
self.output("No config updates")
return
self.output('Loading configs into database')
self.output("Loading configs into database")
count = refresh_db_values()
if count:
self.output('%s configs successfully loaded' % count)
self.output("%s configs successfully loaded" % count)
else:
self.output('No configs found. Please try again later.')
self.output("No configs found. Please try again later.")
repo.set_db_latest()
self.output('Saved latest git repo state to database')
self.output('Done!')
self.output("Saved latest git repo state to database")
self.output("Done!")

Просмотреть файл

@ -29,9 +29,11 @@ class LocaleURLMiddleware:
def __init__(self, get_response=None):
if not settings.USE_I18N or not settings.USE_L10N:
warn("USE_I18N or USE_L10N is False but LocaleURLMiddleware is "
"loaded. Consider removing bedrock.base.middleware."
"LocaleURLMiddleware from your MIDDLEWARE setting.")
warn(
"USE_I18N or USE_L10N is False but LocaleURLMiddleware is "
"loaded. Consider removing bedrock.base.middleware."
"LocaleURLMiddleware from your MIDDLEWARE setting."
)
self.get_response = get_response
def __call__(self, request):
@ -46,12 +48,11 @@ class LocaleURLMiddleware:
full_path = prefixer.fix(prefixer.shortened_path)
if not (request.path in settings.SUPPORTED_LOCALE_IGNORE or full_path == request.path):
query_string = request.META.get('QUERY_STRING', '')
full_path = urllib.parse.quote(full_path.encode('utf-8'))
query_string = request.META.get("QUERY_STRING", "")
full_path = urllib.parse.quote(full_path.encode("utf-8"))
if query_string:
full_path = '?'.join(
[full_path, unquote(query_string, errors='ignore')])
full_path = "?".join([full_path, unquote(query_string, errors="ignore")])
response = HttpResponsePermanentRedirect(full_path)
@ -59,11 +60,11 @@ class LocaleURLMiddleware:
old_locale = prefixer.locale
new_locale, _ = urlresolvers.split_path(full_path)
if old_locale != new_locale:
response['Vary'] = 'Accept-Language'
response["Vary"] = "Accept-Language"
return response
request.path_info = '/' + prefixer.shortened_path
request.path_info = "/" + prefixer.shortened_path
request.locale = prefixer.locale
translation.activate(prefixer.locale or settings.LANGUAGE_CODE)
@ -73,6 +74,7 @@ class BasicAuthMiddleware:
Middleware to protect the entire site with a single basic-auth username and password.
Set the BASIC_AUTH_CREDS environment variable to enable.
"""
def __init__(self, get_response=None):
if not settings.BASIC_AUTH_CREDS:
raise MiddlewareNotUsed
@ -87,8 +89,8 @@ class BasicAuthMiddleware:
def process_request(self, request):
required_auth = settings.BASIC_AUTH_CREDS
if required_auth:
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if "HTTP_AUTHORIZATION" in request.META:
auth = request.META["HTTP_AUTHORIZATION"].split()
if len(auth) == 2:
if auth[0].lower() == "basic":
provided_auth = base64.b64decode(auth[1])
@ -96,11 +98,9 @@ class BasicAuthMiddleware:
# we're good. continue on.
return None
response = HttpResponse(status=401,
content='<h1>Unauthorized. '
'This site is in private demo mode.</h1>')
realm = settings.APP_NAME or 'bedrock-demo'
response['WWW-Authenticate'] = 'Basic realm="{}"'.format(realm)
response = HttpResponse(status=401, content="<h1>Unauthorized. This site is in private demo mode.</h1>")
realm = settings.APP_NAME or "bedrock-demo"
response["WWW-Authenticate"] = 'Basic realm="{}"'.format(realm)
return response

Просмотреть файл

@ -6,10 +6,10 @@ class ConfigValue(models.Model):
value = models.CharField(max_length=200)
class Meta:
app_label = 'base'
app_label = "base"
def __str__(self):
return '%s=%s' % (self.name, self.value)
return "%s=%s" % (self.name, self.value)
def get_config_dict():

Просмотреть файл

@ -40,7 +40,7 @@ def switch(cxt, name, locales=None):
which is a list of locales for a prefix (e.g. "en" expands to "en-US, en-GB").
"""
if locales:
if cxt['LANG'] not in expand_locale_groups(locales):
if cxt["LANG"] not in expand_locale_groups(locales):
return False
return waffle.switch(name)
@ -73,10 +73,8 @@ def urlparams(url_, hash=None, **query):
query_dict = dict(urllib.parse.parse_qsl(smart_str(q))) if q else {}
query_dict.update((k, v) for k, v in query.items())
query_string = _urlencode([(k, v) for k, v in query_dict.items()
if v is not None])
new = urllib.parse.ParseResult(
url.scheme, url.netloc, url.path, url.params, query_string, fragment)
query_string = _urlencode([(k, v) for k, v in query_dict.items() if v is not None])
new = urllib.parse.ParseResult(url.scheme, url.netloc, url.path, url.params, query_string, fragment)
return new.geturl()
@ -92,7 +90,7 @@ def _urlencode(items):
def mailtoencode(txt):
"""Url encode a string using %20 for spaces."""
if isinstance(txt, str):
txt = txt.encode('utf-8')
txt = txt.encode("utf-8")
return urllib.parse.quote(txt)
@ -100,14 +98,14 @@ def mailtoencode(txt):
def urlencode(txt):
"""Url encode a string using + for spaces."""
if isinstance(txt, str):
txt = txt.encode('utf-8')
txt = txt.encode("utf-8")
return urllib.parse.quote_plus(txt)
@library.global_function
def static(path):
if settings.DEBUG and path.startswith('/'):
raise ValueError('Static paths must not begin with a slash')
if settings.DEBUG and path.startswith("/"):
raise ValueError("Static paths must not begin with a slash")
try:
return staticfiles_storage.url(path)
@ -122,7 +120,7 @@ def js_bundle(name):
Bundles are defined in the "media/static-bundles.json" file.
"""
path = 'js/{}.js'.format(name)
path = "js/{}.js".format(name)
path = staticfiles_storage.url(path)
return jinja2.Markup(JS_TEMPLATE % path)
@ -133,7 +131,7 @@ def css_bundle(name):
Bundles are defined in the "media/static-bundles.json" file.
"""
path = 'css/{}.css'.format(name)
path = "css/{}.css".format(name)
path = staticfiles_storage.url(path)
return jinja2.Markup(CSS_TEMPLATE % path)
@ -141,7 +139,7 @@ def css_bundle(name):
@library.global_function
def alternate_url(path, locale):
alt_paths = settings.ALT_CANONICAL_PATHS
path = path.lstrip('/')
path = path.lstrip("/")
if path in alt_paths and locale in alt_paths[path]:
return alt_paths[path][locale]
@ -157,10 +155,9 @@ def get_donate_params(ctx):
:returns: dictionary of donation values, including list of amount presets
"""
donate_params = settings.DONATE_PARAMS.get(
ctx['LANG'], settings.DONATE_PARAMS['en-US'])
donate_params = settings.DONATE_PARAMS.get(ctx["LANG"], settings.DONATE_PARAMS["en-US"])
# presets are stored as a string but we need a list for views
donate_params['preset_list'] = donate_params['presets'].split(',')
donate_params["preset_list"] = donate_params["presets"].split(",")
return donate_params

Просмотреть файл

@ -15,8 +15,9 @@ class AcceptedLocalesTest(TestCase):
DEV_LANGUAGES or PROD_LANGUAGES should be used.
"""
locale = data_path('www-l10n')
locale_bkp = data_path('www-l10n_bkp')
locale = data_path("www-l10n")
locale_bkp = data_path("www-l10n_bkp")
@classmethod
def setup_class(cls):
@ -34,20 +35,22 @@ class AcceptedLocalesTest(TestCase):
"""
if os.path.exists(cls.locale_bkp):
raise Exception('A backup of locale/ exists at %s which might '
'mean that previous tests didn\'t end cleanly. '
'Skipping the test suite.' % cls.locale_bkp)
raise Exception(
"A backup of locale/ exists at %s which might "
"mean that previous tests didn't end cleanly. "
"Skipping the test suite." % cls.locale_bkp
)
cls.DEV = settings.DEV
cls.PROD_LANGUAGES = settings.PROD_LANGUAGES
cls.DEV_LANGUAGES = settings.DEV_LANGUAGES
settings.PROD_LANGUAGES = ('en-US',)
settings.PROD_LANGUAGES = ("en-US",)
if os.path.exists(cls.locale):
shutil.move(cls.locale, cls.locale_bkp)
else:
cls.locale_bkp = None
for loc in ('en-US', 'fr', 'metadata'):
for loc in ("en-US", "fr", "metadata"):
os.makedirs(os.path.join(cls.locale, loc))
open(os.path.join(cls.locale, 'empty_file'), 'w').close()
open(os.path.join(cls.locale, "empty_file"), "w").close()
@classmethod
def teardown_class(cls):
@ -69,8 +72,7 @@ class AcceptedLocalesTest(TestCase):
"""
settings.DEV = True
langs = get_dev_languages()
assert langs == ['en-US', 'fr'] or langs == ['fr', 'en-US'], (
'DEV_LANGUAGES do not correspond to the contents of locale/.')
assert langs == ["en-US", "fr"] or langs == ["fr", "en-US"], "DEV_LANGUAGES do not correspond to the contents of locale/."
def test_dev_languages(self):
"""Test the accepted locales on dev instances.
@ -81,10 +83,11 @@ class AcceptedLocalesTest(TestCase):
settings.DEV = True
# simulate the successful result of the DEV_LANGUAGES list
# comprehension defined in settings.
settings.DEV_LANGUAGES = ['en-US', 'fr']
assert settings.LANGUAGE_URL_MAP == {'en-us': 'en-US', 'fr': 'fr'}, (
'DEV is True, but DEV_LANGUAGES are not used to define the '
'allowed locales.')
settings.DEV_LANGUAGES = ["en-US", "fr"]
assert settings.LANGUAGE_URL_MAP == {
"en-us": "en-US",
"fr": "fr",
}, "DEV is True, but DEV_LANGUAGES are not used to define the allowed locales."
def test_prod_languages(self):
"""Test the accepted locales on prod instances.
@ -93,6 +96,4 @@ class AcceptedLocalesTest(TestCase):
"""
settings.DEV = False
assert settings.LANGUAGE_URL_MAP == {'en-us': 'en-US'}, (
'DEV is False, but PROD_LANGUAGES are not used to define the '
'allowed locales.')
assert settings.LANGUAGE_URL_MAP == {"en-us": "en-US"}, "DEV is False, but PROD_LANGUAGES are not used to define the allowed locales."

Просмотреть файл

@ -7,63 +7,60 @@ from lib.l10n_utils import translation
class TestContext(TestCase):
def setUp(self):
translation.activate('en-US')
translation.activate("en-US")
self.factory = RequestFactory()
translation.activate('en-US')
translation.activate("en-US")
def render(self, content, request=None):
if not request:
request = self.factory.get('/')
request = self.factory.get("/")
tpl = jinja2.Template(content)
return render_to_string(tpl, request=request)
def test_request(self):
assert self.render('{{ request.path }}') == '/'
assert self.render("{{ request.path }}") == "/"
def test_settings(self):
assert self.render('{{ settings.LANGUAGE_CODE }}') == 'en-US'
assert self.render("{{ settings.LANGUAGE_CODE }}") == "en-US"
def test_languages(self):
assert self.render("{{ LANGUAGES['en-us'] }}") == 'English (US)'
assert self.render("{{ LANGUAGES['en-us'] }}") == "English (US)"
def test_lang_setting(self):
assert self.render("{{ LANG }}") == 'en-US'
assert self.render("{{ LANG }}") == "en-US"
def test_lang_dir(self):
assert self.render("{{ DIR }}") == 'ltr'
assert self.render("{{ DIR }}") == "ltr"
def test_geo_header(self):
"""Country code from request header should work"""
req = self.factory.get('/', HTTP_CF_IPCOUNTRY='de')
assert self.render('{{ country_code }}', req) == 'DE'
req = self.factory.get("/", HTTP_CF_IPCOUNTRY="de")
assert self.render("{{ country_code }}", req) == "DE"
@override_settings(DEV=False)
def test_geo_no_header(self):
"""Country code when header absent should be None"""
req = self.factory.get('/')
assert self.render('{{ country_code }}', req) == 'None'
req = self.factory.get("/")
assert self.render("{{ country_code }}", req) == "None"
def test_geo_param(self):
"""Country code from header should be overridden by query param
for pre-prod domains."""
req = self.factory.get('/', data={'geo': 'fr'}, HTTP_CF_IPCOUNTRY='de')
assert self.render('{{ country_code }}', req) == 'FR'
for pre-prod domains."""
req = self.factory.get("/", data={"geo": "fr"}, HTTP_CF_IPCOUNTRY="de")
assert self.render("{{ country_code }}", req) == "FR"
# should use header if at prod domain
req = self.factory.get('/', data={'geo': 'fr'},
HTTP_CF_IPCOUNTRY='de',
HTTP_HOST='www.mozilla.org')
assert self.render('{{ country_code }}', req) == 'DE'
req = self.factory.get("/", data={"geo": "fr"}, HTTP_CF_IPCOUNTRY="de", HTTP_HOST="www.mozilla.org")
assert self.render("{{ country_code }}", req) == "DE"
@override_settings(DEV=False)
def test_invalid_geo_param(self):
req = self.factory.get('/', data={'geo': 'france'}, HTTP_CF_IPCOUNTRY='de')
assert self.render('{{ country_code }}', req) == 'DE'
req = self.factory.get("/", data={"geo": "france"}, HTTP_CF_IPCOUNTRY="de")
assert self.render("{{ country_code }}", req) == "DE"
req = self.factory.get('/', data={'geo': ''}, HTTP_CF_IPCOUNTRY='de')
assert self.render('{{ country_code }}', req) == 'DE'
req = self.factory.get("/", data={"geo": ""}, HTTP_CF_IPCOUNTRY="de")
assert self.render("{{ country_code }}", req) == "DE"
req = self.factory.get('/', data={'geo': 'france'})
assert self.render('{{ country_code }}', req) == 'None'
req = self.factory.get("/", data={"geo": "france"})
assert self.render("{{ country_code }}", req) == "None"

Просмотреть файл

@ -9,34 +9,32 @@ class TestGeo(TestCase):
def test_geo_header(self):
"""Country code from request header should work"""
req = self.factory.get('/', HTTP_CF_IPCOUNTRY='de')
assert get_country_from_request(req) == 'DE'
req = self.factory.get("/", HTTP_CF_IPCOUNTRY="de")
assert get_country_from_request(req) == "DE"
@override_settings(DEV=False)
def test_geo_no_header(self):
"""Country code when header absent should be None"""
req = self.factory.get('/')
req = self.factory.get("/")
assert get_country_from_request(req) is None
def test_geo_param(self):
"""Country code from header should be overridden by query param
for pre-prod domains."""
req = self.factory.get('/', data={'geo': 'fr'}, HTTP_CF_IPCOUNTRY='de')
assert get_country_from_request(req) == 'FR'
for pre-prod domains."""
req = self.factory.get("/", data={"geo": "fr"}, HTTP_CF_IPCOUNTRY="de")
assert get_country_from_request(req) == "FR"
# should use header if at prod domain
req = self.factory.get('/', data={'geo': 'fr'},
HTTP_CF_IPCOUNTRY='de',
HTTP_HOST='www.mozilla.org')
assert get_country_from_request(req) == 'DE'
req = self.factory.get("/", data={"geo": "fr"}, HTTP_CF_IPCOUNTRY="de", HTTP_HOST="www.mozilla.org")
assert get_country_from_request(req) == "DE"
@override_settings(DEV=False)
def test_invalid_geo_param(self):
req = self.factory.get('/', data={'geo': 'france'}, HTTP_CF_IPCOUNTRY='de')
assert get_country_from_request(req) == 'DE'
req = self.factory.get("/", data={"geo": "france"}, HTTP_CF_IPCOUNTRY="de")
assert get_country_from_request(req) == "DE"
req = self.factory.get('/', data={'geo': ''}, HTTP_CF_IPCOUNTRY='de')
assert get_country_from_request(req) == 'DE'
req = self.factory.get("/", data={"geo": ""}, HTTP_CF_IPCOUNTRY="de")
assert get_country_from_request(req) == "DE"
req = self.factory.get('/', data={'geo': 'france'})
req = self.factory.get("/", data={"geo": "france"})
assert get_country_from_request(req) is None

Просмотреть файл

@ -9,11 +9,11 @@ from bedrock.base.templatetags import helpers
jinja_env = Jinja2.get_default()
SEND_TO_DEVICE_MESSAGE_SETS = {
'default': {
'email': {
'android': 'download-firefox-android',
'ios': 'download-firefox-ios',
'all': 'download-firefox-mobile',
"default": {
"email": {
"android": "download-firefox-android",
"ios": "download-firefox-ios",
"all": "download-firefox-mobile",
}
}
}
@ -27,61 +27,60 @@ def render(s, context={}):
class HelpersTests(TestCase):
def test_urlencode_with_unicode(self):
template = '<a href="?var={{ key|urlencode }}">'
context = {'key': '?& /()'}
context = {"key": "?& /()"}
assert render(template, context) == '<a href="?var=%3F%26+%2F%28%29">'
# non-ascii
context = {'key': u'\xe4'}
context = {"key": "\xe4"}
assert render(template, context) == '<a href="?var=%C3%A4">'
def test_mailtoencode_with_unicode(self):
template = '<a href="?var={{ key|mailtoencode }}">'
context = {'key': '?& /()'}
context = {"key": "?& /()"}
assert render(template, context) == '<a href="?var=%3F%26%20/%28%29">'
# non-ascii
context = {'key': u'\xe4'}
context = {"key": "\xe4"}
assert render(template, context) == '<a href="?var=%C3%A4">'
@override_settings(LANG_GROUPS={'en': ['en-US', 'en-GB']})
@override_settings(LANG_GROUPS={"en": ["en-US", "en-GB"]})
def test_switch():
with patch.object(helpers, 'waffle') as waffle:
ret = helpers.switch({'LANG': 'de'}, 'dude', ['fr', 'de'])
with patch.object(helpers, "waffle") as waffle:
ret = helpers.switch({"LANG": "de"}, "dude", ["fr", "de"])
assert ret is waffle.switch.return_value
waffle.switch.assert_called_with('dude')
waffle.switch.assert_called_with("dude")
with patch.object(helpers, 'waffle') as waffle:
assert not helpers.switch({'LANG': 'de'}, 'dude', ['fr', 'en'])
with patch.object(helpers, "waffle") as waffle:
assert not helpers.switch({"LANG": "de"}, "dude", ["fr", "en"])
waffle.switch.assert_not_called()
with patch.object(helpers, 'waffle') as waffle:
ret = helpers.switch({'LANG': 'de'}, 'dude')
with patch.object(helpers, "waffle") as waffle:
ret = helpers.switch({"LANG": "de"}, "dude")
assert ret is waffle.switch.return_value
waffle.switch.assert_called_with('dude')
waffle.switch.assert_called_with("dude")
with patch.object(helpers, 'waffle') as waffle:
ret = helpers.switch({'LANG': 'en-GB'}, 'dude', ['de', 'en'])
with patch.object(helpers, "waffle") as waffle:
ret = helpers.switch({"LANG": "en-GB"}, "dude", ["de", "en"])
assert ret is waffle.switch.return_value
waffle.switch.assert_called_with('dude')
waffle.switch.assert_called_with("dude")
@override_settings(DONATE_PARAMS={'en-US': {'presets': '50,30,20,10'},
'id': {'presets': '270000,140000,70000,40000'}})
@override_settings(DONATE_PARAMS={"en-US": {"presets": "50,30,20,10"}, "id": {"presets": "270000,140000,70000,40000"}})
class TestGetDonateParams(TestCase):
def test_en_us(self):
ctx = {'LANG': 'en-US'}
ctx = {"LANG": "en-US"}
params = helpers.get_donate_params(ctx)
assert params['preset_list'] == '50,30,20,10'.split(',')
assert params["preset_list"] == "50,30,20,10".split(",")
def test_id(self):
ctx = {'LANG': 'id'}
ctx = {"LANG": "id"}
params = helpers.get_donate_params(ctx)
assert params['preset_list'] == '270000,140000,70000,40000'.split(',')
assert params["preset_list"] == "270000,140000,70000,40000".split(",")
def test_undefined_locale(self):
ctx = {'LANG': 'de'}
ctx = {"LANG": "de"}
params = helpers.get_donate_params(ctx)
assert params['preset_list'] == '50,30,20,10'.split(',')
assert params["preset_list"] == "50,30,20,10".split(",")

Просмотреть файл

@ -12,32 +12,28 @@ class TestLocaleURLMiddleware(TestCase):
self.rf = RequestFactory()
self.middleware = LocaleURLMiddleware()
@override_settings(DEV_LANGUAGES=('de', 'fr'))
@override_settings(DEV_LANGUAGES=("de", "fr"))
def test_redirects_to_correct_language(self):
"""Should redirect to lang prefixed url."""
path = '/the/dude/'
req = self.rf.get(path, HTTP_ACCEPT_LANGUAGE='de')
path = "/the/dude/"
req = self.rf.get(path, HTTP_ACCEPT_LANGUAGE="de")
resp = self.middleware.process_request(req)
self.assertEqual(resp['Location'], '/de' + path)
self.assertEqual(resp["Location"], "/de" + path)
@override_settings(DEV_LANGUAGES=('es', 'fr'),
LANGUAGE_CODE='en-US')
@override_settings(DEV_LANGUAGES=("es", "fr"), LANGUAGE_CODE="en-US")
def test_redirects_to_default_language(self):
"""Should redirect to default lang if not in settings."""
path = '/the/dude/'
req = self.rf.get(path, HTTP_ACCEPT_LANGUAGE='de')
path = "/the/dude/"
req = self.rf.get(path, HTTP_ACCEPT_LANGUAGE="de")
resp = self.middleware.process_request(req)
self.assertEqual(resp['Location'], '/en-US' + path)
self.assertEqual(resp["Location"], "/en-US" + path)
@override_settings(DEV_LANGUAGES=('de', 'fr'))
@override_settings(DEV_LANGUAGES=("de", "fr"))
def test_redirects_to_correct_language_despite_unicode_errors(self):
"""Should redirect to lang prefixed url, stripping invalid chars."""
path = '/the/dude/'
corrupt_querystring = '?' + urlencode(
{b'a\xa4\x91b\xa4\x91i\xc0de': 's'})
corrected_querystring = '?abide=s'
req = self.rf.get(path + corrupt_querystring,
HTTP_ACCEPT_LANGUAGE='de')
path = "/the/dude/"
corrupt_querystring = "?" + urlencode({b"a\xa4\x91b\xa4\x91i\xc0de": "s"})
corrected_querystring = "?abide=s"
req = self.rf.get(path + corrupt_querystring, HTTP_ACCEPT_LANGUAGE="de")
resp = self.middleware.process_request(req)
self.assertEqual(resp['Location'],
'/de' + path + corrected_querystring)
self.assertEqual(resp["Location"], "/de" + path + corrected_querystring)

Просмотреть файл

@ -4,11 +4,10 @@ from django.conf import settings
from django.test import override_settings
@override_settings(DEV=False, PROD_LANGUAGES=('de', 'fr', 'nb-NO', 'ja', 'ja-JP-mac',
'en-US', 'en-GB'))
@override_settings(DEV=False, PROD_LANGUAGES=("de", "fr", "nb-NO", "ja", "ja-JP-mac", "en-US", "en-GB"))
def test_lang_groups():
# should not contain 'nb' and 'ja' group should contain 'ja'
assert dict(settings.LANG_GROUPS) == {
'ja': ['ja-JP-mac', 'ja'],
'en': ['en-US', 'en-GB'],
"ja": ["ja-JP-mac", "ja"],
"en": ["en-US", "en-GB"],
}

Просмотреть файл

@ -24,17 +24,17 @@ class C:
def custom_key_func(key, key_prefix, version):
"A customized cache key function"
return 'CUSTOM-' + '-'.join([key_prefix, str(version), key])
return "CUSTOM-" + "-".join([key_prefix, str(version), key])
_caches_setting_base = {
'default': {},
'prefix': {'KEY_PREFIX': 'cacheprefix{}'.format(os.getpid())},
'v2': {'VERSION': 2},
'custom_key': {'KEY_FUNCTION': custom_key_func},
'custom_key2': {'KEY_FUNCTION': 'bedrock.base.tests.test_simple_dict_cache.custom_key_func'},
'cull': {'OPTIONS': {'MAX_ENTRIES': 30}},
'zero_cull': {'OPTIONS': {'CULL_FREQUENCY': 0, 'MAX_ENTRIES': 30}},
"default": {},
"prefix": {"KEY_PREFIX": "cacheprefix{}".format(os.getpid())},
"v2": {"VERSION": 2},
"custom_key": {"KEY_FUNCTION": custom_key_func},
"custom_key2": {"KEY_FUNCTION": "bedrock.base.tests.test_simple_dict_cache.custom_key_func"},
"cull": {"OPTIONS": {"MAX_ENTRIES": 30}},
"zero_cull": {"OPTIONS": {"CULL_FREQUENCY": 0, "MAX_ENTRIES": 30}},
}
@ -52,9 +52,11 @@ def caches_setting_for_tests(base=None, **params):
return setting
@override_settings(CACHES=caches_setting_for_tests(
BACKEND='bedrock.base.cache.SimpleDictCache',
))
@override_settings(
CACHES=caches_setting_for_tests(
BACKEND="bedrock.base.cache.SimpleDictCache",
)
)
class SimpleDictCacheTests(TestCase):
def setUp(self):
self.factory = RequestFactory()
@ -76,15 +78,15 @@ class SimpleDictCacheTests(TestCase):
def test_prefix(self):
# Test for same cache key conflicts between shared backend
cache.set('somekey', 'value')
cache.set("somekey", "value")
# should not be set in the prefixed cache
self.assertFalse('somekey' in caches['prefix']) # noqa
self.assertFalse("somekey" in caches["prefix"]) # noqa
caches['prefix'].set('somekey', 'value2')
caches["prefix"].set("somekey", "value2")
self.assertEqual(cache.get('somekey'), 'value')
self.assertEqual(caches['prefix'].get('somekey'), 'value2')
self.assertEqual(cache.get("somekey"), "value")
self.assertEqual(caches["prefix"].get("somekey"), "value2")
def test_non_existent(self):
# Non-existent cache keys return as None/default
@ -94,17 +96,17 @@ class SimpleDictCacheTests(TestCase):
def test_non_none_default(self):
# Should cache None values if default is not None
cache.set('is_none', None)
self.assertIsNone(cache.get('is_none', 'bang!'))
cache.set("is_none", None)
self.assertIsNone(cache.get("is_none", "bang!"))
def test_get_many(self):
# Multiple cache keys can be returned using get_many
cache.set('a', 'a')
cache.set('b', 'b')
cache.set('c', 'c')
cache.set('d', 'd')
self.assertEqual(cache.get_many(['a', 'c', 'd']), {'a': 'a', 'c': 'c', 'd': 'd'})
self.assertEqual(cache.get_many(['a', 'b', 'e']), {'a': 'a', 'b': 'b'})
cache.set("a", "a")
cache.set("b", "b")
cache.set("c", "c")
cache.set("d", "d")
self.assertEqual(cache.get_many(["a", "c", "d"]), {"a": "a", "c": "c", "d": "d"})
self.assertEqual(cache.get_many(["a", "b", "e"]), {"a": "a", "b": "b"})
def test_delete(self):
# Cache keys can be deleted
@ -131,47 +133,47 @@ class SimpleDictCacheTests(TestCase):
def test_incr(self):
# Cache values can be incremented
cache.set('answer', 41)
self.assertEqual(cache.incr('answer'), 42)
self.assertEqual(cache.get('answer'), 42)
self.assertEqual(cache.incr('answer', 10), 52)
self.assertEqual(cache.get('answer'), 52)
self.assertEqual(cache.incr('answer', -10), 42)
self.assertRaises(ValueError, cache.incr, 'does_not_exist')
cache.set("answer", 41)
self.assertEqual(cache.incr("answer"), 42)
self.assertEqual(cache.get("answer"), 42)
self.assertEqual(cache.incr("answer", 10), 52)
self.assertEqual(cache.get("answer"), 52)
self.assertEqual(cache.incr("answer", -10), 42)
self.assertRaises(ValueError, cache.incr, "does_not_exist")
def test_decr(self):
# Cache values can be decremented
cache.set('answer', 43)
self.assertEqual(cache.decr('answer'), 42)
self.assertEqual(cache.get('answer'), 42)
self.assertEqual(cache.decr('answer', 10), 32)
self.assertEqual(cache.get('answer'), 32)
self.assertEqual(cache.decr('answer', -10), 42)
self.assertRaises(ValueError, cache.decr, 'does_not_exist')
cache.set("answer", 43)
self.assertEqual(cache.decr("answer"), 42)
self.assertEqual(cache.get("answer"), 42)
self.assertEqual(cache.decr("answer", 10), 32)
self.assertEqual(cache.get("answer"), 32)
self.assertEqual(cache.decr("answer", -10), 42)
self.assertRaises(ValueError, cache.decr, "does_not_exist")
def test_close(self):
self.assertTrue(hasattr(cache, 'close'))
self.assertTrue(hasattr(cache, "close"))
cache.close()
def test_data_types(self):
# Many different data types can be cached
stuff = {
'string': 'this is a string',
'int': 42,
'list': [1, 2, 3, 4],
'tuple': (1, 2, 3, 4),
'dict': {'A': 1, 'B': 2},
'function': f,
'class': C,
"string": "this is a string",
"int": 42,
"list": [1, 2, 3, 4],
"tuple": (1, 2, 3, 4),
"dict": {"A": 1, "B": 2},
"function": f,
"class": C,
}
cache.set("stuff", stuff)
self.assertEqual(cache.get("stuff"), stuff)
def test_expiration(self):
# Cache values can be set to expire
cache.set('expire1', 'very quickly', 1)
cache.set('expire2', 'very quickly', 1)
cache.set('expire3', 'very quickly', 1)
cache.set("expire1", "very quickly", 1)
cache.set("expire2", "very quickly", 1)
cache.set("expire3", "very quickly", 1)
time.sleep(2)
self.assertEqual(cache.get("expire1"), None)
@ -183,10 +185,10 @@ class SimpleDictCacheTests(TestCase):
def test_unicode(self):
# Unicode values can be cached
stuff = {
'ascii': 'ascii_value',
'unicode_ascii': 'Iñtërnâtiônàlizætiøn1',
'Iñtërnâtiônàlizætiøn': 'Iñtërnâtiônàlizætiøn2',
'ascii2': {'x': 1}
"ascii": "ascii_value",
"unicode_ascii": "Iñtërnâtiônàlizætiøn1",
"Iñtërnâtiônàlizætiøn": "Iñtërnâtiônàlizætiøn2",
"ascii2": {"x": 1},
}
# Test `set`
for key, value in stuff.items():
@ -210,24 +212,24 @@ class SimpleDictCacheTests(TestCase):
# Binary strings should be cacheable
from zlib import compress, decompress
value = 'value_to_be_compressed'
value = "value_to_be_compressed"
compressed_value = compress(value.encode())
# Test set
cache.set('binary1', compressed_value)
compressed_result = cache.get('binary1')
cache.set("binary1", compressed_value)
compressed_result = cache.get("binary1")
self.assertEqual(compressed_value, compressed_result)
self.assertEqual(value, decompress(compressed_result).decode())
# Test add
cache.add('binary1-add', compressed_value)
compressed_result = cache.get('binary1-add')
cache.add("binary1-add", compressed_value)
compressed_result = cache.get("binary1-add")
self.assertEqual(compressed_value, compressed_result)
self.assertEqual(value, decompress(compressed_result).decode())
# Test set_many
cache.set_many({'binary1-set_many': compressed_value})
compressed_result = cache.get('binary1-set_many')
cache.set_many({"binary1-set_many": compressed_value})
compressed_result = cache.get("binary1-set_many")
self.assertEqual(compressed_value, compressed_result)
self.assertEqual(value, decompress(compressed_result).decode())
@ -263,51 +265,51 @@ class SimpleDictCacheTests(TestCase):
self.assertEqual(cache.get("key2"), None)
def test_long_timeout(self):
'''
"""
Using a timeout greater than 30 days makes memcached think
it is an absolute expiration timestamp instead of a relative
offset. Test that we honour this convention. Refs #12399.
'''
cache.set('key1', 'eggs', 60 * 60 * 24 * 30 + 1) # 30 days + 1 second
self.assertEqual(cache.get('key1'), 'eggs')
"""
cache.set("key1", "eggs", 60 * 60 * 24 * 30 + 1) # 30 days + 1 second
self.assertEqual(cache.get("key1"), "eggs")
cache.add('key2', 'ham', 60 * 60 * 24 * 30 + 1)
self.assertEqual(cache.get('key2'), 'ham')
cache.add("key2", "ham", 60 * 60 * 24 * 30 + 1)
self.assertEqual(cache.get("key2"), "ham")
cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, 60 * 60 * 24 * 30 + 1)
self.assertEqual(cache.get('key3'), 'sausage')
self.assertEqual(cache.get('key4'), 'lobster bisque')
cache.set_many({"key3": "sausage", "key4": "lobster bisque"}, 60 * 60 * 24 * 30 + 1)
self.assertEqual(cache.get("key3"), "sausage")
self.assertEqual(cache.get("key4"), "lobster bisque")
def test_forever_timeout(self):
'''
"""
Passing in None into timeout results in a value that is cached forever
'''
cache.set('key1', 'eggs', None)
self.assertEqual(cache.get('key1'), 'eggs')
"""
cache.set("key1", "eggs", None)
self.assertEqual(cache.get("key1"), "eggs")
cache.add('key2', 'ham', None)
self.assertEqual(cache.get('key2'), 'ham')
added = cache.add('key1', 'new eggs', None)
cache.add("key2", "ham", None)
self.assertEqual(cache.get("key2"), "ham")
added = cache.add("key1", "new eggs", None)
self.assertEqual(added, False)
self.assertEqual(cache.get('key1'), 'eggs')
self.assertEqual(cache.get("key1"), "eggs")
cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, None)
self.assertEqual(cache.get('key3'), 'sausage')
self.assertEqual(cache.get('key4'), 'lobster bisque')
cache.set_many({"key3": "sausage", "key4": "lobster bisque"}, None)
self.assertEqual(cache.get("key3"), "sausage")
self.assertEqual(cache.get("key4"), "lobster bisque")
def test_zero_timeout(self):
'''
"""
Passing in zero into timeout results in a value that is not cached
'''
cache.set('key1', 'eggs', 0)
self.assertEqual(cache.get('key1'), None)
"""
cache.set("key1", "eggs", 0)
self.assertEqual(cache.get("key1"), None)
cache.add('key2', 'ham', 0)
self.assertEqual(cache.get('key2'), None)
cache.add("key2", "ham", 0)
self.assertEqual(cache.get("key2"), None)
cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, 0)
self.assertEqual(cache.get('key3'), None)
self.assertEqual(cache.get('key4'), None)
cache.set_many({"key3": "sausage", "key4": "lobster bisque"}, 0)
self.assertEqual(cache.get("key3"), None)
self.assertEqual(cache.get("key4"), None)
def test_float_timeout(self):
# Make sure a timeout given as a float doesn't crash anything.
@ -318,19 +320,19 @@ class SimpleDictCacheTests(TestCase):
# Create initial cache key entries. This will overflow the cache,
# causing a cull.
for i in range(1, initial_count):
cull_cache.set('cull%d' % i, 'value', 1000)
cull_cache.set("cull%d" % i, "value", 1000)
count = 0
# Count how many keys are left in the cache.
for i in range(1, initial_count):
if 'cull%d' % i in cull_cache: # noqa
if "cull%d" % i in cull_cache: # noqa
count = count + 1
self.assertEqual(count, final_count)
def test_cull(self):
self._perform_cull_test(caches['cull'], 50, 29)
self._perform_cull_test(caches["cull"], 50, 29)
def test_zero_cull(self):
self._perform_cull_test(caches['zero_cull'], 50, 19)
self._perform_cull_test(caches["zero_cull"], 50, 19)
def test_invalid_keys(self):
"""
@ -351,13 +353,13 @@ class SimpleDictCacheTests(TestCase):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# memcached does not allow whitespace or control characters in keys
cache.set('key with spaces', 'value')
cache.set("key with spaces", "value")
self.assertTrue(w)
self.assertIsInstance(w[0].message, CacheKeyWarning)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# memcached limits key length to 250
cache.set('a' * 251, 'value')
cache.set("a" * 251, "value")
self.assertEqual(len(w), 1)
self.assertIsInstance(w[0].message, CacheKeyWarning)
finally:
@ -365,291 +367,278 @@ class SimpleDictCacheTests(TestCase):
def test_cache_versioning_get_set(self):
# set, using default version = 1
cache.set('answer1', 42)
self.assertEqual(cache.get('answer1'), 42)
self.assertEqual(cache.get('answer1', version=1), 42)
self.assertEqual(cache.get('answer1', version=2), None)
cache.set("answer1", 42)
self.assertEqual(cache.get("answer1"), 42)
self.assertEqual(cache.get("answer1", version=1), 42)
self.assertEqual(cache.get("answer1", version=2), None)
self.assertEqual(caches['v2'].get('answer1'), None)
self.assertEqual(caches['v2'].get('answer1', version=1), 42)
self.assertEqual(caches['v2'].get('answer1', version=2), None)
self.assertEqual(caches["v2"].get("answer1"), None)
self.assertEqual(caches["v2"].get("answer1", version=1), 42)
self.assertEqual(caches["v2"].get("answer1", version=2), None)
# set, default version = 1, but manually override version = 2
cache.set('answer2', 42, version=2)
self.assertEqual(cache.get('answer2'), None)
self.assertEqual(cache.get('answer2', version=1), None)
self.assertEqual(cache.get('answer2', version=2), 42)
cache.set("answer2", 42, version=2)
self.assertEqual(cache.get("answer2"), None)
self.assertEqual(cache.get("answer2", version=1), None)
self.assertEqual(cache.get("answer2", version=2), 42)
self.assertEqual(caches['v2'].get('answer2'), 42)
self.assertEqual(caches['v2'].get('answer2', version=1), None)
self.assertEqual(caches['v2'].get('answer2', version=2), 42)
self.assertEqual(caches["v2"].get("answer2"), 42)
self.assertEqual(caches["v2"].get("answer2", version=1), None)
self.assertEqual(caches["v2"].get("answer2", version=2), 42)
# v2 set, using default version = 2
caches['v2'].set('answer3', 42)
self.assertEqual(cache.get('answer3'), None)
self.assertEqual(cache.get('answer3', version=1), None)
self.assertEqual(cache.get('answer3', version=2), 42)
caches["v2"].set("answer3", 42)
self.assertEqual(cache.get("answer3"), None)
self.assertEqual(cache.get("answer3", version=1), None)
self.assertEqual(cache.get("answer3", version=2), 42)
self.assertEqual(caches['v2'].get('answer3'), 42)
self.assertEqual(caches['v2'].get('answer3', version=1), None)
self.assertEqual(caches['v2'].get('answer3', version=2), 42)
self.assertEqual(caches["v2"].get("answer3"), 42)
self.assertEqual(caches["v2"].get("answer3", version=1), None)
self.assertEqual(caches["v2"].get("answer3", version=2), 42)
# v2 set, default version = 2, but manually override version = 1
caches['v2'].set('answer4', 42, version=1)
self.assertEqual(cache.get('answer4'), 42)
self.assertEqual(cache.get('answer4', version=1), 42)
self.assertEqual(cache.get('answer4', version=2), None)
caches["v2"].set("answer4", 42, version=1)
self.assertEqual(cache.get("answer4"), 42)
self.assertEqual(cache.get("answer4", version=1), 42)
self.assertEqual(cache.get("answer4", version=2), None)
self.assertEqual(caches['v2'].get('answer4'), None)
self.assertEqual(caches['v2'].get('answer4', version=1), 42)
self.assertEqual(caches['v2'].get('answer4', version=2), None)
self.assertEqual(caches["v2"].get("answer4"), None)
self.assertEqual(caches["v2"].get("answer4", version=1), 42)
self.assertEqual(caches["v2"].get("answer4", version=2), None)
def test_cache_versioning_add(self):
# add, default version = 1, but manually override version = 2
cache.add('answer1', 42, version=2)
self.assertEqual(cache.get('answer1', version=1), None)
self.assertEqual(cache.get('answer1', version=2), 42)
cache.add("answer1", 42, version=2)
self.assertEqual(cache.get("answer1", version=1), None)
self.assertEqual(cache.get("answer1", version=2), 42)
cache.add('answer1', 37, version=2)
self.assertEqual(cache.get('answer1', version=1), None)
self.assertEqual(cache.get('answer1', version=2), 42)
cache.add("answer1", 37, version=2)
self.assertEqual(cache.get("answer1", version=1), None)
self.assertEqual(cache.get("answer1", version=2), 42)
cache.add('answer1', 37, version=1)
self.assertEqual(cache.get('answer1', version=1), 37)
self.assertEqual(cache.get('answer1', version=2), 42)
cache.add("answer1", 37, version=1)
self.assertEqual(cache.get("answer1", version=1), 37)
self.assertEqual(cache.get("answer1", version=2), 42)
# v2 add, using default version = 2
caches['v2'].add('answer2', 42)
self.assertEqual(cache.get('answer2', version=1), None)
self.assertEqual(cache.get('answer2', version=2), 42)
caches["v2"].add("answer2", 42)
self.assertEqual(cache.get("answer2", version=1), None)
self.assertEqual(cache.get("answer2", version=2), 42)
caches['v2'].add('answer2', 37)
self.assertEqual(cache.get('answer2', version=1), None)
self.assertEqual(cache.get('answer2', version=2), 42)
caches["v2"].add("answer2", 37)
self.assertEqual(cache.get("answer2", version=1), None)
self.assertEqual(cache.get("answer2", version=2), 42)
caches['v2'].add('answer2', 37, version=1)
self.assertEqual(cache.get('answer2', version=1), 37)
self.assertEqual(cache.get('answer2', version=2), 42)
caches["v2"].add("answer2", 37, version=1)
self.assertEqual(cache.get("answer2", version=1), 37)
self.assertEqual(cache.get("answer2", version=2), 42)
# v2 add, default version = 2, but manually override version = 1
caches['v2'].add('answer3', 42, version=1)
self.assertEqual(cache.get('answer3', version=1), 42)
self.assertEqual(cache.get('answer3', version=2), None)
caches["v2"].add("answer3", 42, version=1)
self.assertEqual(cache.get("answer3", version=1), 42)
self.assertEqual(cache.get("answer3", version=2), None)
caches['v2'].add('answer3', 37, version=1)
self.assertEqual(cache.get('answer3', version=1), 42)
self.assertEqual(cache.get('answer3', version=2), None)
caches["v2"].add("answer3", 37, version=1)
self.assertEqual(cache.get("answer3", version=1), 42)
self.assertEqual(cache.get("answer3", version=2), None)
caches['v2'].add('answer3', 37)
self.assertEqual(cache.get('answer3', version=1), 42)
self.assertEqual(cache.get('answer3', version=2), 37)
caches["v2"].add("answer3", 37)
self.assertEqual(cache.get("answer3", version=1), 42)
self.assertEqual(cache.get("answer3", version=2), 37)
def test_cache_versioning_has_key(self):
cache.set('answer1', 42)
cache.set("answer1", 42)
# has_key
self.assertTrue('answer1' in cache) # noqa
self.assertTrue(cache.has_key('answer1', version=1)) # noqa
self.assertFalse(cache.has_key('answer1', version=2)) # noqa
self.assertTrue("answer1" in cache) # noqa
self.assertTrue(cache.has_key("answer1", version=1)) # noqa
self.assertFalse(cache.has_key("answer1", version=2)) # noqa
self.assertFalse('answer1' in caches['v2']) # noqa
self.assertTrue(caches['v2'].has_key('answer1', version=1)) # noqa
self.assertFalse(caches['v2'].has_key('answer1', version=2)) # noqa
self.assertFalse("answer1" in caches["v2"]) # noqa
self.assertTrue(caches["v2"].has_key("answer1", version=1)) # noqa
self.assertFalse(caches["v2"].has_key("answer1", version=2)) # noqa
def test_cache_versioning_delete(self):
cache.set('answer1', 37, version=1)
cache.set('answer1', 42, version=2)
cache.delete('answer1')
self.assertEqual(cache.get('answer1', version=1), None)
self.assertEqual(cache.get('answer1', version=2), 42)
cache.set("answer1", 37, version=1)
cache.set("answer1", 42, version=2)
cache.delete("answer1")
self.assertEqual(cache.get("answer1", version=1), None)
self.assertEqual(cache.get("answer1", version=2), 42)
cache.set('answer2', 37, version=1)
cache.set('answer2', 42, version=2)
cache.delete('answer2', version=2)
self.assertEqual(cache.get('answer2', version=1), 37)
self.assertEqual(cache.get('answer2', version=2), None)
cache.set("answer2", 37, version=1)
cache.set("answer2", 42, version=2)
cache.delete("answer2", version=2)
self.assertEqual(cache.get("answer2", version=1), 37)
self.assertEqual(cache.get("answer2", version=2), None)
cache.set('answer3', 37, version=1)
cache.set('answer3', 42, version=2)
caches['v2'].delete('answer3')
self.assertEqual(cache.get('answer3', version=1), 37)
self.assertEqual(cache.get('answer3', version=2), None)
cache.set("answer3", 37, version=1)
cache.set("answer3", 42, version=2)
caches["v2"].delete("answer3")
self.assertEqual(cache.get("answer3", version=1), 37)
self.assertEqual(cache.get("answer3", version=2), None)
cache.set('answer4', 37, version=1)
cache.set('answer4', 42, version=2)
caches['v2'].delete('answer4', version=1)
self.assertEqual(cache.get('answer4', version=1), None)
self.assertEqual(cache.get('answer4', version=2), 42)
cache.set("answer4", 37, version=1)
cache.set("answer4", 42, version=2)
caches["v2"].delete("answer4", version=1)
self.assertEqual(cache.get("answer4", version=1), None)
self.assertEqual(cache.get("answer4", version=2), 42)
def test_cache_versioning_incr_decr(self):
cache.set('answer1', 37, version=1)
cache.set('answer1', 42, version=2)
cache.incr('answer1')
self.assertEqual(cache.get('answer1', version=1), 38)
self.assertEqual(cache.get('answer1', version=2), 42)
cache.decr('answer1')
self.assertEqual(cache.get('answer1', version=1), 37)
self.assertEqual(cache.get('answer1', version=2), 42)
cache.set("answer1", 37, version=1)
cache.set("answer1", 42, version=2)
cache.incr("answer1")
self.assertEqual(cache.get("answer1", version=1), 38)
self.assertEqual(cache.get("answer1", version=2), 42)
cache.decr("answer1")
self.assertEqual(cache.get("answer1", version=1), 37)
self.assertEqual(cache.get("answer1", version=2), 42)
cache.set('answer2', 37, version=1)
cache.set('answer2', 42, version=2)
cache.incr('answer2', version=2)
self.assertEqual(cache.get('answer2', version=1), 37)
self.assertEqual(cache.get('answer2', version=2), 43)
cache.decr('answer2', version=2)
self.assertEqual(cache.get('answer2', version=1), 37)
self.assertEqual(cache.get('answer2', version=2), 42)
cache.set("answer2", 37, version=1)
cache.set("answer2", 42, version=2)
cache.incr("answer2", version=2)
self.assertEqual(cache.get("answer2", version=1), 37)
self.assertEqual(cache.get("answer2", version=2), 43)
cache.decr("answer2", version=2)
self.assertEqual(cache.get("answer2", version=1), 37)
self.assertEqual(cache.get("answer2", version=2), 42)
cache.set('answer3', 37, version=1)
cache.set('answer3', 42, version=2)
caches['v2'].incr('answer3')
self.assertEqual(cache.get('answer3', version=1), 37)
self.assertEqual(cache.get('answer3', version=2), 43)
caches['v2'].decr('answer3')
self.assertEqual(cache.get('answer3', version=1), 37)
self.assertEqual(cache.get('answer3', version=2), 42)
cache.set("answer3", 37, version=1)
cache.set("answer3", 42, version=2)
caches["v2"].incr("answer3")
self.assertEqual(cache.get("answer3", version=1), 37)
self.assertEqual(cache.get("answer3", version=2), 43)
caches["v2"].decr("answer3")
self.assertEqual(cache.get("answer3", version=1), 37)
self.assertEqual(cache.get("answer3", version=2), 42)
cache.set('answer4', 37, version=1)
cache.set('answer4', 42, version=2)
caches['v2'].incr('answer4', version=1)
self.assertEqual(cache.get('answer4', version=1), 38)
self.assertEqual(cache.get('answer4', version=2), 42)
caches['v2'].decr('answer4', version=1)
self.assertEqual(cache.get('answer4', version=1), 37)
self.assertEqual(cache.get('answer4', version=2), 42)
cache.set("answer4", 37, version=1)
cache.set("answer4", 42, version=2)
caches["v2"].incr("answer4", version=1)
self.assertEqual(cache.get("answer4", version=1), 38)
self.assertEqual(cache.get("answer4", version=2), 42)
caches["v2"].decr("answer4", version=1)
self.assertEqual(cache.get("answer4", version=1), 37)
self.assertEqual(cache.get("answer4", version=2), 42)
def test_cache_versioning_get_set_many(self):
# set, using default version = 1
cache.set_many({'ford1': 37, 'arthur1': 42})
self.assertEqual(cache.get_many(['ford1', 'arthur1']),
{'ford1': 37, 'arthur1': 42})
self.assertEqual(cache.get_many(['ford1', 'arthur1'], version=1),
{'ford1': 37, 'arthur1': 42})
self.assertEqual(cache.get_many(['ford1', 'arthur1'], version=2), {})
cache.set_many({"ford1": 37, "arthur1": 42})
self.assertEqual(cache.get_many(["ford1", "arthur1"]), {"ford1": 37, "arthur1": 42})
self.assertEqual(cache.get_many(["ford1", "arthur1"], version=1), {"ford1": 37, "arthur1": 42})
self.assertEqual(cache.get_many(["ford1", "arthur1"], version=2), {})
self.assertEqual(caches['v2'].get_many(['ford1', 'arthur1']), {})
self.assertEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=1),
{'ford1': 37, 'arthur1': 42})
self.assertEqual(caches['v2'].get_many(['ford1', 'arthur1'], version=2), {})
self.assertEqual(caches["v2"].get_many(["ford1", "arthur1"]), {})
self.assertEqual(caches["v2"].get_many(["ford1", "arthur1"], version=1), {"ford1": 37, "arthur1": 42})
self.assertEqual(caches["v2"].get_many(["ford1", "arthur1"], version=2), {})
# set, default version = 1, but manually override version = 2
cache.set_many({'ford2': 37, 'arthur2': 42}, version=2)
self.assertEqual(cache.get_many(['ford2', 'arthur2']), {})
self.assertEqual(cache.get_many(['ford2', 'arthur2'], version=1), {})
self.assertEqual(cache.get_many(['ford2', 'arthur2'], version=2),
{'ford2': 37, 'arthur2': 42})
cache.set_many({"ford2": 37, "arthur2": 42}, version=2)
self.assertEqual(cache.get_many(["ford2", "arthur2"]), {})
self.assertEqual(cache.get_many(["ford2", "arthur2"], version=1), {})
self.assertEqual(cache.get_many(["ford2", "arthur2"], version=2), {"ford2": 37, "arthur2": 42})
self.assertEqual(caches['v2'].get_many(['ford2', 'arthur2']),
{'ford2': 37, 'arthur2': 42})
self.assertEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=1), {})
self.assertEqual(caches['v2'].get_many(['ford2', 'arthur2'], version=2),
{'ford2': 37, 'arthur2': 42})
self.assertEqual(caches["v2"].get_many(["ford2", "arthur2"]), {"ford2": 37, "arthur2": 42})
self.assertEqual(caches["v2"].get_many(["ford2", "arthur2"], version=1), {})
self.assertEqual(caches["v2"].get_many(["ford2", "arthur2"], version=2), {"ford2": 37, "arthur2": 42})
# v2 set, using default version = 2
caches['v2'].set_many({'ford3': 37, 'arthur3': 42})
self.assertEqual(cache.get_many(['ford3', 'arthur3']), {})
self.assertEqual(cache.get_many(['ford3', 'arthur3'], version=1), {})
self.assertEqual(cache.get_many(['ford3', 'arthur3'], version=2),
{'ford3': 37, 'arthur3': 42})
caches["v2"].set_many({"ford3": 37, "arthur3": 42})
self.assertEqual(cache.get_many(["ford3", "arthur3"]), {})
self.assertEqual(cache.get_many(["ford3", "arthur3"], version=1), {})
self.assertEqual(cache.get_many(["ford3", "arthur3"], version=2), {"ford3": 37, "arthur3": 42})
self.assertEqual(caches['v2'].get_many(['ford3', 'arthur3']),
{'ford3': 37, 'arthur3': 42})
self.assertEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=1), {})
self.assertEqual(caches['v2'].get_many(['ford3', 'arthur3'], version=2),
{'ford3': 37, 'arthur3': 42})
self.assertEqual(caches["v2"].get_many(["ford3", "arthur3"]), {"ford3": 37, "arthur3": 42})
self.assertEqual(caches["v2"].get_many(["ford3", "arthur3"], version=1), {})
self.assertEqual(caches["v2"].get_many(["ford3", "arthur3"], version=2), {"ford3": 37, "arthur3": 42})
# v2 set, default version = 2, but manually override version = 1
caches['v2'].set_many({'ford4': 37, 'arthur4': 42}, version=1)
self.assertEqual(cache.get_many(['ford4', 'arthur4']),
{'ford4': 37, 'arthur4': 42})
self.assertEqual(cache.get_many(['ford4', 'arthur4'], version=1),
{'ford4': 37, 'arthur4': 42})
self.assertEqual(cache.get_many(['ford4', 'arthur4'], version=2), {})
caches["v2"].set_many({"ford4": 37, "arthur4": 42}, version=1)
self.assertEqual(cache.get_many(["ford4", "arthur4"]), {"ford4": 37, "arthur4": 42})
self.assertEqual(cache.get_many(["ford4", "arthur4"], version=1), {"ford4": 37, "arthur4": 42})
self.assertEqual(cache.get_many(["ford4", "arthur4"], version=2), {})
self.assertEqual(caches['v2'].get_many(['ford4', 'arthur4']), {})
self.assertEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=1),
{'ford4': 37, 'arthur4': 42})
self.assertEqual(caches['v2'].get_many(['ford4', 'arthur4'], version=2), {})
self.assertEqual(caches["v2"].get_many(["ford4", "arthur4"]), {})
self.assertEqual(caches["v2"].get_many(["ford4", "arthur4"], version=1), {"ford4": 37, "arthur4": 42})
self.assertEqual(caches["v2"].get_many(["ford4", "arthur4"], version=2), {})
def test_incr_version(self):
cache.set('answer', 42, version=2)
self.assertEqual(cache.get('answer'), None)
self.assertEqual(cache.get('answer', version=1), None)
self.assertEqual(cache.get('answer', version=2), 42)
self.assertEqual(cache.get('answer', version=3), None)
cache.set("answer", 42, version=2)
self.assertEqual(cache.get("answer"), None)
self.assertEqual(cache.get("answer", version=1), None)
self.assertEqual(cache.get("answer", version=2), 42)
self.assertEqual(cache.get("answer", version=3), None)
self.assertEqual(cache.incr_version('answer', version=2), 3)
self.assertEqual(cache.get('answer'), None)
self.assertEqual(cache.get('answer', version=1), None)
self.assertEqual(cache.get('answer', version=2), None)
self.assertEqual(cache.get('answer', version=3), 42)
self.assertEqual(cache.incr_version("answer", version=2), 3)
self.assertEqual(cache.get("answer"), None)
self.assertEqual(cache.get("answer", version=1), None)
self.assertEqual(cache.get("answer", version=2), None)
self.assertEqual(cache.get("answer", version=3), 42)
caches['v2'].set('answer2', 42)
self.assertEqual(caches['v2'].get('answer2'), 42)
self.assertEqual(caches['v2'].get('answer2', version=1), None)
self.assertEqual(caches['v2'].get('answer2', version=2), 42)
self.assertEqual(caches['v2'].get('answer2', version=3), None)
caches["v2"].set("answer2", 42)
self.assertEqual(caches["v2"].get("answer2"), 42)
self.assertEqual(caches["v2"].get("answer2", version=1), None)
self.assertEqual(caches["v2"].get("answer2", version=2), 42)
self.assertEqual(caches["v2"].get("answer2", version=3), None)
self.assertEqual(caches['v2'].incr_version('answer2'), 3)
self.assertEqual(caches['v2'].get('answer2'), None)
self.assertEqual(caches['v2'].get('answer2', version=1), None)
self.assertEqual(caches['v2'].get('answer2', version=2), None)
self.assertEqual(caches['v2'].get('answer2', version=3), 42)
self.assertEqual(caches["v2"].incr_version("answer2"), 3)
self.assertEqual(caches["v2"].get("answer2"), None)
self.assertEqual(caches["v2"].get("answer2", version=1), None)
self.assertEqual(caches["v2"].get("answer2", version=2), None)
self.assertEqual(caches["v2"].get("answer2", version=3), 42)
self.assertRaises(ValueError, cache.incr_version, 'does_not_exist')
self.assertRaises(ValueError, cache.incr_version, "does_not_exist")
def test_decr_version(self):
cache.set('answer', 42, version=2)
self.assertEqual(cache.get('answer'), None)
self.assertEqual(cache.get('answer', version=1), None)
self.assertEqual(cache.get('answer', version=2), 42)
cache.set("answer", 42, version=2)
self.assertEqual(cache.get("answer"), None)
self.assertEqual(cache.get("answer", version=1), None)
self.assertEqual(cache.get("answer", version=2), 42)
self.assertEqual(cache.decr_version('answer', version=2), 1)
self.assertEqual(cache.get('answer'), 42)
self.assertEqual(cache.get('answer', version=1), 42)
self.assertEqual(cache.get('answer', version=2), None)
self.assertEqual(cache.decr_version("answer", version=2), 1)
self.assertEqual(cache.get("answer"), 42)
self.assertEqual(cache.get("answer", version=1), 42)
self.assertEqual(cache.get("answer", version=2), None)
caches['v2'].set('answer2', 42)
self.assertEqual(caches['v2'].get('answer2'), 42)
self.assertEqual(caches['v2'].get('answer2', version=1), None)
self.assertEqual(caches['v2'].get('answer2', version=2), 42)
caches["v2"].set("answer2", 42)
self.assertEqual(caches["v2"].get("answer2"), 42)
self.assertEqual(caches["v2"].get("answer2", version=1), None)
self.assertEqual(caches["v2"].get("answer2", version=2), 42)
self.assertEqual(caches['v2'].decr_version('answer2'), 1)
self.assertEqual(caches['v2'].get('answer2'), None)
self.assertEqual(caches['v2'].get('answer2', version=1), 42)
self.assertEqual(caches['v2'].get('answer2', version=2), None)
self.assertEqual(caches["v2"].decr_version("answer2"), 1)
self.assertEqual(caches["v2"].get("answer2"), None)
self.assertEqual(caches["v2"].get("answer2", version=1), 42)
self.assertEqual(caches["v2"].get("answer2", version=2), None)
self.assertRaises(ValueError, cache.decr_version, 'does_not_exist', version=2)
self.assertRaises(ValueError, cache.decr_version, "does_not_exist", version=2)
def test_custom_key_func(self):
# Two caches with different key functions aren't visible to each other
cache.set('answer1', 42)
self.assertEqual(cache.get('answer1'), 42)
self.assertEqual(caches['custom_key'].get('answer1'), None)
self.assertEqual(caches['custom_key2'].get('answer1'), None)
cache.set("answer1", 42)
self.assertEqual(cache.get("answer1"), 42)
self.assertEqual(caches["custom_key"].get("answer1"), None)
self.assertEqual(caches["custom_key2"].get("answer1"), None)
caches['custom_key'].set('answer2', 42)
self.assertEqual(cache.get('answer2'), None)
self.assertEqual(caches['custom_key'].get('answer2'), 42)
self.assertEqual(caches['custom_key2'].get('answer2'), 42)
caches["custom_key"].set("answer2", 42)
self.assertEqual(cache.get("answer2"), None)
self.assertEqual(caches["custom_key"].get("answer2"), 42)
self.assertEqual(caches["custom_key2"].get("answer2"), 42)
@override_settings(CACHES={
'default': {'BACKEND': 'bedrock.base.cache.SimpleDictCache'},
'other': {
'BACKEND': 'bedrock.base.cache.SimpleDictCache',
'LOCATION': 'other'
},
})
@override_settings(
CACHES={
"default": {"BACKEND": "bedrock.base.cache.SimpleDictCache"},
"other": {"BACKEND": "bedrock.base.cache.SimpleDictCache", "LOCATION": "other"},
}
)
def test_multiple_caches(self):
"""Check that multiple locmem caches are isolated"""
cache.set('value', 42)
self.assertEqual(caches['default'].get('value'), 42)
self.assertEqual(caches['other'].get('value'), None)
cache.set("value", 42)
self.assertEqual(caches["default"].get("value"), 42)
self.assertEqual(caches["other"].get("value"), None)
def test_incr_decr_timeout(self):
"""incr/decr does not modify expiry time (matches memcached behavior)"""
key = 'value'
key = "value"
_key = cache.make_key(key)
cache.set(key, 1, timeout=cache.default_timeout * 10)
expire = cache._expire_info[_key]

Просмотреть файл

@ -9,25 +9,26 @@ from bedrock.base.urlresolvers import find_supported, reverse, split_path, Prefi
from mock import patch, Mock
@pytest.mark.parametrize('path, result', [
# Basic
('en-US/some/action', ('en-US', 'some/action')),
# First slash doesn't matter
('/en-US/some/action', ('en-US', 'some/action')),
# Nor does capitalization
('En-uS/some/action', ('en-US', 'some/action')),
# Unsupported languages return a blank language
('unsupported/some/action', ('', 'unsupported/some/action')),
])
@pytest.mark.parametrize(
"path, result",
[
# Basic
("en-US/some/action", ("en-US", "some/action")),
# First slash doesn't matter
("/en-US/some/action", ("en-US", "some/action")),
# Nor does capitalization
("En-uS/some/action", ("en-US", "some/action")),
# Unsupported languages return a blank language
("unsupported/some/action", ("", "unsupported/some/action")),
],
)
def test_split_path(path, result):
res = split_path(path)
assert res == result
# Test urlpatterns
urlpatterns = [
re_path(r'^test/$', lambda r: None, name='test.view')
]
urlpatterns = [re_path(r"^test/$", lambda r: None, name="test.view")]
class FakePrefixer:
@ -35,17 +36,17 @@ class FakePrefixer:
self.fix = fix
@patch('bedrock.base.urlresolvers.get_url_prefix')
@override_settings(ROOT_URLCONF='bedrock.base.tests.test_urlresolvers')
@patch("bedrock.base.urlresolvers.get_url_prefix")
@override_settings(ROOT_URLCONF="bedrock.base.tests.test_urlresolvers")
class TestReverse(TestCase):
def test_unicode_url(self, get_url_prefix):
# If the prefixer returns a unicode URL it should be escaped and cast
# as a str object.
get_url_prefix.return_value = FakePrefixer(lambda p: u'/Françoi%s' % p)
result = reverse('test.view')
get_url_prefix.return_value = FakePrefixer(lambda p: "/Françoi%s" % p)
result = reverse("test.view")
# Ensure that UTF-8 characters are escaped properly.
self.assertEqual(result, '/Fran%C3%A7oi/test/')
self.assertEqual(result, "/Fran%C3%A7oi/test/")
self.assertEqual(type(result), str)
@ -53,109 +54,106 @@ class TestPrefixer(TestCase):
def setUp(self):
self.factory = RequestFactory()
@override_settings(LANGUAGE_CODE='en-US')
@override_settings(LANGUAGE_CODE="en-US")
def test_get_language_default_language_code(self):
"""
Should return default set by settings.LANGUAGE_CODE if no 'lang'
url parameter and no Accept-Language header
"""
request = self.factory.get('/')
self.assertFalse('lang' in request.GET)
self.assertFalse(request.META.get('HTTP_ACCEPT_LANGUAGE'))
request = self.factory.get("/")
self.assertFalse("lang" in request.GET)
self.assertFalse(request.META.get("HTTP_ACCEPT_LANGUAGE"))
prefixer = Prefixer(request)
assert prefixer.get_language() == 'en-US'
assert prefixer.get_language() == "en-US"
def test_get_language_returns_best(self):
"""
Should pass Accept-Language header value to get_best_language
and return result
"""
request = self.factory.get('/')
request.META['HTTP_ACCEPT_LANGUAGE'] = 'de, es'
request = self.factory.get("/")
request.META["HTTP_ACCEPT_LANGUAGE"] = "de, es"
prefixer = Prefixer(request)
prefixer.get_best_language = Mock(return_value='de')
assert prefixer.get_language() == 'de'
prefixer.get_best_language.assert_called_once_with('de, es')
prefixer.get_best_language = Mock(return_value="de")
assert prefixer.get_language() == "de"
prefixer.get_best_language.assert_called_once_with("de, es")
@override_settings(LANGUAGE_CODE='en-US')
@override_settings(LANGUAGE_CODE="en-US")
def test_get_language_no_best(self):
"""
Should return default set by settings.LANGUAGE_CODE if
get_best_language return value is None
"""
request = self.factory.get('/')
request.META['HTTP_ACCEPT_LANGUAGE'] = 'de, es'
request = self.factory.get("/")
request.META["HTTP_ACCEPT_LANGUAGE"] = "de, es"
prefixer = Prefixer(request)
prefixer.get_best_language = Mock(return_value=None)
assert prefixer.get_language() == 'en-US'
prefixer.get_best_language.assert_called_once_with('de, es')
assert prefixer.get_language() == "en-US"
prefixer.get_best_language.assert_called_once_with("de, es")
@override_settings(LANGUAGE_URL_MAP={'en-us': 'en-US', 'de': 'de'})
@override_settings(LANGUAGE_URL_MAP={"en-us": "en-US", "de": "de"})
def test_get_best_language_exact_match(self):
"""
Should return exact match if it is in settings.LANGUAGE_URL_MAP
"""
request = self.factory.get('/')
request = self.factory.get("/")
prefixer = Prefixer(request)
assert prefixer.get_best_language('de, es') == 'de'
assert prefixer.get_best_language("de, es") == "de"
@override_settings(LANGUAGE_URL_MAP={'en-gb': 'en-GB', 'en-us': 'en-US', 'es-ar': 'es-AR'},
CANONICAL_LOCALES={'es': 'es-ES', 'en': 'en-US'})
@override_settings(LANGUAGE_URL_MAP={"en-gb": "en-GB", "en-us": "en-US", "es-ar": "es-AR"}, CANONICAL_LOCALES={"es": "es-ES", "en": "en-US"})
def test_get_best_language_prefix_match(self):
"""
Should return a language with a matching prefix from
settings.LANGUAGE_URL_MAP + settings.CANONICAL_LOCALES if it exists but
no exact match does
"""
request = self.factory.get('/')
request = self.factory.get("/")
prefixer = Prefixer(request)
assert prefixer.get_best_language('en') == 'en-US'
assert prefixer.get_best_language('en-CA') == 'en-US'
assert prefixer.get_best_language('en-GB') == 'en-GB'
assert prefixer.get_best_language('en-US') == 'en-US'
assert prefixer.get_best_language('es') == 'es-ES'
assert prefixer.get_best_language('es-AR') == 'es-AR'
assert prefixer.get_best_language('es-CL') == 'es-ES'
assert prefixer.get_best_language('es-MX') == 'es-ES'
assert prefixer.get_best_language("en") == "en-US"
assert prefixer.get_best_language("en-CA") == "en-US"
assert prefixer.get_best_language("en-GB") == "en-GB"
assert prefixer.get_best_language("en-US") == "en-US"
assert prefixer.get_best_language("es") == "es-ES"
assert prefixer.get_best_language("es-AR") == "es-AR"
assert prefixer.get_best_language("es-CL") == "es-ES"
assert prefixer.get_best_language("es-MX") == "es-ES"
@override_settings(LANGUAGE_URL_MAP={'en-us': 'en-US'})
@override_settings(LANGUAGE_URL_MAP={"en-us": "en-US"})
def test_get_best_language_no_match(self):
"""
Should return None if there is no exact match or matching
prefix
"""
request = self.factory.get('/')
request = self.factory.get("/")
prefixer = Prefixer(request)
assert prefixer.get_best_language('de') is None
assert prefixer.get_best_language("de") is None
@override_settings(LANGUAGE_URL_MAP={'en-ar': 'en-AR', 'en-gb': 'en-GB', 'en-us': 'en-US'},
CANONICAL_LOCALES={'en': 'en-US'})
@override_settings(LANGUAGE_URL_MAP={"en-ar": "en-AR", "en-gb": "en-GB", "en-us": "en-US"}, CANONICAL_LOCALES={"en": "en-US"})
def test_prefixer_with_non_supported_locale(self):
"""
Should set prefixer.locale to a supported locale that repects CANONICAL_LOCALES
when given a URL with a non-supported locale.
"""
request = self.factory.get('/en-CA/')
request = self.factory.get("/en-CA/")
prefixer = Prefixer(request)
assert prefixer.locale == 'en-US'
assert prefixer.locale == "en-US"
@override_settings(LANGUAGE_URL_MAP={'es-ar': 'es-AR', 'en-gb': 'en-GB', 'es-us': 'es-US'},
CANONICAL_LOCALES={'es': 'es-ES', 'en': 'en-US'})
@override_settings(LANGUAGE_URL_MAP={"es-ar": "es-AR", "en-gb": "en-GB", "es-us": "es-US"}, CANONICAL_LOCALES={"es": "es-ES", "en": "en-US"})
class TestFindSupported(TestCase):
def test_find_supported(self):
assert find_supported('en-CA') == 'en-US'
assert find_supported('en-US') == 'en-US'
assert find_supported('en-GB') == 'en-GB'
assert find_supported('en') == 'en-US'
assert find_supported('es-MX') == 'es-ES'
assert find_supported('es-AR') == 'es-AR'
assert find_supported('es') == 'es-ES'
assert find_supported("en-CA") == "en-US"
assert find_supported("en-US") == "en-US"
assert find_supported("en-GB") == "en-GB"
assert find_supported("en") == "en-US"
assert find_supported("es-MX") == "es-ES"
assert find_supported("es-AR") == "es-AR"
assert find_supported("es") == "es-ES"
def test_find_supported_none(self):
"""
Should return None if it can't find any supported locale.
"""
assert find_supported('de') is None
assert find_supported('fr') is None
assert find_supported('dude') is None
assert find_supported("de") is None
assert find_supported("fr") is None
assert find_supported("dude") is None

Просмотреть файл

@ -9,102 +9,107 @@ from bedrock.base.views import geolocate, GeoRedirectView, GeoTemplateView
class TestGeolocate(TestCase):
def get_country(self, country):
with patch('bedrock.base.views.get_country_from_request') as geo_mock:
with patch("bedrock.base.views.get_country_from_request") as geo_mock:
geo_mock.return_value = country
rf = RequestFactory()
req = rf.get('/')
req = rf.get("/")
resp = geolocate(req)
return json.loads(resp.content)
def test_geo_returns(self):
self.assertDictEqual(self.get_country('US'), {'country_code': 'US'})
self.assertDictEqual(self.get_country('FR'), {'country_code': 'FR'})
self.assertDictEqual(self.get_country(None), {
"error": {
"errors": [{
"domain": "geolocation",
"reason": "notFound",
self.assertDictEqual(self.get_country("US"), {"country_code": "US"})
self.assertDictEqual(self.get_country("FR"), {"country_code": "FR"})
self.assertDictEqual(
self.get_country(None),
{
"error": {
"errors": [
{
"domain": "geolocation",
"reason": "notFound",
"message": "Not found",
}
],
"code": 404,
"message": "Not found",
}],
"code": 404,
"message": "Not found",
}
})
}
},
)
geo_view = GeoRedirectView.as_view(
geo_urls={
'CA': 'firefox.new',
'US': 'firefox',
"CA": "firefox.new",
"US": "firefox",
},
default_url='https://abide.dude'
default_url="https://abide.dude",
)
@override_settings(DEV=False)
class TestGeoRedirectView(TestCase):
def get_response(self, country):
with patch('bedrock.base.views.get_country_from_request') as geo_mock:
with patch("bedrock.base.views.get_country_from_request") as geo_mock:
geo_mock.return_value = country
rf = RequestFactory()
req = rf.get('/')
req = rf.get("/")
return geo_view(req)
def test_special_country(self):
resp = self.get_response('CA')
resp = self.get_response("CA")
assert resp.status_code == 302
assert resp['location'] == '/firefox/new/'
assert resp["location"] == "/firefox/new/"
resp = self.get_response('US')
resp = self.get_response("US")
assert resp.status_code == 302
assert resp['location'] == '/firefox/'
assert resp["location"] == "/firefox/"
def test_other_country(self):
resp = self.get_response('DE')
resp = self.get_response("DE")
assert resp.status_code == 302
assert resp['location'] == 'https://abide.dude'
assert resp["location"] == "https://abide.dude"
resp = self.get_response('JA')
resp = self.get_response("JA")
assert resp.status_code == 302
assert resp['location'] == 'https://abide.dude'
assert resp["location"] == "https://abide.dude"
def test_invalid_country(self):
resp = self.get_response('dude')
resp = self.get_response("dude")
assert resp.status_code == 302
assert resp['location'] == 'https://abide.dude'
assert resp["location"] == "https://abide.dude"
resp = self.get_response('42')
resp = self.get_response("42")
assert resp.status_code == 302
assert resp['location'] == 'https://abide.dude'
assert resp["location"] == "https://abide.dude"
geo_template_view = GeoTemplateView.as_view(
geo_template_names={
'DE': 'firefox-klar.html',
'GB': 'firefox-focus.html',
"DE": "firefox-klar.html",
"GB": "firefox-focus.html",
},
template_name='firefox-mobile.html',
template_name="firefox-mobile.html",
)
class TestGeoTemplateView(TestCase):
def get_template(self, country):
with patch('bedrock.firefox.views.l10n_utils.render') as render_mock:
with patch('bedrock.base.views.get_country_from_request') as geo_mock:
with patch("bedrock.firefox.views.l10n_utils.render") as render_mock:
with patch("bedrock.base.views.get_country_from_request") as geo_mock:
geo_mock.return_value = country
rf = RequestFactory()
req = rf.get('/')
req = rf.get("/")
geo_template_view(req)
return render_mock.call_args[0][1][0]
def test_country_template(self):
template = self.get_template('DE')
assert template == 'firefox-klar.html'
template = self.get_template("DE")
assert template == "firefox-klar.html"
def test_default_template(self):
template = self.get_template('US')
assert template == 'firefox-mobile.html'
template = self.get_template("US")
assert template == "firefox-mobile.html"
def test_no_country(self):
template = self.get_template(None)
assert template == 'firefox-mobile.html'
assert template == "firefox-mobile.html"

Просмотреть файл

@ -5,7 +5,7 @@ from mock import patch
from bedrock.base import waffle
@patch('bedrock.base.waffle.config')
@patch("bedrock.base.waffle.config")
def test_switch_helper(config_mock):
waffle.switch('dude-and-walter')
config_mock.assert_called_with('DUDE_AND_WALTER', namespace='SWITCH', default=str(settings.DEV), parser=bool)
waffle.switch("dude-and-walter")
config_mock.assert_called_with("DUDE_AND_WALTER", namespace="SWITCH", default=str(settings.DEV), parser=bool)

Просмотреть файл

@ -6,74 +6,73 @@ from bedrock.mozorg.tests import TestCase
GOOD_CONFIG = {
'THE_DUDE': 'abides',
'BOWLING': 'true',
"THE_DUDE": "abides",
"BOWLING": "true",
}
@patch.object(waffle_config, 'get_config_dict',
return_value=GOOD_CONFIG)
@patch.object(waffle_config, "get_config_dict", return_value=GOOD_CONFIG)
class TestConfigDBEnv(TestCase):
def setUp(self):
self.cdbe = waffle_config.ConfigDBEnv()
self.config = ConfigManager([self.cdbe])
def test_db_config(self, gcd_mock):
self.assertEqual('abides', self.config('the_dude'))
self.assertEqual('abides', self.config('dude', namespace='the'))
self.assertTrue(self.config('bowling', parser=bool))
self.assertTrue(self.config('BOWLING', parser=bool))
self.assertEqual("abides", self.config("the_dude"))
self.assertEqual("abides", self.config("dude", namespace="the"))
self.assertTrue(self.config("bowling", parser=bool))
self.assertTrue(self.config("BOWLING", parser=bool))
with self.assertRaises(ConfigurationMissingError):
self.config('donnie')
self.config("donnie")
def test_db_config_cache(self, gcd_mock):
self.assertEqual('abides', self.config('the_dude'))
self.assertEqual('abides', self.config('the_dude'))
self.assertEqual("abides", self.config("the_dude"))
self.assertEqual("abides", self.config("the_dude"))
self.assertEqual(gcd_mock.call_count, 1)
self.cdbe.last_update -= self.cdbe.timeout + 1
self.assertEqual('abides', self.config('the_dude'))
self.assertEqual('abides', self.config('the_dude'))
self.assertEqual("abides", self.config("the_dude"))
self.assertEqual("abides", self.config("the_dude"))
self.assertEqual(gcd_mock.call_count, 2)
class TestDictOf(TestCase):
def test_dict_of(self):
parser = waffle_config.DictOf(int)
assert parser('dude:1,walter:2,donnie:3') == {
'dude': 1,
'walter': 2,
'donnie': 3,
assert parser("dude:1,walter:2,donnie:3") == {
"dude": 1,
"walter": 2,
"donnie": 3,
}
def test_dict_of_whitespace(self):
parser = waffle_config.DictOf(int)
assert parser(' dude:1, walter: 2 , donnie : 3 ') == {
'dude': 1,
'walter': 2,
'donnie': 3,
assert parser(" dude:1, walter: 2 , donnie : 3 ") == {
"dude": 1,
"walter": 2,
"donnie": 3,
}
def test_dict_of_floats(self):
parser = waffle_config.DictOf(float)
assert parser('dude:1,walter:2,donnie:3.3') == {
'dude': 1.0,
'walter': 2.0,
'donnie': 3.3,
assert parser("dude:1,walter:2,donnie:3.3") == {
"dude": 1.0,
"walter": 2.0,
"donnie": 3.3,
}
def test_dict_of_strings(self):
parser = waffle_config.DictOf(str)
assert parser('dude:abides,walter:rages,donnie:bowls') == {
'dude': 'abides',
'walter': 'rages',
'donnie': 'bowls',
assert parser("dude:abides,walter:rages,donnie:bowls") == {
"dude": "abides",
"walter": "rages",
"donnie": "bowls",
}
def test_wrong_type(self):
parser = waffle_config.DictOf(int)
with self.assertRaises(ValueError):
parser('dude:abides,walter:2,donnie:3')
parser("dude:abides,walter:2,donnie:3")
def test_empty(self):
parser = waffle_config.DictOf(int)
assert parser('') == {}
assert parser("") == {}

Просмотреть файл

@ -18,7 +18,7 @@ def set_url_prefix(prefix):
def get_url_prefix():
"""Get the prefix for the current thread, or None."""
return getattr(_local, 'prefix', None)
return getattr(_local, "prefix", None)
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
@ -26,7 +26,7 @@ def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
prefixer = get_url_prefix()
if prefixer:
prefix = prefix or '/'
prefix = prefix or "/"
url = django_reverse(viewname, urlconf, args, kwargs, prefix)
if prefixer:
url = prefixer.fix(url)
@ -50,8 +50,7 @@ def _get_language_map():
# en to en-GB (not en-US), es to es-AR (not es-ES), etc. in alphabetical
# order. To override this behavior, explicitly define a preferred locale
# map with the CANONICAL_LOCALES setting.
langs.update((k.split('-')[0], v) for k, v in LUM.items() if
k.split('-')[0] not in langs)
langs.update((k.split("-")[0], v) for k, v in LUM.items() if k.split("-")[0] not in langs)
return langs
@ -63,7 +62,7 @@ def find_supported(lang):
lang = lang.lower()
if lang in FULL_LANGUAGE_MAP:
return FULL_LANGUAGE_MAP[lang]
pre = lang.split('-')[0]
pre = lang.split("-")[0]
if pre in FULL_LANGUAGE_MAP:
return FULL_LANGUAGE_MAP[pre]
@ -74,16 +73,16 @@ def split_path(path_):
locale will be empty if it isn't found.
"""
path = path_.lstrip('/')
path = path_.lstrip("/")
# Use partition instead of split since it always returns 3 parts
first, _, rest = path.partition('/')
first, _, rest = path.partition("/")
supported = find_supported(first)
if supported:
return supported, rest
else:
return '', path
return "", path
class Prefixer:
@ -98,7 +97,7 @@ class Prefixer:
user's Accept-Language header to determine which is best. This
mostly follows the RFCs but read bug 439568 for details.
"""
accept_lang = self.request.META.get('HTTP_ACCEPT_LANGUAGE')
accept_lang = self.request.META.get("HTTP_ACCEPT_LANGUAGE")
if accept_lang:
best = self.get_best_language(accept_lang)
if best:
@ -115,13 +114,13 @@ class Prefixer:
return supported
def fix(self, path):
path = path.lstrip('/')
url_parts = [self.request.META['SCRIPT_NAME']]
path = path.lstrip("/")
url_parts = [self.request.META["SCRIPT_NAME"]]
if path.partition('/')[0] not in settings.SUPPORTED_NONLOCALES:
if path.partition("/")[0] not in settings.SUPPORTED_NONLOCALES:
locale = self.locale if self.locale else self.get_language()
url_parts.append(locale)
url_parts.append(path)
return '/'.join(url_parts)
return "/".join(url_parts)

Просмотреть файл

@ -26,6 +26,7 @@ class GeoTemplateView(l10n_utils.L10nTemplateView):
If the requesting country isn't in the list it falls back to the `template_name`
setting like the normal TemplateView class.
"""
# dict of country codes to template names
geo_template_names = None
@ -49,7 +50,7 @@ class GeoRedirectView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
country_code = get_country_from_request(self.request)
url = self.geo_urls.get(country_code, self.default_url)
if re.match(r'https?://', url, re.I):
if re.match(r"https?://", url, re.I):
self.url = url
else:
self.pattern_name = url
@ -69,33 +70,40 @@ def geolocate(request):
"""
country_code = get_country_from_request(request)
if country_code is None:
return JsonResponse({
"error": {
"errors": [{
"domain": "geolocation",
"reason": "notFound",
return JsonResponse(
{
"error": {
"errors": [
{
"domain": "geolocation",
"reason": "notFound",
"message": "Not found",
}
],
"code": 404,
"message": "Not found",
}],
"code": 404,
"message": "Not found",
}
}, status=404)
}
},
status=404,
)
return JsonResponse({
'country_code': country_code,
})
return JsonResponse(
{
"country_code": country_code,
}
)
# file names and max seconds since last run
HEALTH_FILES = (
('download_database', 600),
('update_locales', 600),
("download_database", 600),
("update_locales", 600),
)
DB_INFO_FILE = getenv('AWS_DB_JSON_DATA_FILE', f'{settings.DATA_PATH}/bedrock_db_info.json')
GIT_SHA = getenv('GIT_SHA')
BUCKET_NAME = getenv('AWS_DB_S3_BUCKET', 'bedrock-db-dev')
REGION_NAME = os.getenv('AWS_DB_REGION', 'us-west-2')
S3_BASE_URL = 'https://s3-{}.amazonaws.com/{}'.format(
DB_INFO_FILE = getenv("AWS_DB_JSON_DATA_FILE", f"{settings.DATA_PATH}/bedrock_db_info.json")
GIT_SHA = getenv("GIT_SHA")
BUCKET_NAME = getenv("AWS_DB_S3_BUCKET", "bedrock-db-dev")
REGION_NAME = os.getenv("AWS_DB_REGION", "us-west-2")
S3_BASE_URL = "https://s3-{}.amazonaws.com/{}".format(
REGION_NAME,
BUCKET_NAME,
)
@ -104,38 +112,41 @@ S3_BASE_URL = 'https://s3-{}.amazonaws.com/{}'.format(
def get_l10n_repo_info():
repo = git.GitRepo(settings.LOCALES_PATH, settings.LOCALES_REPO)
fluent_repo = git.GitRepo(settings.FLUENT_REPO_PATH, settings.FLUENT_REPO_URL)
return ({
'latest_ref': repo.current_hash,
'last_updated': repo.last_updated,
'repo_url': repo.clean_remote_url,
}, {
'latest_ref': fluent_repo.current_hash,
'last_updated': fluent_repo.last_updated,
'repo_url': fluent_repo.clean_remote_url,
})
return (
{
"latest_ref": repo.current_hash,
"last_updated": repo.last_updated,
"repo_url": repo.clean_remote_url,
},
{
"latest_ref": fluent_repo.current_hash,
"last_updated": fluent_repo.last_updated,
"repo_url": fluent_repo.clean_remote_url,
},
)
def get_db_file_url(filename):
return '/'.join([S3_BASE_URL, filename])
return "/".join([S3_BASE_URL, filename])
def get_extra_server_info():
server_name = [getattr(settings, x) for x in ['HOSTNAME', 'CLUSTER_NAME']]
server_name = '.'.join(x for x in server_name if x)
server_name = [getattr(settings, x) for x in ["HOSTNAME", "CLUSTER_NAME"]]
server_name = ".".join(x for x in server_name if x)
server_info = {
'name': server_name,
'git_sha': GIT_SHA,
"name": server_name,
"git_sha": GIT_SHA,
}
try:
with open(DB_INFO_FILE, 'r') as fp:
with open(DB_INFO_FILE, "r") as fp:
db_info = json.load(fp)
except (IOError, ValueError):
pass
else:
db_info['last_update'] = timeago.format(datetime.fromtimestamp(db_info['updated']))
db_info['file_url'] = get_db_file_url(db_info['file_name'])
db_info["last_update"] = timeago.format(datetime.fromtimestamp(db_info["updated"]))
db_info["file_url"] = get_db_file_url(db_info["file_name"])
for key, value in db_info.items():
server_info['db_%s' % key] = value
server_info["db_%s" % key] = value
return server_info
@ -146,12 +157,12 @@ def cron_health_check(request):
results = []
check_pass = True
for fname, max_time in HEALTH_FILES:
fpath = f'{settings.DATA_PATH}/last-run-{fname}'
fpath = f"{settings.DATA_PATH}/last-run-{fname}"
try:
last_check = os.path.getmtime(fpath)
except OSError:
check_pass = False
results.append((fname, max_time, 'None', False))
results.append((fname, max_time, "None", False))
continue
time_since = int(time() - last_check)
@ -163,7 +174,7 @@ def cron_health_check(request):
results.append((fname, max_time, time_since, task_pass))
git_repos = git.GitRepoState.objects.exclude(repo_name='').order_by('repo_name', '-latest_ref_timestamp')
git_repos = git.GitRepoState.objects.exclude(repo_name="").order_by("repo_name", "-latest_ref_timestamp")
unique_repos = {}
for repo in git_repos:
if repo.repo_name in unique_repos:
@ -171,21 +182,26 @@ def cron_health_check(request):
unique_repos[repo.repo_name] = repo
l10n_repo, fluent_repo = get_l10n_repo_info()
return render(request, 'cron-health-check.html', {
'results': results,
'server_info': get_extra_server_info(),
'success': check_pass,
'git_repos': unique_repos.values(),
'l10n_repo': l10n_repo,
'fluent_repo': fluent_repo,
}, status=200 if check_pass else 500)
return render(
request,
"cron-health-check.html",
{
"results": results,
"server_info": get_extra_server_info(),
"success": check_pass,
"git_repos": unique_repos.values(),
"l10n_repo": l10n_repo,
"fluent_repo": fluent_repo,
},
status=200 if check_pass else 500,
)
def server_error_view(request, template_name='500.html'):
def server_error_view(request, template_name="500.html"):
"""500 error handler that runs context processors."""
return l10n_utils.render(request, template_name, ftl_files=['500'], status=500)
return l10n_utils.render(request, template_name, ftl_files=["500"], status=500)
def page_not_found_view(request, exception=None, template_name='404.html'):
def page_not_found_view(request, exception=None, template_name="404.html"):
"""404 error handler that runs context processors."""
return l10n_utils.render(request, template_name, ftl_files=['404', '500'], status=404)
return l10n_utils.render(request, template_name, ftl_files=["404", "500"], status=404)

Просмотреть файл

@ -20,5 +20,5 @@ def switch(name):
would check for an environment variable called `SWITCH_DUDE_AND_WALTER`. The string from the
`switch()` call is converted to uppercase and dashes replaced with underscores.
"""
env_name = name.upper().replace('-', '_')
return config(env_name, default=str(settings.DEV), parser=bool, namespace='SWITCH')
env_name = name.upper().replace("-", "_")
return config(env_name, default=str(settings.DEV), parser=bool, namespace="SWITCH")

Просмотреть файл

@ -18,6 +18,7 @@ class DictOf:
>>> parser('en:10,de:20')
{'en': 10, 'de': 20}
"""
def __init__(self, val_parser):
self.val_parser = val_parser
@ -28,8 +29,8 @@ class DictOf:
if not val:
return out
for part in val.split(','):
k, v = part.split(':')
for part in val.split(","):
k, v = part.split(":")
out[k.strip()] = val_parser(v.strip())
return out
@ -64,8 +65,10 @@ class ConfigDBEnv(ConfigDictEnv):
return configs
config = ConfigManager([
ConfigOSEnv(),
ConfigEnvFileEnv('.env'),
ConfigDBEnv(),
])
config = ConfigManager(
[
ConfigOSEnv(),
ConfigEnvFileEnv(".env"),
ConfigDBEnv(),
]
)

Просмотреть файл

@ -7,31 +7,28 @@ from bedrock.utils.git import GitRepo
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False,
help='If no error occurs, swallow all output.'),
parser.add_argument('-f', '--force', action='store_true', dest='force', default=False,
help='Load the data even if nothing new from git.'),
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="If no error occurs, swallow all output."),
parser.add_argument("-f", "--force", action="store_true", dest="force", default=False, help="Load the data even if nothing new from git."),
def output(self, msg):
if not self.quiet:
print(msg)
def handle(self, *args, **options):
self.quiet = options['quiet']
repo = GitRepo(settings.CONTENT_CARDS_PATH, settings.CONTENT_CARDS_REPO,
branch_name=settings.CONTENT_CARDS_BRANCH, name='Content Cards')
self.output('Updating git repo')
self.quiet = options["quiet"]
repo = GitRepo(settings.CONTENT_CARDS_PATH, settings.CONTENT_CARDS_REPO, branch_name=settings.CONTENT_CARDS_BRANCH, name="Content Cards")
self.output("Updating git repo")
repo.update()
if not (options['force'] or repo.has_changes()):
self.output('No content card updates')
if not (options["force"] or repo.has_changes()):
self.output("No content card updates")
return
self.output('Loading content cards into database')
self.output("Loading content cards into database")
count = ContentCard.objects.refresh()
self.output('%s content cards successfully loaded' % count)
self.output("%s content cards successfully loaded" % count)
repo.set_db_latest()
self.output('Saved latest git repo state to database')
self.output('Done!')
self.output("Saved latest git repo state to database")
self.output("Done!")

Просмотреть файл

@ -11,7 +11,7 @@ from pathlib import Path
from bedrock.base.urlresolvers import reverse
URL_RE = re.compile(r'^https?://', re.I)
URL_RE = re.compile(r"^https?://", re.I)
def get_page_content_cards(page_name, locale):
@ -19,45 +19,47 @@ def get_page_content_cards(page_name, locale):
def get_data_from_file_path(file_path):
card_name, locale = file_path.stem.split('.')
card_name, locale = file_path.stem.split(".")
page_name = file_path.parts[-2]
page_id = '{}-{}-{}'.format(page_name, locale, card_name)
page_id = "{}-{}-{}".format(page_name, locale, card_name)
return {
'locale': locale,
'card_name': card_name,
'page_name': page_name,
'page_id': page_id,
"locale": locale,
"card_name": card_name,
"page_name": page_name,
"page_id": page_id,
}
class ContentCardManager(models.Manager):
def get_card(self, page_name, name, locale='en-US'):
card_id = '{}-{}-{}'.format(page_name, locale, name)
def get_card(self, page_name, name, locale="en-US"):
card_id = "{}-{}-{}".format(page_name, locale, name)
return self.get(id=card_id)
def get_page_cards(self, page_name, locale='en-US'):
def get_page_cards(self, page_name, locale="en-US"):
cards = self.filter(page_name=page_name, locale=locale)
return {c.card_name: c.card_data for c in cards}
def refresh(self):
card_objs = []
cc_path = Path(settings.CONTENT_CARDS_PATH, 'content')
cc_path = Path(settings.CONTENT_CARDS_PATH, "content")
with transaction.atomic(using=self.db):
self.all().delete()
cc_files = cc_path.glob('*/*.json')
cc_files = cc_path.glob("*/*.json")
for ccf in cc_files:
path_data = get_data_from_file_path(ccf)
with ccf.open(encoding='utf-8') as ccfo:
with ccf.open(encoding="utf-8") as ccfo:
data = json.load(ccfo)
card_objs.append(ContentCard(
id=path_data['page_id'],
card_name=path_data['card_name'],
page_name=path_data['page_name'],
locale=path_data['locale'],
content=data.pop('html_content', ''),
data=data,
))
card_objs.append(
ContentCard(
id=path_data["page_id"],
card_name=path_data["card_name"],
page_name=path_data["page_name"],
locale=path_data["locale"],
content=data.pop("html_content", ""),
data=data,
)
)
self.bulk_create(card_objs)
return len(card_objs)
@ -74,10 +76,10 @@ class ContentCard(models.Model):
objects = ContentCardManager()
class Meta:
ordering = ('id',)
ordering = ("id",)
def __str__(self):
return '{} ({})'.format(self.card_name, self.locale)
return "{} ({})".format(self.card_name, self.locale)
@property
def html(self):
@ -88,30 +90,28 @@ class ContentCard(models.Model):
"""Return a dict appropriate for calling the card() macro"""
data = {}
data.update(self.data)
if 'image' in data:
data['image_url'] = '%scontentcards/img/%s' % (settings.CONTENT_CARDS_URL,
data['image'])
del data['image']
if "image" in data:
data["image_url"] = "%scontentcards/img/%s" % (settings.CONTENT_CARDS_URL, data["image"])
del data["image"]
if 'highres_image' in data:
data['highres_image_url'] = '%scontentcards/img/%s' % (settings.CONTENT_CARDS_URL,
data['highres_image'])
del data['highres_image']
if "highres_image" in data:
data["highres_image_url"] = "%scontentcards/img/%s" % (settings.CONTENT_CARDS_URL, data["highres_image"])
del data["highres_image"]
if 'ga_title' not in data:
data['ga_title'] = data['title']
if "ga_title" not in data:
data["ga_title"] = data["title"]
if 'media_icon' in data:
data['media_icon'] = 'mzp-has-%s' % data['media_icon']
if "media_icon" in data:
data["media_icon"] = "mzp-has-%s" % data["media_icon"]
if 'aspect_ratio' in data:
data['aspect_ratio'] = 'mzp-has-aspect-%s' % data['aspect_ratio']
if "aspect_ratio" in data:
data["aspect_ratio"] = "mzp-has-aspect-%s" % data["aspect_ratio"]
if 'size' in data:
data['class'] = 'mzp-c-card-%s' % data['size']
del data['size']
if "size" in data:
data["class"] = "mzp-c-card-%s" % data["size"]
del data["size"]
if 'link_url' in data and not URL_RE.match(data['link_url']):
data['link_url'] = reverse(data['link_url'])
if "link_url" in data and not URL_RE.match(data["link_url"]):
data["link_url"] = reverse(data["link_url"])
return data

Просмотреть файл

@ -8,35 +8,35 @@ from bedrock.contentcards import models
from bedrock.mozorg.tests import TestCase
DATA_PATH = Path(__file__).parent.joinpath('test_data')
DATA_PATH = Path(__file__).parent.joinpath("test_data")
class TestGetDataFromFilePath(TestCase):
def test_the_func(self):
self.assertDictEqual(
models.get_data_from_file_path(Path('content/home/dude.en-US.json')),
models.get_data_from_file_path(Path("content/home/dude.en-US.json")),
{
'locale': 'en-US',
'card_name': 'dude',
'page_name': 'home',
'page_id': 'home-en-US-dude',
}
"locale": "en-US",
"card_name": "dude",
"page_name": "home",
"page_id": "home-en-US-dude",
},
)
self.assertDictEqual(
models.get_data_from_file_path(Path('content/the-dude/10th.de.json')),
models.get_data_from_file_path(Path("content/the-dude/10th.de.json")),
{
'locale': 'de',
'card_name': '10th',
'page_name': 'the-dude',
'page_id': 'the-dude-de-10th',
}
"locale": "de",
"card_name": "10th",
"page_name": "the-dude",
"page_id": "the-dude-de-10th",
},
)
@override_settings(
CONTENT_CARDS_PATH=str(DATA_PATH),
CONTENT_CARDS_URL='/media/',
STATIC_URL='/media/',
CONTENT_CARDS_URL="/media/",
STATIC_URL="/media/",
)
class TestContentCardModel(TestCase):
def setUp(self):
@ -44,62 +44,60 @@ class TestContentCardModel(TestCase):
def test_get_card_missing(self):
with self.assertRaises(models.ContentCard.DoesNotExist):
models.ContentCard.objects.get_card('home', 'card_10')
models.ContentCard.objects.get_card("home", "card_10")
def test_card_data(self):
card1 = models.ContentCard.objects.get_card('home', 'card_1')
self.assertEqual(card1.id, 'home-en-US-card_1')
self.assertEqual(card1.page_name, 'home')
self.assertEqual(card1.card_name, 'card_1')
self.assertEqual(card1.locale, 'en-US')
with self.activate('de'):
card1 = models.ContentCard.objects.get_card("home", "card_1")
self.assertEqual(card1.id, "home-en-US-card_1")
self.assertEqual(card1.page_name, "home")
self.assertEqual(card1.card_name, "card_1")
self.assertEqual(card1.locale, "en-US")
with self.activate("de"):
card_data = card1.card_data
self.assertDictEqual(
card_data,
{
'title': 'We keep your data safe, never sold.',
'ga_title': 'We keep your data safe, never sold.',
'highres_image_url': '/media/contentcards/img/home/ffyr-high-res.191bff93b820.png',
'media_icon': 'mzp-has-video',
'class': 'mzp-c-card-large',
'image_url': '/media/contentcards/img/home/ffyr.75c74c6ba409.png',
'youtube_id': 'rZAQ6vgt8nE',
'aspect_ratio': 'mzp-has-aspect-16-9',
'desc': u'You have the right to your own life \u2014 and your own data. '
u'Everything we make and do fights for you.',
'link_url': '/de/firefox/',
'tag_label': 'Video',
}
"title": "We keep your data safe, never sold.",
"ga_title": "We keep your data safe, never sold.",
"highres_image_url": "/media/contentcards/img/home/ffyr-high-res.191bff93b820.png",
"media_icon": "mzp-has-video",
"class": "mzp-c-card-large",
"image_url": "/media/contentcards/img/home/ffyr.75c74c6ba409.png",
"youtube_id": "rZAQ6vgt8nE",
"aspect_ratio": "mzp-has-aspect-16-9",
"desc": "You have the right to your own life \u2014 and your own data. Everything we make and do fights for you.",
"link_url": "/de/firefox/",
"tag_label": "Video",
},
)
def test_get_page_cards(self):
cards = models.ContentCard.objects.get_page_cards('home')
self.assertTrue(all(name in cards for name in
['card_%d' % i for i in range(1, 6)]))
cards = models.ContentCard.objects.get_page_cards("home")
self.assertTrue(all(name in cards for name in ["card_%d" % i for i in range(1, 6)]))
self.assertDictEqual(
cards['card_2'],
cards["card_2"],
{
'aspect_ratio': 'mzp-has-aspect-1-1',
'desc': 'Microsoft is giving up on an independent shared platform for the internet, '
'and in doing so, hands over control of more of online life to Google.',
'highres_image_url': '/media/contentcards/img/home/edge-high-res.e3fd47cab8f0.png',
'image_url': '/media/contentcards/img/home/edge.72822d0ff717.png',
'link_url': 'https://blog.mozilla.org/blog/2018/12/06/goodbye-edge/?utm_source='
'www.mozilla.org&utm_medium=referral&utm_campaign=homepage&utm_content=card',
'class': 'mzp-c-card-small',
'tag_label': 'Internet Health',
'title': 'Goodbye, EdgeHTML',
'ga_title': 'Goodbye, EdgeHTML',
}
"aspect_ratio": "mzp-has-aspect-1-1",
"desc": "Microsoft is giving up on an independent shared platform for the internet, "
"and in doing so, hands over control of more of online life to Google.",
"highres_image_url": "/media/contentcards/img/home/edge-high-res.e3fd47cab8f0.png",
"image_url": "/media/contentcards/img/home/edge.72822d0ff717.png",
"link_url": "https://blog.mozilla.org/blog/2018/12/06/goodbye-edge/?utm_source="
"www.mozilla.org&utm_medium=referral&utm_campaign=homepage&utm_content=card",
"class": "mzp-c-card-small",
"tag_label": "Internet Health",
"title": "Goodbye, EdgeHTML",
"ga_title": "Goodbye, EdgeHTML",
},
)
def test_get_page_cards_empty(self):
cards = models.ContentCard.objects.get_page_cards('home', 'fr')
cards = models.ContentCard.objects.get_page_cards("home", "fr")
self.assertEqual(cards, {})
def test_html_content(self):
card2 = models.ContentCard.objects.get_card('home', 'card_2')
self.assertFalse('html_content' in card2.data)
self.assertEqual(card2.content, '<p>This is the converted <em>Markdown</em></p>')
self.assertEqual(card2.html, Markup('<p>This is the converted <em>Markdown</em></p>'))
card2 = models.ContentCard.objects.get_card("home", "card_2")
self.assertFalse("html_content" in card2.data)
self.assertEqual(card2.content, "<p>This is the converted <em>Markdown</em></p>")
self.assertEqual(card2.html, Markup("<p>This is the converted <em>Markdown</em></p>"))

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -11,31 +11,31 @@ from bedrock.contentful.models import ContentfulEntry
def data_hash(data):
str_data = json.dumps(data, sort_keys=True)
return sha256(str_data.encode('utf8')).hexdigest()
return sha256(str_data.encode("utf8")).hexdigest()
class Command(BaseCommand):
rf = RequestFactory()
def add_arguments(self, parser):
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False,
help='If no error occurs, swallow all output.'),
parser.add_argument('-f', '--force', action='store_true', dest='force', default=False,
help='Load the data even if nothing new from Contentful.'),
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="If no error occurs, swallow all output."),
parser.add_argument(
"-f", "--force", action="store_true", dest="force", default=False, help="Load the data even if nothing new from Contentful."
),
def log(self, msg):
if not self.quiet:
print(msg)
def handle(self, *args, **options):
self.quiet = options['quiet']
self.force = options['force']
self.quiet = options["quiet"]
self.force = options["force"]
if settings.CONTENTFUL_SPACE_ID and settings.CONTENTFUL_SPACE_KEY:
self.log('Updating Contentful Data')
self.log("Updating Contentful Data")
added, updated = self.refresh()
self.log(f'Done. Added: {added}. Updated: {updated}')
self.log(f"Done. Added: {added}. Updated: {updated}")
else:
print('Contentful credentials not configured')
print("Contentful credentials not configured")
return
def refresh(self):
@ -43,15 +43,15 @@ class Command(BaseCommand):
added_count = 0
content_ids = []
for ctype in settings.CONTENTFUL_CONTENT_TYPES:
for entry in ContentfulPage.client.entries({'content_type': ctype, 'include': 0}).items:
content_ids.append((ctype, entry.sys['id']))
for entry in ContentfulPage.client.entries({"content_type": ctype, "include": 0}).items:
content_ids.append((ctype, entry.sys["id"]))
for ctype, page_id in content_ids:
request = self.rf.get('/')
request.locale = 'en-US'
request = self.rf.get("/")
request.locale = "en-US"
page = ContentfulPage(request, page_id)
page_data = page.get_content()
language = page_data['info']['lang']
language = page_data["info"]["lang"]
hash = data_hash(page_data)
try:

Просмотреть файл

@ -12,7 +12,7 @@ class ContentfulEntryManager(models.Manager):
return self.get(content_type=content_type, language=lang).data
def get_homepage(self, lang):
return self.get(content_type='connectHomepage', language=lang).data
return self.get(content_type="connectHomepage", language=lang).data
class ContentfulEntry(models.Model):

Просмотреть файл

@ -5,41 +5,41 @@ from django_jinja import library
# based on bleach.sanitizer.ALLOWED_TAGS
ALLOWED_TAGS = [
'a',
'abbr',
'acronym',
'b',
'blockquote',
'button',
'code',
'div',
'em',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'i',
'img',
'li',
'ol',
'p',
'small',
'span',
'strike',
'strong',
'ul',
"a",
"abbr",
"acronym",
"b",
"blockquote",
"button",
"code",
"div",
"em",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"i",
"img",
"li",
"ol",
"p",
"small",
"span",
"strike",
"strong",
"ul",
]
ALLOWED_ATTRS = [
'alt',
'class',
'href',
'id',
'src',
'srcset',
'rel',
'title',
"alt",
"class",
"href",
"id",
"src",
"srcset",
"rel",
"title",
]
@ -47,7 +47,7 @@ def _allowed_attrs(tag, name, value):
if name in ALLOWED_ATTRS:
return True
if name.startswith('data-'):
if name.startswith("data-"):
return True
return False

Просмотреть файл

@ -1,7 +1,3 @@
from bedrock.redirects.util import redirect
redirectpatterns = (
redirect(r'^exp/firefox/new/nav/?$', 'firefox.new'),
)
redirectpatterns = (redirect(r"^exp/firefox/new/nav/?$", "firefox.new"),)

Просмотреть файл

@ -13,12 +13,10 @@ from bedrock.mozorg.tests import TestCase
@override_settings(DEV=False)
@patch('bedrock.exp.views.l10n_utils.render')
@patch("bedrock.exp.views.l10n_utils.render")
class TestExpFirefoxNew(TestCase):
def test_download_template(self, render_mock):
req = RequestFactory().get('/exp/firefox/new/')
req.locale = 'en-US'
req = RequestFactory().get("/exp/firefox/new/")
req.locale = "en-US"
views.new(req)
render_mock.assert_called_once_with(
req, 'exp/firefox/new/download.html', ANY, ftl_files='firefox/new/desktop'
)
render_mock.assert_called_once_with(req, "exp/firefox/new/download.html", ANY, ftl_files="firefox/new/desktop")

Просмотреть файл

@ -9,13 +9,13 @@ from bedrock.releasenotes import version_re
from bedrock.exp import views
latest_re = r'^firefox(?:/(?P<version>%s))?/%s/$'
whatsnew_re_all = latest_re % (version_re, 'whatsnew/all')
latest_re = r"^firefox(?:/(?P<version>%s))?/%s/$"
whatsnew_re_all = latest_re % (version_re, "whatsnew/all")
urlpatterns = (
page('opt-out', 'exp/opt-out.html'),
page('firefox', 'exp/firefox/index.html', ftl_files=['firefox/home']),
url(r'^firefox/new/$', views.new, name='exp.firefox.new'),
url(r'^$', views.home_view, name='exp.mozorg.home'),
page('firefox/accounts', 'exp/firefox/accounts.html', ftl_files=['firefox/accounts']),
page("opt-out", "exp/opt-out.html"),
page("firefox", "exp/firefox/index.html", ftl_files=["firefox/home"]),
url(r"^firefox/new/$", views.new, name="exp.firefox.new"),
url(r"^$", views.home_view, name="exp.mozorg.home"),
page("firefox/accounts", "exp/firefox/accounts.html", ftl_files=["firefox/accounts"]),
)

Просмотреть файл

@ -12,50 +12,48 @@ from bedrock.pocketfeed.models import PocketArticle
def new(request):
# note: v and xv params only allow a-z, A-Z, 0-9, -, and _ characters
experience = request.GET.get('xv', None)
variant = request.GET.get('v', None)
experience = request.GET.get("xv", None)
variant = request.GET.get("v", None)
# ensure experience matches pre-defined value
if experience not in ['']: # place expected ?xv= values in this list
if experience not in [""]: # place expected ?xv= values in this list
experience = None
# ensure variant matches pre-defined value
if variant not in ['']: # place expected ?v= values in this list
if variant not in [""]: # place expected ?v= values in this list
variant = None
template_name = 'exp/firefox/new/download.html'
template_name = "exp/firefox/new/download.html"
# no harm done by passing 'v' to template, even when no experiment is running
# (also makes tests easier to maintain by always sending a context)
context = {'experience': experience, 'v': variant}
context = {"experience": experience, "v": variant}
return l10n_utils.render(request, template_name, context,
ftl_files='firefox/new/desktop')
return l10n_utils.render(request, template_name, context, ftl_files="firefox/new/desktop")
def home_view(request):
locale = l10n_utils.get_locale(request)
donate_params = settings.DONATE_PARAMS.get(
locale, settings.DONATE_PARAMS['en-US'])
donate_params = settings.DONATE_PARAMS.get(locale, settings.DONATE_PARAMS["en-US"])
# presets are stored as a string but, for the home banner
# we need it as a list.
donate_params['preset_list'] = donate_params['presets'].split(',')
donate_params["preset_list"] = donate_params["presets"].split(",")
ctx = {
'donate_params': donate_params,
'pocket_articles': PocketArticle.objects.all()[:4],
'ftl_files': ['mozorg/home-mr1-promo'],
'active_locales': ['de', 'fr', 'en-US']
"donate_params": donate_params,
"pocket_articles": PocketArticle.objects.all()[:4],
"ftl_files": ["mozorg/home-mr1-promo"],
"active_locales": ["de", "fr", "en-US"],
}
if locale == 'de':
template_name = 'exp/home/home-de.html'
ctx['page_content_cards'] = get_page_content_cards('home-de', 'de')
elif locale == 'fr':
template_name = 'exp/home/home-fr.html'
ctx['page_content_cards'] = get_page_content_cards('home-fr', 'fr')
if locale == "de":
template_name = "exp/home/home-de.html"
ctx["page_content_cards"] = get_page_content_cards("home-de", "de")
elif locale == "fr":
template_name = "exp/home/home-fr.html"
ctx["page_content_cards"] = get_page_content_cards("home-fr", "fr")
else:
template_name = 'exp/home/home-en.html'
ctx['page_content_cards'] = get_page_content_cards('home-2019', 'en-US')
template_name = "exp/home/home-en.html"
ctx["page_content_cards"] = get_page_content_cards("home-2019", "en-US")
return l10n_utils.render(request, template_name, ctx)

Просмотреть файл

@ -21,12 +21,12 @@ class ExternalFile:
try:
fileinfo = settings.EXTERNAL_FILES[file_id]
except KeyError:
raise ValueError('No external file with the {0} ID.'.format(file_id))
raise ValueError("No external file with the {0} ID.".format(file_id))
self._cache = caches['externalfiles']
self._cache = caches["externalfiles"]
self.file_id = file_id
self.name = fileinfo['name']
self.cache_key = 'externalfile:{}'.format(self.file_id)
self.name = fileinfo["name"]
self.cache_key = "externalfile:{}".format(self.file_id)
self.file_path = os.path.join(settings.EXTERNAL_FILES_PATH, self.name)
@property
@ -75,11 +75,11 @@ class ExternalFile:
:return: str or None
:raises: ValueError
"""
with codecs.open(self.file_path, encoding='utf-8') as fp:
with codecs.open(self.file_path, encoding="utf-8") as fp:
content = fp.read()
if not content:
raise ValueError('%s is empty' % self.name)
raise ValueError("%s is empty" % self.name)
return self.validate_content(content)
@ -92,7 +92,7 @@ class ExternalFile:
def update(self):
from bedrock.externalfiles.models import ExternalFile as EFModel
log.info('Updating {0}.'.format(self.name))
log.info("Updating {0}.".format(self.name))
content = self.validate_file()
fo = self.file_object
if fo:
@ -101,7 +101,7 @@ class ExternalFile:
else:
EFModel.objects.create(name=self.file_id, content=content)
log.info('Successfully updated {0}.'.format(self.name))
log.info("Successfully updated {0}.".format(self.name))
return True
def clear_cache(self):

Просмотреть файл

@ -7,38 +7,34 @@ from bedrock.utils.git import GitRepo
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False,
help='If no error occurs, swallow all output.'),
parser.add_argument('-f', '--force', action='store_true', dest='force', default=False,
help='Load the data even if nothing new from git.'),
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="If no error occurs, swallow all output."),
parser.add_argument("-f", "--force", action="store_true", dest="force", default=False, help="Load the data even if nothing new from git."),
def output(self, msg):
if not self.quiet:
print(msg)
def handle(self, *args, **options):
self.quiet = options['quiet']
repo = GitRepo(settings.EXTERNAL_FILES_PATH, settings.EXTERNAL_FILES_REPO,
branch_name=settings.EXTERNAL_FILES_BRANCH,
name='Community Data')
self.output('Updating git repo')
self.quiet = options["quiet"]
repo = GitRepo(settings.EXTERNAL_FILES_PATH, settings.EXTERNAL_FILES_REPO, branch_name=settings.EXTERNAL_FILES_BRANCH, name="Community Data")
self.output("Updating git repo")
repo.update()
if not (options['force'] or repo.has_changes()):
self.output('No community data updates')
if not (options["force"] or repo.has_changes()):
self.output("No community data updates")
return
self.output('Loading community data into database')
self.output("Loading community data into database")
for fid, finfo in settings.EXTERNAL_FILES.items():
klass = import_string(finfo['type'])
klass = import_string(finfo["type"])
try:
klass(fid).update()
except ValueError as e:
raise CommandError(str(e))
self.output('Community data successfully loaded')
self.output("Community data successfully loaded")
repo.set_db_latest()
self.output('Saved latest git repo state to database')
self.output('Done!')
self.output("Saved latest git repo state to database")
self.output("Done!")

Просмотреть файл

@ -7,4 +7,4 @@ class ExternalFile(models.Model):
last_modified = models.DateTimeField(auto_now=True)
class Meta:
app_label = 'externalfiles'
app_label = "externalfiles"

Просмотреть файл

@ -20,17 +20,14 @@ class TestExternalFile(TestCase):
timezone.activate(utc)
def setUp(self):
settings.EXTERNAL_FILES['test'] = {
'url': 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul',
'name': 'there.is.no.data.xul'
}
settings.EXTERNAL_FILES["test"] = {"url": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "name": "there.is.no.data.xul"}
def tearDown(self):
externalfiles.ExternalFile('test').clear_cache()
del settings.EXTERNAL_FILES['test']
externalfiles.ExternalFile("test").clear_cache()
del settings.EXTERNAL_FILES["test"]
def test_last_modified(self):
"""Should return the modified timestamp."""
EFModel.objects.create(name='test', content='test')
efo = EFModel.objects.get(name='test')
self.assertEqual(externalfiles.ExternalFile('test').last_modified, efo.last_modified)
EFModel.objects.create(name="test", content="test")
efo = EFModel.objects.get(name="test")
self.assertEqual(externalfiles.ExternalFile("test").last_modified, efo.last_modified)

Просмотреть файл

@ -7,6 +7,6 @@ from bedrock.firefox.firefox_details import firefox_desktop
def latest_firefox_versions(request):
return {
'latest_firefox_version': firefox_desktop.latest_version(),
'esr_firefox_versions': firefox_desktop.esr_minor_versions,
"latest_firefox_version": firefox_desktop.latest_version(),
"esr_firefox_versions": firefox_desktop.esr_minor_versions,
}

Просмотреть файл

@ -18,57 +18,60 @@ class _ProductDetails(ProductDetails):
bouncer_url = settings.BOUNCER_URL
def _matches_query(self, info, query):
words = re.split(r',|,?\s+', query.strip().lower())
return all((word in info['name_en'].lower() or
word in info['name_native'].lower()) for word in words)
words = re.split(r",|,?\s+", query.strip().lower())
return all((word in info["name_en"].lower() or word in info["name_native"].lower()) for word in words)
class FirefoxDesktop(_ProductDetails):
download_base_url_transition = '/firefox/download/thanks/'
download_base_url_transition = "/firefox/download/thanks/"
# Human-readable platform names
platform_labels = OrderedDict([
('win64', 'Windows 64-bit'),
('win64-msi', 'Windows 64-bit MSI'),
('win64-aarch64', 'Windows ARM64/AArch64'),
('win', 'Windows 32-bit'),
('win-msi', 'Windows 32-bit MSI'),
('osx', 'macOS'),
('linux64', 'Linux 64-bit'),
('linux', 'Linux 32-bit'),
])
platform_labels = OrderedDict(
[
("win64", "Windows 64-bit"),
("win64-msi", "Windows 64-bit MSI"),
("win64-aarch64", "Windows ARM64/AArch64"),
("win", "Windows 32-bit"),
("win-msi", "Windows 32-bit MSI"),
("osx", "macOS"),
("linux64", "Linux 64-bit"),
("linux", "Linux 32-bit"),
]
)
# Recommended/modern vs traditional/legacy platforms
platform_classification = OrderedDict([
('recommended', ('win64', 'win64-msi', 'win64-aarch64', 'osx', 'linux64')),
('traditional', ('linux', 'win', 'win-msi')),
])
platform_classification = OrderedDict(
[
("recommended", ("win64", "win64-msi", "win64-aarch64", "osx", "linux64")),
("traditional", ("linux", "win", "win-msi")),
]
)
# Human-readable channel names
channel_labels = {
'nightly': 'Firefox Nightly',
'alpha': 'Developer Edition',
'devedition': 'Developer Edition',
'beta': 'Firefox Beta',
'esr': 'Firefox Extended Support Release',
'release': 'Firefox',
"nightly": "Firefox Nightly",
"alpha": "Developer Edition",
"devedition": "Developer Edition",
"beta": "Firefox Beta",
"esr": "Firefox Extended Support Release",
"release": "Firefox",
}
# Version property names in product-details
version_map = {
'nightly': 'FIREFOX_NIGHTLY',
'alpha': 'FIREFOX_DEVEDITION',
'devedition': 'FIREFOX_DEVEDITION',
'beta': 'LATEST_FIREFOX_DEVEL_VERSION',
'esr': 'FIREFOX_ESR',
'esr_next': 'FIREFOX_ESR_NEXT',
'release': 'LATEST_FIREFOX_VERSION',
"nightly": "FIREFOX_NIGHTLY",
"alpha": "FIREFOX_DEVEDITION",
"devedition": "FIREFOX_DEVEDITION",
"beta": "LATEST_FIREFOX_DEVEL_VERSION",
"esr": "FIREFOX_ESR",
"esr_next": "FIREFOX_ESR_NEXT",
"release": "LATEST_FIREFOX_VERSION",
}
def __init__(self, **kwargs):
super(FirefoxDesktop, self).__init__(**kwargs)
def platforms(self, channel='release', classified=False):
def platforms(self, channel="release", classified=False):
"""
Get the desktop platform dictionary containing slugs and corresponding
labels. If the classified option is True, it will be ordered by the
@ -85,14 +88,14 @@ class FirefoxDesktop(_ProductDetails):
return list(platforms.items())
def latest_version(self, channel='release'):
version = self.version_map.get(channel, 'LATEST_FIREFOX_VERSION')
def latest_version(self, channel="release"):
version = self.version_map.get(channel, "LATEST_FIREFOX_VERSION")
try:
return self.firefox_versions[version]
except KeyError:
if channel in ['alpha', 'devedition']:
if channel in ["alpha", "devedition"]:
# beta as a fall-back until all product-details data is updated
return self.latest_version('beta')
return self.latest_version("beta")
return None
@ -103,14 +106,14 @@ class FirefoxDesktop(_ProductDetails):
return 0
try:
return int(lv.split('.')[0])
return int(lv.split(".")[0])
except ValueError:
return 0
@property
def esr_major_versions(self):
versions = []
for channel in ('esr', 'esr_next'):
for channel in ("esr", "esr_next"):
version_int = self.latest_major_version(channel)
if version_int:
versions.append(version_int)
@ -120,33 +123,32 @@ class FirefoxDesktop(_ProductDetails):
@property
def esr_minor_versions(self):
versions = []
for channel in ('esr', 'esr_next'):
for channel in ("esr", "esr_next"):
version = self.latest_version(channel)
version_int = self.latest_major_version(channel)
if version and version_int:
versions.append(str(version).replace('esr', ''))
versions.append(str(version).replace("esr", ""))
return versions
def latest_builds(self, locale, channel='release'):
def latest_builds(self, locale, channel="release"):
"""Return build info for a locale and channel.
:param locale: locale string of the build
:param channel: channel of the build: release, beta, or aurora
:return: dict or None
"""
all_builds = (self.firefox_primary_builds,
self.firefox_beta_builds)
all_builds = (self.firefox_primary_builds, self.firefox_beta_builds)
version = self.latest_version(channel)
for builds in all_builds:
if locale in builds and version in builds[locale]:
_builds = builds[locale][version]
# Append 64-bit builds
if 'Windows' in _builds:
_builds['Windows 64-bit'] = _builds['Windows']
if 'Linux' in _builds:
_builds['Linux 64-bit'] = _builds['Linux']
if "Windows" in _builds:
_builds["Windows 64-bit"] = _builds["Windows"]
if "Linux" in _builds:
_builds["Linux 64-bit"] = _builds["Linux"]
return version, _builds
def _get_filtered_builds(self, builds, channel, version=None, query=None):
@ -167,10 +169,10 @@ class FirefoxDesktop(_ProductDetails):
continue
build_info = {
'locale': locale,
'name_en': self.languages[locale]['English'],
'name_native': self.languages[locale]['native'],
'platforms': {},
"locale": locale,
"name_en": self.languages[locale]["English"],
"name_native": self.languages[locale]["native"],
"platforms": {},
}
# only include builds that match a search query
@ -178,15 +180,13 @@ class FirefoxDesktop(_ProductDetails):
continue
for platform, label in self.platform_labels.items():
build_info['platforms'][platform] = {
'download_url': self.get_download_url(channel, version,
platform, locale,
True, True),
build_info["platforms"][platform] = {
"download_url": self.get_download_url(channel, version, platform, locale, True, True),
}
f_builds.append(build_info)
return sorted(f_builds, key=itemgetter('name_en'))
return sorted(f_builds, key=itemgetter("name_en"))
def get_filtered_full_builds(self, channel, version=None, query=None):
"""
@ -196,8 +196,7 @@ class FirefoxDesktop(_ProductDetails):
:param query: a string to match against native or english locale name
:return: list
"""
return self._get_filtered_builds(self.firefox_primary_builds,
channel, version, query)
return self._get_filtered_builds(self.firefox_primary_builds, channel, version, query)
def get_filtered_test_builds(self, channel, version=None, query=None):
"""
@ -207,13 +206,20 @@ class FirefoxDesktop(_ProductDetails):
:param query: a string to match against native or english locale name
:return: list
"""
return self._get_filtered_builds(self.firefox_beta_builds,
channel, version, query)
return self._get_filtered_builds(self.firefox_beta_builds, channel, version, query)
def get_download_url(self, channel, version, platform, locale,
force_direct=False, force_full_installer=False,
force_funnelcake=False, funnelcake_id=None,
locale_in_transition=False):
def get_download_url(
self,
channel,
version,
platform,
locale,
force_direct=False,
force_full_installer=False,
force_funnelcake=False,
funnelcake_id=None,
locale_in_transition=False,
):
"""
Get direct download url for the product.
:param channel: one of self.version_map.keys().
@ -233,23 +239,23 @@ class FirefoxDesktop(_ProductDetails):
# no longer used, but still passed in. leaving here for now
# as it will likely be used in future.
# _version = version
_locale = 'ja-JP-mac' if platform == 'osx' and locale == 'ja' else locale
channel = 'devedition' if channel == 'alpha' else channel
force_direct = True if channel != 'release' else force_direct
stub_platforms = ['win', 'win64']
esr_channels = ['esr', 'esr_next']
_locale = "ja-JP-mac" if platform == "osx" and locale == "ja" else locale
channel = "devedition" if channel == "alpha" else channel
force_direct = True if channel != "release" else force_direct
stub_platforms = ["win", "win64"]
esr_channels = ["esr", "esr_next"]
include_funnelcake_param = False
# support optional MSI installer downloads
# bug 1493205
is_msi = platform.endswith('-msi')
is_msi = platform.endswith("-msi")
if is_msi:
platform = platform[:-4]
# Bug 1345467 - Only allow specifically configured funnelcake builds
if funnelcake_id:
fc_platforms = config('FUNNELCAKE_%s_PLATFORMS' % funnelcake_id, default='', parser=ListOf(str))
fc_locales = config('FUNNELCAKE_%s_LOCALES' % funnelcake_id, default='', parser=ListOf(str))
fc_platforms = config("FUNNELCAKE_%s_PLATFORMS" % funnelcake_id, default="", parser=ListOf(str))
fc_locales = config("FUNNELCAKE_%s_LOCALES" % funnelcake_id, default="", parser=ListOf(str))
include_funnelcake_param = platform in fc_platforms and _locale in fc_locales
# Check if direct download link has been requested
@ -259,55 +265,63 @@ class FirefoxDesktop(_ProductDetails):
transition_url = self.download_base_url_transition
if funnelcake_id:
# include funnelcake in scene 2 URL
transition_url += '?f=%s' % funnelcake_id
transition_url += "?f=%s" % funnelcake_id
if locale_in_transition:
transition_url = '/%s%s' % (locale, transition_url)
transition_url = "/%s%s" % (locale, transition_url)
return transition_url
# otherwise build a full download URL
prod_name = 'firefox' if channel == 'release' else 'firefox-%s' % channel
suffix = 'latest-ssl'
prod_name = "firefox" if channel == "release" else "firefox-%s" % channel
suffix = "latest-ssl"
if is_msi:
suffix = 'msi-' + suffix
suffix = "msi-" + suffix
if channel in esr_channels:
# nothing special about ESR other than there is no stub.
# included in this contitional to avoid the following elif.
if channel == 'esr_next':
prod_name = 'firefox-esr-next'
if channel == "esr_next":
prod_name = "firefox-esr-next"
elif platform in stub_platforms and not is_msi and not force_full_installer:
# Use the stub installer for approved platforms
# append funnelcake id to version if we have one
if include_funnelcake_param:
suffix = 'stub-f%s' % funnelcake_id
suffix = "stub-f%s" % funnelcake_id
else:
suffix = 'stub'
elif channel == 'nightly' and locale != 'en-US':
suffix = "stub"
elif channel == "nightly" and locale != "en-US":
# Nightly uses a different product name for localized builds,
# and is the only one ಠ_ಠ
suffix = 'latest-l10n-ssl'
suffix = "latest-l10n-ssl"
if is_msi:
suffix = 'msi-' + suffix
suffix = "msi-" + suffix
product = '%s-%s' % (prod_name, suffix)
product = "%s-%s" % (prod_name, suffix)
return '?'.join([self.bouncer_url,
urlencode([
('product', product),
('os', platform),
# Order matters, lang must be last for bouncer.
('lang', _locale),
])])
return "?".join(
[
self.bouncer_url,
urlencode(
[
("product", product),
("os", platform),
# Order matters, lang must be last for bouncer.
("lang", _locale),
]
),
]
)
class FirefoxAndroid(_ProductDetails):
# Human-readable architecture names
platform_labels = OrderedDict([
('arm', 'ARM devices\n(Android %s+)'),
('x86', 'Intel devices\n(Android %s+ x86 CPU)'),
])
platform_labels = OrderedDict(
[
("arm", "ARM devices\n(Android %s+)"),
("x86", "Intel devices\n(Android %s+ x86 CPU)"),
]
)
# Recommended/modern vs traditional/legacy platforms
# Unused but required to match FirefoxDesktop
@ -315,57 +329,58 @@ class FirefoxAndroid(_ProductDetails):
# Human-readable channel names
channel_labels = {
'nightly': 'Firefox Nightly',
'beta': 'Firefox Beta',
'release': 'Firefox',
"nightly": "Firefox Nightly",
"beta": "Firefox Beta",
"release": "Firefox",
}
# Version property names in product-details
version_map = {
'nightly': 'nightly_version',
'beta': 'beta_version',
'release': 'version',
"nightly": "nightly_version",
"beta": "beta_version",
"release": "version",
}
# Build property names in product-details
build_map = {
'beta': 'beta_builds',
'release': 'builds',
"beta": "beta_builds",
"release": "builds",
}
# Platform names defined in bouncer
platform_map = OrderedDict([
('arm', 'android'),
('x86', 'android-x86'),
])
platform_map = OrderedDict(
[
("arm", "android"),
("x86", "android-x86"),
]
)
# Product names defined in bouncer
product_map = {
'nightly': 'fennec-nightly-latest',
'beta': 'fennec-beta-latest',
'release': 'fennec-latest',
"nightly": "fennec-nightly-latest",
"beta": "fennec-beta-latest",
"release": "fennec-latest",
}
store_url = settings.GOOGLE_PLAY_FIREFOX_LINK_UTMS
# Product IDs defined on Google Play
# Nightly reuses the Aurora ID to migrate the user base
store_product_ids = {
'nightly': 'org.mozilla.fenix',
'beta': 'org.mozilla.firefox_beta',
'release': 'org.mozilla.firefox',
"nightly": "org.mozilla.fenix",
"beta": "org.mozilla.firefox_beta",
"release": "org.mozilla.firefox",
}
archive_url_base = ('https://archive.mozilla.org/pub/mobile/nightly/'
'latest-mozilla-%s-android')
archive_url_base = "https://archive.mozilla.org/pub/mobile/nightly/latest-mozilla-%s-android"
archive_repo = {
'nightly': 'central',
"nightly": "central",
}
archive_urls = {
'arm': archive_url_base + '-%s/fennec-%s.multi.android-arm.apk',
'x86': archive_url_base + '-%s/fennec-%s.multi.android-i386.apk',
"arm": archive_url_base + "-%s/fennec-%s.multi.android-arm.apk",
"x86": archive_url_base + "-%s/fennec-%s.multi.android-i386.apk",
}
def platforms(self, channel='release', classified=False):
def platforms(self, channel="release", classified=False):
"""
Get the Android platform dictionary containing slugs and corresponding
labels. The classified option is unused but required to match the
@ -375,7 +390,7 @@ class FirefoxAndroid(_ProductDetails):
platforms = OrderedDict()
# Supported Android version has been changed with Firefox 56
min_version = '4.1'
min_version = "4.1"
# key is a bouncer platform name, value is the human-readable label
for arch, platform in self.platform_map.items():
@ -384,7 +399,7 @@ class FirefoxAndroid(_ProductDetails):
return list(platforms.items())
def latest_version(self, channel):
version = self.version_map.get(channel, 'version')
version = self.version_map.get(channel, "version")
return self.mobile_details[version]
def latest_major_version(self, channel):
@ -394,7 +409,7 @@ class FirefoxAndroid(_ProductDetails):
return 0
try:
return int(lv.split('.')[0])
return int(lv.split(".")[0])
except ValueError:
return 0
@ -408,28 +423,28 @@ class FirefoxAndroid(_ProductDetails):
:param query: a string to match against native or english locale name
:return: list
"""
locales = [build['locale']['code'] for build in builds]
locales = [build["locale"]["code"] for build in builds]
f_builds = []
# For now, only list the multi-locale builds because the single-locale
# builds are fragile (Bug 1301650)
locales = ['multi']
locales = ["multi"]
for locale in locales:
if locale == 'multi':
name_en = 'Multi-locale'
name_native = ''
if locale == "multi":
name_en = "Multi-locale"
name_native = ""
elif locale in self.languages:
name_en = self.languages[locale]['English']
name_native = self.languages[locale]['native']
name_en = self.languages[locale]["English"]
name_native = self.languages[locale]["native"]
else:
continue
build_info = {
'locale': locale,
'name_en': name_en,
'name_native': name_native,
'platforms': {},
"locale": locale,
"name_en": name_en,
"name_native": name_native,
"platforms": {},
}
# only include builds that match a search query
@ -438,12 +453,12 @@ class FirefoxAndroid(_ProductDetails):
for arch, platform in self.platform_map.items():
# x86 builds are not localized yet
if arch == 'x86' and locale not in ['multi', 'en-US']:
if arch == "x86" and locale not in ["multi", "en-US"]:
continue
# Use a direct link instead of Google Play for the /all/ page
url = self.get_download_url(channel, arch, locale, True)
build_info['platforms'][platform] = {'download_url': url}
build_info["platforms"][platform] = {"download_url": url}
f_builds.append(build_info)
@ -457,7 +472,7 @@ class FirefoxAndroid(_ProductDetails):
:param query: a string to match against native or english locale name
:return: list
"""
builds = self.mobile_details[self.build_map.get(channel, 'builds')]
builds = self.mobile_details[self.build_map.get(channel, "builds")]
return self._get_filtered_builds(builds, channel, version, query)
@ -465,8 +480,7 @@ class FirefoxAndroid(_ProductDetails):
# We don't have pre-release builds yet
return []
def get_download_url(self, channel='release', arch='arm', locale='multi',
force_direct=False):
def get_download_url(self, channel="release", arch="arm", locale="multi", force_direct=False):
"""
Get direct download url for the product.
:param channel: one of self.version_map.keys() such as nightly, beta.
@ -478,16 +492,23 @@ class FirefoxAndroid(_ProductDetails):
"""
if force_direct:
# Use a bouncer link
return '?'.join([self.bouncer_url, urlencode([
('product', self.product_map.get(channel, 'fennec-latest')),
('os', self.platform_map[arch]),
# Order matters, lang must be last for bouncer.
('lang', locale),
])])
return "?".join(
[
self.bouncer_url,
urlencode(
[
("product", self.product_map.get(channel, "fennec-latest")),
("os", self.platform_map[arch]),
# Order matters, lang must be last for bouncer.
("lang", locale),
]
),
]
)
if channel != 'release':
product_id = self.store_product_ids.get(channel, 'org.mozilla.firefox')
return self.store_url.replace(self.store_product_ids['release'], product_id)
if channel != "release":
product_id = self.store_product_ids.get(channel, "org.mozilla.firefox")
return self.store_url.replace(self.store_product_ids["release"], product_id)
return self.store_url
@ -495,22 +516,22 @@ class FirefoxAndroid(_ProductDetails):
class FirefoxIOS(_ProductDetails):
# Version property names in product-details
version_map = {
'beta': 'ios_beta_version',
'release': 'ios_version',
"beta": "ios_beta_version",
"release": "ios_version",
}
store_url = settings.APPLE_APPSTORE_FIREFOX_LINK
def latest_version(self, channel):
version = self.version_map.get(channel, 'ios_version')
version = self.version_map.get(channel, "ios_version")
return self.mobile_details[version]
def get_download_url(self, channel='release', locale='en-US'):
def get_download_url(self, channel="release", locale="en-US"):
countries = settings.APPLE_APPSTORE_COUNTRY_MAP
if locale in countries:
return self.store_url.format(country=countries[locale])
return self.store_url.replace('/{country}/', '/')
return self.store_url.replace("/{country}/", "/")
firefox_desktop = FirefoxDesktop()

Просмотреть файл

@ -7,8 +7,11 @@ from django import forms
class SendToDeviceWidgetForm(forms.Form):
email = forms.EmailField(max_length=100, required=False)
platform = forms.ChoiceField(choices=(
('ios', 'ios'),
('android', 'android'),
('all', 'all'),
), required=False)
platform = forms.ChoiceField(
choices=(
("ios", "ios"),
("android", "android"),
("all", "all"),
),
required=False,
)

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -9,23 +9,31 @@ from bedrock.base.urlresolvers import reverse
from lib.l10n_utils import get_locale
def desktop_builds(channel, builds=None, locale=None, force_direct=False,
force_full_installer=False, force_funnelcake=False,
funnelcake_id=False, locale_in_transition=False, classified=False):
def desktop_builds(
channel,
builds=None,
locale=None,
force_direct=False,
force_full_installer=False,
force_funnelcake=False,
funnelcake_id=False,
locale_in_transition=False,
classified=False,
):
builds = builds or []
l_version = firefox_desktop.latest_builds(locale, channel)
# Developer Edition is now based on the Beta channel, so the build list
# should be generated from the Beta locales.
if channel == 'alpha':
l_version = firefox_desktop.latest_builds(locale, 'beta')
if channel == "alpha":
l_version = firefox_desktop.latest_builds(locale, "beta")
if l_version:
version, platforms = l_version
else:
locale = 'en-US'
version, platforms = firefox_desktop.latest_builds('en-US', channel)
locale = "en-US"
version, platforms = firefox_desktop.latest_builds("en-US", channel)
for plat_os, plat_os_pretty in firefox_desktop.platforms(channel, classified):
@ -34,16 +42,19 @@ def desktop_builds(channel, builds=None, locale=None, force_direct=False,
# Firefox Nightly: The Windows stub installer is now universal,
# automatically detecting a 32-bit and 64-bit desktop, so the
# win64-specific entry can be skipped.
if channel == 'nightly':
if plat_os == 'win':
if channel == "nightly":
if plat_os == "win":
continue
if plat_os == 'win64':
plat_os = 'win'
os_pretty = 'Windows 32/64-bit'
if plat_os == "win64":
plat_os = "win"
os_pretty = "Windows 32/64-bit"
# And generate all the info
download_link = firefox_desktop.get_download_url(
channel, version, plat_os, locale,
channel,
version,
plat_os,
locale,
force_direct=force_direct,
force_full_installer=force_full_installer,
force_funnelcake=force_funnelcake,
@ -58,7 +69,10 @@ def desktop_builds(channel, builds=None, locale=None, force_direct=False,
download_link_direct = False
else:
download_link_direct = firefox_desktop.get_download_url(
channel, version, plat_os, locale,
channel,
version,
plat_os,
locale,
force_direct=True,
force_full_installer=force_full_installer,
force_funnelcake=force_funnelcake,
@ -67,10 +81,7 @@ def desktop_builds(channel, builds=None, locale=None, force_direct=False,
if download_link_direct == download_link:
download_link_direct = False
builds.append({'os': plat_os,
'os_pretty': os_pretty,
'download_link': download_link,
'download_link_direct': download_link_direct})
builds.append({"os": plat_os, "os_pretty": os_pretty, "download_link": download_link, "download_link_direct": download_link_direct})
return builds
@ -78,9 +89,7 @@ def desktop_builds(channel, builds=None, locale=None, force_direct=False,
def android_builds(channel, builds=None):
builds = builds or []
link = firefox_android.get_download_url(channel.lower())
builds.append({'os': 'android',
'os_pretty': 'Android',
'download_link': link})
builds.append({"os": "android", "os_pretty": "Android", "download_link": link})
return builds
@ -88,21 +97,28 @@ def android_builds(channel, builds=None):
def ios_builds(channel, builds=None):
builds = builds or []
link = firefox_ios.get_download_url(channel)
builds.append({'os': 'ios',
'os_pretty': 'iOS',
'download_link': link})
builds.append({"os": "ios", "os_pretty": "iOS", "download_link": link})
return builds
@library.global_function
@jinja2.contextfunction
def download_firefox(ctx, channel='release', platform='all',
dom_id=None, locale=None, force_direct=False,
force_full_installer=False, force_funnelcake=False,
alt_copy=None, button_class='mzp-t-xl',
locale_in_transition=False, download_location=None):
""" Output a "download firefox" button.
def download_firefox(
ctx,
channel="release",
platform="all",
dom_id=None,
locale=None,
force_direct=False,
force_full_installer=False,
force_funnelcake=False,
alt_copy=None,
button_class="mzp-t-xl",
locale_in_transition=False,
download_location=None,
):
"""Output a "download firefox" button.
:param ctx: context from calling template.
:param channel: name of channel: 'release', 'beta', 'alpha', or 'nightly'.
@ -120,23 +136,20 @@ def download_firefox(ctx, channel='release', platform='all',
:param download_location: Specify the location of download button for
GA reporting: 'primary cta', 'nav', 'sub nav', or 'other'.
"""
show_desktop = platform in ['all', 'desktop']
show_android = platform in ['all', 'android']
show_ios = platform in ['all', 'ios']
alt_channel = '' if channel == 'release' else channel
locale = locale or get_locale(ctx['request'])
funnelcake_id = ctx.get('funnelcake_id', False)
dom_id = dom_id or 'download-button-%s-%s' % (
'desktop' if platform == 'all' else platform, channel)
show_desktop = platform in ["all", "desktop"]
show_android = platform in ["all", "android"]
show_ios = platform in ["all", "ios"]
alt_channel = "" if channel == "release" else channel
locale = locale or get_locale(ctx["request"])
funnelcake_id = ctx.get("funnelcake_id", False)
dom_id = dom_id or "download-button-%s-%s" % ("desktop" if platform == "all" else platform, channel)
# Gather data about the build for each platform
builds = []
if show_desktop:
version = firefox_desktop.latest_version(channel)
builds = desktop_builds(channel, builds, locale, force_direct,
force_full_installer, force_funnelcake,
funnelcake_id, locale_in_transition)
builds = desktop_builds(channel, builds, locale, force_direct, force_full_installer, force_funnelcake, funnelcake_id, locale_in_transition)
if show_android:
version = firefox_android.latest_version(channel)
@ -144,40 +157,36 @@ def download_firefox(ctx, channel='release', platform='all',
if show_ios:
version = firefox_ios.latest_version(channel)
builds.append({'os': 'ios',
'os_pretty': 'iOS',
'download_link': firefox_ios.get_download_url()})
builds.append({"os": "ios", "os_pretty": "iOS", "download_link": firefox_ios.get_download_url()})
# Get the native name for current locale
langs = firefox_desktop.languages
locale_name = langs[locale]['native'] if locale in langs else locale
locale_name = langs[locale]["native"] if locale in langs else locale
data = {
'locale_name': locale_name,
'version': version,
'product': 'firefox-%s' % platform,
'builds': builds,
'id': dom_id,
'channel': alt_channel,
'show_desktop': show_desktop,
'show_android': show_android,
'show_ios': show_ios,
'alt_copy': alt_copy,
'button_class': button_class,
'download_location': download_location,
'fluent_l10n': ctx['fluent_l10n']
"locale_name": locale_name,
"version": version,
"product": "firefox-%s" % platform,
"builds": builds,
"id": dom_id,
"channel": alt_channel,
"show_desktop": show_desktop,
"show_android": show_android,
"show_ios": show_ios,
"alt_copy": alt_copy,
"button_class": button_class,
"download_location": download_location,
"fluent_l10n": ctx["fluent_l10n"],
}
html = render_to_string('firefox/includes/download-button.html', data,
request=ctx['request'])
html = render_to_string("firefox/includes/download-button.html", data, request=ctx["request"])
return jinja2.Markup(html)
@library.global_function
@jinja2.contextfunction
def download_firefox_thanks(ctx, dom_id=None, locale=None, alt_copy=None, button_class=None,
locale_in_transition=False, download_location=None):
""" Output a simple "download firefox" button that only points to /download/thanks/
def download_firefox_thanks(ctx, dom_id=None, locale=None, alt_copy=None, button_class=None, locale_in_transition=False, download_location=None):
"""Output a simple "download firefox" button that only points to /download/thanks/
:param ctx: context from calling template.
:param dom_id: Use this string as the id attr on the element.
@ -189,23 +198,26 @@ def download_firefox_thanks(ctx, dom_id=None, locale=None, alt_copy=None, button
GA reporting: 'primary cta', 'nav', 'sub nav', or 'other'.
"""
channel = 'release'
locale = locale or get_locale(ctx['request'])
funnelcake_id = ctx.get('funnelcake_id', False)
dom_id = dom_id or 'download-button-thanks'
transition_url = '/firefox/download/thanks/'
channel = "release"
locale = locale or get_locale(ctx["request"])
funnelcake_id = ctx.get("funnelcake_id", False)
dom_id = dom_id or "download-button-thanks"
transition_url = "/firefox/download/thanks/"
version = firefox_desktop.latest_version(channel)
# if there's a funnelcake param in the page URL e.g. ?f=123
if funnelcake_id:
# include param in transitional URL e.g. /firefox/download/thanks/?f=123
transition_url += '?f=%s' % funnelcake_id
transition_url += "?f=%s" % funnelcake_id
if locale_in_transition:
transition_url = '/%s%s' % (locale, transition_url)
transition_url = "/%s%s" % (locale, transition_url)
download_link_direct = firefox_desktop.get_download_url(
channel, version, 'win', locale,
channel,
version,
"win",
locale,
force_direct=True,
force_full_installer=False,
force_funnelcake=False,
@ -213,24 +225,22 @@ def download_firefox_thanks(ctx, dom_id=None, locale=None, alt_copy=None, button
)
data = {
'id': dom_id,
'transition_url': transition_url,
'download_link_direct': download_link_direct,
'alt_copy': alt_copy,
'button_class': button_class,
'download_location': download_location,
'fluent_l10n': ctx['fluent_l10n']
"id": dom_id,
"transition_url": transition_url,
"download_link_direct": download_link_direct,
"alt_copy": alt_copy,
"button_class": button_class,
"download_location": download_location,
"fluent_l10n": ctx["fluent_l10n"],
}
html = render_to_string('firefox/includes/download-button-thanks.html', data,
request=ctx['request'])
html = render_to_string("firefox/includes/download-button-thanks.html", data, request=ctx["request"])
return jinja2.Markup(html)
@library.global_function
@jinja2.contextfunction
def download_firefox_desktop_list(ctx, channel='release', dom_id=None, locale=None,
force_full_installer=False):
def download_firefox_desktop_list(ctx, channel="release", dom_id=None, locale=None, force_full_installer=False):
"""
Return a HTML list of platform download links for Firefox desktop
@ -241,41 +251,38 @@ def download_firefox_desktop_list(ctx, channel='release', dom_id=None, locale=No
the stub installer (for aurora).
"""
dom_id = dom_id or 'download-platform-list-%s' % (channel)
locale = locale or get_locale(ctx['request'])
dom_id = dom_id or "download-platform-list-%s" % (channel)
locale = locale or get_locale(ctx["request"])
# Make sure funnelcake_id is not passed as builds are often Windows only.
builds = desktop_builds(channel, None, locale, True, force_full_installer,
False, False, False, True)
builds = desktop_builds(channel, None, locale, True, force_full_installer, False, False, False, True)
recommended_builds = []
traditional_builds = []
for plat in builds:
# Add 32-bit label for Windows and Linux builds.
if channel != 'nightly':
if plat['os'] == 'win':
plat['os_pretty'] = 'Windows 32-bit'
if channel != "nightly":
if plat["os"] == "win":
plat["os_pretty"] = "Windows 32-bit"
if plat['os'] == 'linux':
plat['os_pretty'] = 'Linux 32-bit'
if plat["os"] == "linux":
plat["os_pretty"] = "Linux 32-bit"
if (plat['os'] in firefox_desktop.platform_classification['recommended'] or
channel == 'nightly' and plat['os'] == 'win'):
if plat["os"] in firefox_desktop.platform_classification["recommended"] or channel == "nightly" and plat["os"] == "win":
recommended_builds.append(plat)
else:
traditional_builds.append(plat)
data = {
'id': dom_id,
'builds': {
'recommended': recommended_builds,
'traditional': traditional_builds,
"id": dom_id,
"builds": {
"recommended": recommended_builds,
"traditional": traditional_builds,
},
}
html = render_to_string('firefox/includes/download-list.html', data,
request=ctx['request'])
html = render_to_string("firefox/includes/download-list.html", data, request=ctx["request"])
return jinja2.Markup(html)
@ -299,45 +306,45 @@ def firefox_url(platform, page, channel=None):
anchor = None
# Tweak the channel name for the naming URL pattern in urls.py
if channel == 'release':
if channel == "release":
channel = None
if channel == 'alpha':
if platform == 'desktop':
channel = 'developer'
if platform == 'android':
channel = 'aurora'
if channel == 'esr':
channel = 'organizations'
if channel == "alpha":
if platform == "desktop":
channel = "developer"
if platform == "android":
channel = "aurora"
if channel == "esr":
channel = "organizations"
# There is now only one /all page URL - issue 8096
if page == 'all':
if platform == 'desktop':
if channel == 'beta':
anchor = 'product-desktop-beta'
elif channel == 'developer':
anchor = 'product-desktop-developer'
elif channel == 'nightly':
anchor = 'product-desktop-nightly'
elif channel == 'organizations':
anchor = 'product-desktop-esr'
if page == "all":
if platform == "desktop":
if channel == "beta":
anchor = "product-desktop-beta"
elif channel == "developer":
anchor = "product-desktop-developer"
elif channel == "nightly":
anchor = "product-desktop-nightly"
elif channel == "organizations":
anchor = "product-desktop-esr"
else:
anchor = 'product-desktop-release'
elif platform == 'android':
if channel == 'beta':
anchor = 'product-android-beta'
elif channel == 'nightly':
anchor = 'product-android-nightly'
anchor = "product-desktop-release"
elif platform == "android":
if channel == "beta":
anchor = "product-android-beta"
elif channel == "nightly":
anchor = "product-android-nightly"
else:
anchor = 'product-android-release'
anchor = "product-android-release"
else:
if channel:
kwargs['channel'] = channel
if platform != 'desktop':
kwargs['platform'] = platform
kwargs["channel"] = channel
if platform != "desktop":
kwargs["platform"] = platform
# Firefox for Android and iOS have the system requirements page on SUMO
if platform in ['android', 'ios'] and page == 'sysreq':
if platform in ["android", "ios"] and page == "sysreq":
return settings.FIREFOX_MOBILE_SYSREQ_URL
anchor = '#' + anchor if anchor else ''
return reverse(f'firefox.{page}', kwargs=kwargs) + anchor
anchor = "#" + anchor if anchor else ""
return reverse(f"firefox.{page}", kwargs=kwargs) + anchor

Просмотреть файл

@ -20,20 +20,19 @@ from bedrock.firefox.firefox_details import FirefoxDesktop
from bedrock.mozorg.tests import TestCase
TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), 'test_data')
PROD_DETAILS_DIR = os.path.join(TEST_DATA_DIR, 'product_details_json')
GOOD_PLATS = {'Windows': {}, 'OS X': {}, 'Linux': {}}
TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "test_data")
PROD_DETAILS_DIR = os.path.join(TEST_DATA_DIR, "product_details_json")
GOOD_PLATS = {"Windows": {}, "OS X": {}, "Linux": {}}
jinja_env = Jinja2.get_default().env
class TestInstallerHelp(TestCase):
def setUp(self):
self.button_mock = Mock()
self.patcher = patch.dict(jinja_env.globals,
download_firefox=self.button_mock)
self.patcher = patch.dict(jinja_env.globals, download_firefox=self.button_mock)
self.patcher.start()
self.view_name = 'firefox.installer-help'
with self.activate('en-US'):
self.view_name = "firefox.installer-help"
with self.activate("en-US"):
self.url = reverse(self.view_name)
def tearDown(self):
@ -43,78 +42,158 @@ class TestInstallerHelp(TestCase):
"""
The buttons should use the lang from the query parameter.
"""
self.client.get(self.url, {
'installer_lang': 'fr'
})
self.button_mock.assert_has_calls([
call(alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale='fr'),
call('beta', alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale='fr'),
call('alpha', alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale='fr', platform='desktop'),
call('nightly', alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale='fr', platform='desktop'),
])
self.client.get(self.url, {"installer_lang": "fr"})
self.button_mock.assert_has_calls(
[
call(
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale="fr",
),
call(
"beta",
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale="fr",
),
call(
"alpha",
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale="fr",
platform="desktop",
),
call(
"nightly",
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale="fr",
platform="desktop",
),
]
)
def test_buttons_ignore_non_lang(self):
"""
The buttons should ignore an invalid lang.
"""
self.client.get(self.url, {
'installer_lang': 'not-a-locale'
})
self.button_mock.assert_has_calls([
call(alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale=None),
call('beta', alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale=None),
call('alpha', alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale=None, platform='desktop'),
call('nightly', alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale=None, platform='desktop'),
])
self.client.get(self.url, {"installer_lang": "not-a-locale"})
self.button_mock.assert_has_calls(
[
call(
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale=None,
),
call(
"beta",
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale=None,
),
call(
"alpha",
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale=None,
platform="desktop",
),
call(
"nightly",
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale=None,
platform="desktop",
),
]
)
def test_invalid_channel_specified(self):
"""
All buttons should show when channel is invalid.
"""
self.client.get(self.url, {
'channel': 'dude',
})
self.button_mock.assert_has_calls([
call(alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale=None),
call('beta', alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale=None),
call('alpha', alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale=None, platform='desktop'),
call('nightly', alt_copy=Markup('Download Now'), button_class='mzp-t-secondary mzp-t-md', force_direct=True,
force_full_installer=True, locale=None, platform='desktop'),
])
self.client.get(
self.url,
{
"channel": "dude",
},
)
self.button_mock.assert_has_calls(
[
call(
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale=None,
),
call(
"beta",
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale=None,
),
call(
"alpha",
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale=None,
platform="desktop",
),
call(
"nightly",
alt_copy=Markup("Download Now"),
button_class="mzp-t-secondary mzp-t-md",
force_direct=True,
force_full_installer=True,
locale=None,
platform="desktop",
),
]
)
def test_one_button_when_channel_specified(self):
"""
There should be only one button when the channel is given.
"""
self.client.get(self.url, {
'channel': 'beta',
})
self.button_mock.assert_called_once_with('beta',
alt_copy=Markup('Download Now'), button_class='mzp-t-md',
force_direct=True,
force_full_installer=True,
locale=None)
self.client.get(
self.url,
{
"channel": "beta",
},
)
self.button_mock.assert_called_once_with(
"beta", alt_copy=Markup("Download Now"), button_class="mzp-t-md", force_direct=True, force_full_installer=True, locale=None
)
class TestFirefoxAll(TestCase):
pd_cache = caches['product-details']
pd_cache = caches["product-details"]
def setUp(self):
self.pd_cache.clear()
self.firefox_desktop = FirefoxDesktop(json_dir=PROD_DETAILS_DIR)
self.patcher = patch.object(
fx_views, 'firefox_desktop', self.firefox_desktop)
self.patcher = patch.object(fx_views, "firefox_desktop", self.firefox_desktop)
self.patcher.start()
def tearDown(self):
@ -124,27 +203,27 @@ class TestFirefoxAll(TestCase):
"""
The unified page should display builds for all products
"""
resp = self.client.get(reverse('firefox.all'))
resp = self.client.get(reverse("firefox.all"))
doc = pq(resp.content)
assert len(doc('.c-all-downloads-build')) == 9
assert len(doc(".c-all-downloads-build")) == 9
desktop_release_builds = len(self.firefox_desktop.get_filtered_full_builds('release'))
desktop_release_builds = len(self.firefox_desktop.get_filtered_full_builds("release"))
assert len(doc('.c-locale-list[data-product="desktop_release"] > li')) == desktop_release_builds
assert len(doc('.c-locale-list[data-product="desktop_release"] > li[data-language="en-US"] > ul > li > a')) == 8
desktop_beta_builds = len(self.firefox_desktop.get_filtered_full_builds('beta'))
desktop_beta_builds = len(self.firefox_desktop.get_filtered_full_builds("beta"))
assert len(doc('.c-locale-list[data-product="desktop_beta"] > li')) == desktop_beta_builds
assert len(doc('.c-locale-list[data-product="desktop_beta"] > li[data-language="en-US"] > ul > li > a')) == 8
desktop_developer_builds = len(self.firefox_desktop.get_filtered_full_builds('alpha'))
desktop_developer_builds = len(self.firefox_desktop.get_filtered_full_builds("alpha"))
assert len(doc('.c-locale-list[data-product="desktop_developer"] > li')) == desktop_developer_builds
assert len(doc('.c-locale-list[data-product="desktop_developer"] > li[data-language="en-US"] > ul > li > a')) == 8
desktop_nightly_builds = len(self.firefox_desktop.get_filtered_full_builds('nightly'))
desktop_nightly_builds = len(self.firefox_desktop.get_filtered_full_builds("nightly"))
assert len(doc('.c-locale-list[data-product="desktop_nightly"] > li')) == desktop_nightly_builds
assert len(doc('.c-locale-list[data-product="desktop_nightly"] > li[data-language="en-US"] > ul > li > a')) == 8
desktop_esr_builds = len(self.firefox_desktop.get_filtered_full_builds('esr'))
desktop_esr_builds = len(self.firefox_desktop.get_filtered_full_builds("esr"))
assert len(doc('.c-locale-list[data-product="desktop_esr"] > li')) == desktop_esr_builds
assert len(doc('.c-locale-list[data-product="desktop_esr"] > li[data-language="en-US"] > ul > li > a')) == 8
@ -170,65 +249,69 @@ class TestFirefoxAll(TestCase):
locale details are not updated yet, the filtered build list should not
include the localized build.
"""
builds = self.firefox_desktop.get_filtered_full_builds('release')
assert 'uz' in self.firefox_desktop.firefox_primary_builds
assert 'uz' not in self.firefox_desktop.languages
assert len([build for build in builds if build['locale'] == 'uz']) == 0
builds = self.firefox_desktop.get_filtered_full_builds("release")
assert "uz" in self.firefox_desktop.firefox_primary_builds
assert "uz" not in self.firefox_desktop.languages
assert len([build for build in builds if build["locale"] == "uz"]) == 0
@patch('bedrock.firefox.views.l10n_utils.render', return_value=HttpResponse())
@patch("bedrock.firefox.views.l10n_utils.render", return_value=HttpResponse())
class TestWhatsNew(TestCase):
def setUp(self):
self.view = fx_views.WhatsnewView.as_view()
self.rf = RequestFactory(HTTP_USER_AGENT='Firefox')
self.rf = RequestFactory(HTTP_USER_AGENT="Firefox")
# begin context variable tests
@override_settings(DEV=True)
@patch.object(fx_views, 'ftl_file_is_active', lambda *x: True)
@patch.object(fx_views, "ftl_file_is_active", lambda *x: True)
def test_context_variables_whatsnew(self, render_mock):
"""Should pass the correct context variables"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='70.0')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="70.0")
template = render_mock.call_args[0][1]
ctx = render_mock.call_args[0][2]
assert template == ['firefox/whatsnew/index-account.html']
assert ctx['version'] == '70.0'
assert ctx['analytics_version'] == '70'
assert ctx['entrypoint'] == 'mozilla.org-whatsnew70'
assert ctx['campaign'] == 'whatsnew70'
assert ctx['utm_params'] == ('utm_source=mozilla.org-whatsnew70&utm_medium=referral'
'&utm_campaign=whatsnew70&entrypoint=mozilla.org-whatsnew70')
assert template == ["firefox/whatsnew/index-account.html"]
assert ctx["version"] == "70.0"
assert ctx["analytics_version"] == "70"
assert ctx["entrypoint"] == "mozilla.org-whatsnew70"
assert ctx["campaign"] == "whatsnew70"
assert ctx["utm_params"] == (
"utm_source=mozilla.org-whatsnew70&utm_medium=referral&utm_campaign=whatsnew70&entrypoint=mozilla.org-whatsnew70"
)
@override_settings(DEV=True)
def test_context_variables_whatsnew_developer(self, render_mock):
"""Should pass the correct context variables for developer channel"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='72.0a2')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="72.0a2")
template = render_mock.call_args[0][1]
ctx = render_mock.call_args[0][2]
assert template == ['firefox/developer/whatsnew.html']
assert ctx['version'] == '72.0a2'
assert ctx['analytics_version'] == '72developer'
assert ctx['entrypoint'] == 'mozilla.org-whatsnew72developer'
assert ctx['campaign'] == 'whatsnew72developer'
assert ctx['utm_params'] == ('utm_source=mozilla.org-whatsnew72developer&utm_medium=referral'
'&utm_campaign=whatsnew72developer&entrypoint=mozilla.org-whatsnew72developer')
assert template == ["firefox/developer/whatsnew.html"]
assert ctx["version"] == "72.0a2"
assert ctx["analytics_version"] == "72developer"
assert ctx["entrypoint"] == "mozilla.org-whatsnew72developer"
assert ctx["campaign"] == "whatsnew72developer"
assert ctx["utm_params"] == (
"utm_source=mozilla.org-whatsnew72developer&utm_medium=referral"
"&utm_campaign=whatsnew72developer&entrypoint=mozilla.org-whatsnew72developer"
)
@override_settings(DEV=True)
def test_context_variables_whatsnew_nightly(self, render_mock):
"""Should pass the correct context variables for nightly channel"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='72.0a1')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="72.0a1")
template = render_mock.call_args[0][1]
ctx = render_mock.call_args[0][2]
assert template == ['firefox/nightly/whatsnew.html']
assert ctx['version'] == '72.0a1'
assert ctx['analytics_version'] == '72nightly'
assert ctx['entrypoint'] == 'mozilla.org-whatsnew72nightly'
assert ctx['campaign'] == 'whatsnew72nightly'
assert ctx['utm_params'] == ('utm_source=mozilla.org-whatsnew72nightly&utm_medium=referral'
'&utm_campaign=whatsnew72nightly&entrypoint=mozilla.org-whatsnew72nightly')
assert template == ["firefox/nightly/whatsnew.html"]
assert ctx["version"] == "72.0a1"
assert ctx["analytics_version"] == "72nightly"
assert ctx["entrypoint"] == "mozilla.org-whatsnew72nightly"
assert ctx["campaign"] == "whatsnew72nightly"
assert ctx["utm_params"] == (
"utm_source=mozilla.org-whatsnew72nightly&utm_medium=referral&utm_campaign=whatsnew72nightly&entrypoint=mozilla.org-whatsnew72nightly"
)
# end context variable tests
@ -237,10 +320,10 @@ class TestWhatsNew(TestCase):
@override_settings(DEV=True)
def test_fx_nightly_68_0_a1_whatsnew(self, render_mock):
"""Should show nightly whatsnew template"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='68.0a1')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="68.0a1")
template = render_mock.call_args[0][1]
assert template == ['firefox/nightly/whatsnew.html']
assert template == ["firefox/nightly/whatsnew.html"]
# end nightly whatsnew tests
@ -249,90 +332,90 @@ class TestWhatsNew(TestCase):
@override_settings(DEV=True)
def test_fx_dev_browser_35_0_a2_whatsnew(self, render_mock):
"""Should show default whatsnew template"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='35.0a2')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="35.0a2")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index.html']
assert template == ["firefox/whatsnew/index.html"]
@override_settings(DEV=True)
def test_fx_dev_browser_57_0_a2_whatsnew(self, render_mock):
"""Should show dev browser 57 whatsnew template"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='57.0a2')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="57.0a2")
template = render_mock.call_args[0][1]
assert template == ['firefox/developer/whatsnew.html']
assert template == ["firefox/developer/whatsnew.html"]
@override_settings(DEV=True)
@patch.dict(os.environ, SWITCH_DEV_WHATSNEW_68='False')
@patch.dict(os.environ, SWITCH_DEV_WHATSNEW_68="False")
def test_fx_dev_browser_68_0_a2_whatsnew_off(self, render_mock):
"""Should show regular dev browser whatsnew template"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='68.0a2')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="68.0a2")
template = render_mock.call_args[0][1]
assert template == ['firefox/developer/whatsnew.html']
assert template == ["firefox/developer/whatsnew.html"]
# end dev edition whatsnew tests
@override_settings(DEV=True)
def test_rv_prefix(self, render_mock):
"""Prefixed oldversion shouldn't impact version sniffing."""
req = self.rf.get('/en-US/firefox/whatsnew/?oldversion=rv:10.0')
self.view(req, version='54.0')
req = self.rf.get("/en-US/firefox/whatsnew/?oldversion=rv:10.0")
self.view(req, version="54.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index.html']
assert template == ["firefox/whatsnew/index.html"]
@override_settings(DEV=True)
@patch.object(fx_views, 'ftl_file_is_active', lambda *x: True)
@patch.object(fx_views, "ftl_file_is_active", lambda *x: True)
def test_fx_default_whatsnew_sync(self, render_mock):
"""Should use sync template for 60.0"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='60.0')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="60.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index-account.html']
assert template == ["firefox/whatsnew/index-account.html"]
@override_settings(DEV=True)
@patch.object(fx_views, 'ftl_file_is_active', lambda *x: False)
@patch.object(fx_views, "ftl_file_is_active", lambda *x: False)
def test_fx_default_whatsnew_fallback(self, render_mock):
"""Should use standard template for 60.0 as fallback"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='60.0')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="60.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index.html']
assert template == ["firefox/whatsnew/index.html"]
@override_settings(DEV=True)
@patch.object(fx_views, 'ftl_file_is_active', lambda *x: True)
@patch.object(fx_views, "ftl_file_is_active", lambda *x: True)
def test_fx_default_whatsnew(self, render_mock):
"""Should use standard template for 59.0"""
req = self.rf.get('/en-US/firefox/whatsnew/')
self.view(req, version='59.0')
req = self.rf.get("/en-US/firefox/whatsnew/")
self.view(req, version="59.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index.html']
assert template == ["firefox/whatsnew/index.html"]
# begin 86.0 whatsnew tests
def test_fx_86_0_0_en(self, render_mock):
"""Should use whatsnew-fx86 template for 86.0 in English"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'en-US'
self.view(req, version='86.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "en-US"
self.view(req, version="86.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx86-en.html']
assert template == ["firefox/whatsnew/whatsnew-fx86-en.html"]
def test_fx_86_0_0_de(self, render_mock):
"""Should use whatsnew-mobile-qrcode-de template for 86.0 in German"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'de'
self.view(req, version='86.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "de"
self.view(req, version="86.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-mobile-qrcode-de.html']
assert template == ["firefox/whatsnew/whatsnew-mobile-qrcode-de.html"]
def test_fx_86_0_0_locale(self, render_mock):
"""Should use standard whatsnew template for 86.0 in other locales"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'es-ES'
self.view(req, version='86.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "es-ES"
self.view(req, version="86.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index-account.html']
assert template == ["firefox/whatsnew/index-account.html"]
# end 86.0 whatsnew tests
@ -340,19 +423,19 @@ class TestWhatsNew(TestCase):
def test_fx_87_0_0_en(self, render_mock):
"""Should use PiP template for 87.0 in US English"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'en-US'
self.view(req, version='87.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "en-US"
self.view(req, version="87.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx87-en.html']
assert template == ["firefox/whatsnew/whatsnew-fx87-en.html"]
def test_fx_87_0_0_locale(self, render_mock):
"""Should use standard whatsnew template for 87.0 in other locales"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'es-ES'
self.view(req, version='87.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "es-ES"
self.view(req, version="87.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index-account.html']
assert template == ["firefox/whatsnew/index-account.html"]
# end 87.0 whatsnew tests
@ -360,19 +443,19 @@ class TestWhatsNew(TestCase):
def test_fx_88_0_0_en(self, render_mock):
"""Should use whatsnew-fx88-en template for 88.0 in English"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'en-US'
self.view(req, version='88.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "en-US"
self.view(req, version="88.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx88-en.html']
assert template == ["firefox/whatsnew/whatsnew-fx88-en.html"]
def test_fx_88_0_0_locale(self, render_mock):
"""Should use standard whatsnew template for 88.0 in other locales"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'es-ES'
self.view(req, version='88.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "es-ES"
self.view(req, version="88.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index-account.html']
assert template == ["firefox/whatsnew/index-account.html"]
# end 88.0 whatsnew tests
@ -380,51 +463,51 @@ class TestWhatsNew(TestCase):
def test_fx_90_0_0_en(self, render_mock):
"""Should use whatsnew-fx90-en template for 90.0 in English"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'en-US'
self.view(req, version='90.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "en-US"
self.view(req, version="90.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx90-en.html']
assert template == ["firefox/whatsnew/whatsnew-fx90-en.html"]
def test_fx_90_0_0_de(self, render_mock):
"""Should use VPN template for 90.0 in German"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'de'
self.view(req, version='90.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "de"
self.view(req, version="90.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx90-eu.html']
assert template == ["firefox/whatsnew/whatsnew-fx90-eu.html"]
def test_fx_90_0_0_fr(self, render_mock):
"""Should use VPN template for 90.0 in French"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'fr'
self.view(req, version='90.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "fr"
self.view(req, version="90.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx90-eu.html']
assert template == ["firefox/whatsnew/whatsnew-fx90-eu.html"]
def test_fx_90_0_0_it(self, render_mock):
"""Should use VPN template for 90.0 in Italian"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'it'
self.view(req, version='90.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "it"
self.view(req, version="90.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx90-eu.html']
assert template == ["firefox/whatsnew/whatsnew-fx90-eu.html"]
def test_fx_90_0_0_nl(self, render_mock):
"""Should use VPN template for 90.0 in Dutch"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'nl'
self.view(req, version='90.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "nl"
self.view(req, version="90.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx90-eu.html']
assert template == ["firefox/whatsnew/whatsnew-fx90-eu.html"]
def test_fx_90_0_0_es(self, render_mock):
"""Should use VPN template for 90.0 in Spanish"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'es-ES'
self.view(req, version='90.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "es-ES"
self.view(req, version="90.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx90-eu.html']
assert template == ["firefox/whatsnew/whatsnew-fx90-eu.html"]
# end 90.0 whatsnew tests
@ -432,27 +515,27 @@ class TestWhatsNew(TestCase):
def test_fx_91_0_0_en(self, render_mock):
"""Should use whatsnew-fx91-en template for 91.0 in English"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'en-US'
self.view(req, version='91.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "en-US"
self.view(req, version="91.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx91-en.html']
assert template == ["firefox/whatsnew/whatsnew-fx91-en.html"]
def test_fx_91_0_0_de(self, render_mock):
"""Should use whatsnew-fx91-de template for 91.0 in German"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'de'
self.view(req, version='91.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "de"
self.view(req, version="91.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx91-de.html']
assert template == ["firefox/whatsnew/whatsnew-fx91-de.html"]
def test_fx_91_0_0_locale(self, render_mock):
"""Should use standard whatsnew template for 91.0 in other locales"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'pl'
self.view(req, version='91.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "pl"
self.view(req, version="91.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index-account.html']
assert template == ["firefox/whatsnew/index-account.html"]
# end 91.0 whatsnew tests
@ -460,92 +543,92 @@ class TestWhatsNew(TestCase):
def test_fx_92_0_0_de(self, render_mock):
"""Should use whatsnew-fx92-de template for 92.0 in German"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'de'
self.view(req, version='92.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "de"
self.view(req, version="92.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx92-de.html']
assert template == ["firefox/whatsnew/whatsnew-fx92-de.html"]
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING='False')
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING="False")
def test_fx_92_0_0_en(self, render_mock):
"""Should use whatsnew-fx92-en template for 92.0 in English when VPN switch is OFF"""
req = self.rf.get('/firefox/whatsnew/en/')
req.locale = 'en-US'
self.view(req, version='92.0')
req = self.rf.get("/firefox/whatsnew/en/")
req.locale = "en-US"
self.view(req, version="92.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx92-en.html']
assert template == ["firefox/whatsnew/whatsnew-fx92-en.html"]
def test_fx_92_0_0_locale(self, render_mock):
"""Should use standard whatsnew template for 92.0 in other locales"""
req = self.rf.get('/firefox/whatsnew/')
req.locale = 'pl'
self.view(req, version='92.0')
req = self.rf.get("/firefox/whatsnew/")
req.locale = "pl"
self.view(req, version="92.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/index-account.html']
assert template == ["firefox/whatsnew/index-account.html"]
# end 92.0 whatsnew tests
@patch('bedrock.firefox.views.l10n_utils.render', return_value=HttpResponse())
@patch("bedrock.firefox.views.l10n_utils.render", return_value=HttpResponse())
class TestWhatsNew92English(TestCase):
def setUp(self):
self.view = fx_views.WhatsNewEnglishView.as_view()
self.rf = RequestFactory(HTTP_USER_AGENT='Firefox')
self.rf = RequestFactory(HTTP_USER_AGENT="Firefox")
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING='False')
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING="False")
def test_fx_92_0_0_en(self, render_mock):
"""Should use whatsnew-fx92-en template for 92.0 in English when VPN switch is OFF"""
req = self.rf.get('/firefox/whatsnew/en/')
req.locale = 'en-US'
self.view(req, version='92.0')
req = self.rf.get("/firefox/whatsnew/en/")
req.locale = "en-US"
self.view(req, version="92.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx92-en.html']
assert template == ["firefox/whatsnew/whatsnew-fx92-en.html"]
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING='True')
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING="True")
def test_fx_92_0_0_vpn_en(self, render_mock):
"""Should use whatsnew-fx92-vpn-en template for 92.0 in English when VPN switch is ON"""
req = self.rf.get('/firefox/whatsnew/en/')
req.locale = 'en-US'
self.view(req, version='92.0')
req = self.rf.get("/firefox/whatsnew/en/")
req.locale = "en-US"
self.view(req, version="92.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx92-vpn-en.html']
assert template == ["firefox/whatsnew/whatsnew-fx92-vpn-en.html"]
@patch('bedrock.firefox.views.l10n_utils.render', return_value=HttpResponse())
@patch("bedrock.firefox.views.l10n_utils.render", return_value=HttpResponse())
class TestWhatsNew92France(TestCase):
def setUp(self):
self.view = fx_views.WhatsNewFranceView.as_view()
self.rf = RequestFactory(HTTP_USER_AGENT='Firefox')
self.rf = RequestFactory(HTTP_USER_AGENT="Firefox")
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING_FR='False')
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING_FR="False")
def test_fx_92_0_0_fr(self, render_mock):
"""Should use whatsnew-fx92-fr template for 92.0 in French when VPN switch is OFF"""
req = self.rf.get('/firefox/whatsnew/france/')
req.locale = 'fr'
self.view(req, version='92.0')
req = self.rf.get("/firefox/whatsnew/france/")
req.locale = "fr"
self.view(req, version="92.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx92-fr.html']
assert template == ["firefox/whatsnew/whatsnew-fx92-fr.html"]
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING_FR='True')
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING_FR="True")
def test_fx_92_0_0_vpn_fr(self, render_mock):
"""Should use whatsnew-fx92-vpn-fr template for 92.0 in French when VPN switch is ON"""
req = self.rf.get('/firefox/whatsnew/france/')
req.locale = 'fr'
self.view(req, version='92.0')
req = self.rf.get("/firefox/whatsnew/france/")
req.locale = "fr"
self.view(req, version="92.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/whatsnew/whatsnew-fx92-vpn-fr.html']
assert template == ["firefox/whatsnew/whatsnew-fx92-vpn-fr.html"]
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING='True')
@patch.dict(os.environ, SWITCH_FIREFOX_WHATSNEW_92_VPN_PRICING="True")
def test_fx_94_0_a1_fr(self, render_mock):
"""Should use Nightly /whatsnew template for 94.0.a1"""
req = self.rf.get('/firefox/whatsnew/france/')
req.locale = 'fr'
self.view(req, version='94.0a1')
req = self.rf.get("/firefox/whatsnew/france/")
req.locale = "fr"
self.view(req, version="94.0a1")
template = render_mock.call_args[0][1]
assert template == ['firefox/nightly/whatsnew.html']
assert template == ["firefox/nightly/whatsnew.html"]
@patch('bedrock.firefox.views.l10n_utils.render', return_value=HttpResponse())
@patch("bedrock.firefox.views.l10n_utils.render", return_value=HttpResponse())
class TestFirstRun(TestCase):
def setUp(self):
self.view = fx_views.FirstrunView.as_view()
@ -554,48 +637,48 @@ class TestFirstRun(TestCase):
@override_settings(DEV=True)
def test_fx_firstrun_40_0(self, render_mock):
"""Should use default firstrun template"""
req = self.rf.get('/en-US/firefox/firstrun/')
self.view(req, version='40.0')
req = self.rf.get("/en-US/firefox/firstrun/")
self.view(req, version="40.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/firstrun/firstrun.html']
assert template == ["firefox/firstrun/firstrun.html"]
@override_settings(DEV=True)
def test_fx_firstrun_56_0(self, render_mock):
"""Should use the default firstrun template"""
req = self.rf.get('/en-US/firefox/firstrun/')
self.view(req, version='56.0a2')
req = self.rf.get("/en-US/firefox/firstrun/")
self.view(req, version="56.0a2")
template = render_mock.call_args[0][1]
assert template == ['firefox/firstrun/firstrun.html']
assert template == ["firefox/firstrun/firstrun.html"]
@override_settings(DEV=True)
def test_fxdev_firstrun_57_0(self, render_mock):
"""Should use 57 quantum dev edition firstrun template"""
req = self.rf.get('/en-US/firefox/firstrun/')
self.view(req, version='57.0a2')
req = self.rf.get("/en-US/firefox/firstrun/")
self.view(req, version="57.0a2")
template = render_mock.call_args[0][1]
assert template == ['firefox/developer/firstrun.html']
assert template == ["firefox/developer/firstrun.html"]
@override_settings(DEV=True)
def test_fx_firstrun_57_0(self, render_mock):
"""Should use 57 quantum firstrun template"""
req = self.rf.get('/en-US/firefox/firstrun/')
self.view(req, version='57.0')
req = self.rf.get("/en-US/firefox/firstrun/")
self.view(req, version="57.0")
template = render_mock.call_args[0][1]
assert template == ['firefox/firstrun/firstrun.html']
assert template == ["firefox/firstrun/firstrun.html"]
# test redirect to /firefox/new/ for legacy /firstrun URLs - Bug 1343823
@override_settings(DEV=True)
def test_fx_firstrun_legacy_redirect(self, render_mock):
req = self.rf.get('/firefox/firstrun/')
req.locale = 'en-US'
resp = self.view(req, version='39.0')
req = self.rf.get("/firefox/firstrun/")
req.locale = "en-US"
resp = self.view(req, version="39.0")
assert resp.status_code == 301
assert resp['location'].endswith('/firefox/new/')
assert resp["location"].endswith("/firefox/new/")
def test_fx_firstrun_dev_edition_legacy_redirect(self, render_mock):
req = self.rf.get('/firefox/firstrun/')
req.locale = 'en-US'
resp = self.view(req, version='39.0a2')
req = self.rf.get("/firefox/firstrun/")
req.locale = "en-US"
resp = self.view(req, version="39.0a2")
assert resp.status_code == 301
assert resp['location'].endswith('/firefox/new/')
assert resp["location"].endswith("/firefox/new/")

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -18,42 +18,39 @@ def render(s, context=None):
class TestDownloadButtons(TestCase):
def latest_version(self):
from product_details import product_details
return product_details.firefox_versions['LATEST_FIREFOX_VERSION']
return product_details.firefox_versions["LATEST_FIREFOX_VERSION"]
def get_l10n(self, locale):
return fluent_l10n([locale, 'en'], settings.FLUENT_DEFAULT_FILES)
return fluent_l10n([locale, "en"], settings.FLUENT_DEFAULT_FILES)
def check_desktop_links(self, links):
"""Desktop links should have the correct firefox version"""
# valid product strings
keys = [
'firefox-%s' % self.latest_version(),
'firefox-stub',
'firefox-latest-ssl',
'firefox-beta-stub',
'firefox-beta-latest-ssl',
"firefox-%s" % self.latest_version(),
"firefox-stub",
"firefox-latest-ssl",
"firefox-beta-stub",
"firefox-beta-latest-ssl",
]
for link in links:
url = pq(link).attr('href')
url = pq(link).attr("href")
assert any(key in url for key in keys)
def check_dumb_button(self, doc):
# Make sure 5 links are present
links = doc('li a')
links = doc("li a")
assert links.length == 5
self.check_desktop_links(links[:4])
# Check that the rest of the links are Android and iOS
assert pq(links[4]).attr('href') == settings.GOOGLE_PLAY_FIREFOX_LINK_UTMS
assert (
pq(links[5]).attr('href') ==
settings.APPLE_APPSTORE_FIREFOX_LINK.replace('/{country}/', '/')
)
assert pq(links[4]).attr("href") == settings.GOOGLE_PLAY_FIREFOX_LINK_UTMS
assert pq(links[5]).attr("href") == settings.APPLE_APPSTORE_FIREFOX_LINK.replace("/{country}/", "/")
def test_button_force_direct(self):
"""
@ -61,85 +58,87 @@ class TestDownloadButtons(TestCase):
directly to https://download.mozilla.org.
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'fr'
doc = pq(render("{{ download_firefox(force_direct=true) }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "fr"
doc = pq(render("{{ download_firefox(force_direct=true) }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)}))
# Check that the first 5 links are direct.
links = doc('.download-list a')
links = doc(".download-list a")
for link in links[:5]:
link = pq(link)
href = link.attr('href')
assert href.startswith('https://download.mozilla.org')
self.assertListEqual(parse_qs(urlparse(href).query)['lang'], ['fr'])
href = link.attr("href")
assert href.startswith("https://download.mozilla.org")
self.assertListEqual(parse_qs(urlparse(href).query)["lang"], ["fr"])
# direct links should not have the data attr.
assert link.attr('data-direct-link') is None
assert link.attr("data-direct-link") is None
def test_button_locale_in_transition(self):
"""
If the locale_in_transition parameter is True, the link to scene2 should include the locale
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'fr'
doc = pq(render("{{ download_firefox(locale_in_transition=true) }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "fr"
doc = pq(
render("{{ download_firefox(locale_in_transition=true) }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)})
)
links = doc('.download-list a')
links = doc(".download-list a")
for link in links[1:5]:
link = pq(link)
href = link.attr('href')
assert href == '/fr/firefox/download/thanks/'
href = link.attr("href")
assert href == "/fr/firefox/download/thanks/"
doc = pq(render("{{ download_firefox(locale_in_transition=false) }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
doc = pq(
render("{{ download_firefox(locale_in_transition=false) }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)})
)
links = doc('.download-list a')
links = doc(".download-list a")
for link in links[1:5]:
link = pq(link)
href = link.attr('href')
assert href == '/firefox/download/thanks/'
href = link.attr("href")
assert href == "/firefox/download/thanks/"
def test_download_location_attribute(self):
"""
If the download_location parameter is set, it should be included as a data attribute.
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'fr'
doc = pq(render("{{ download_firefox(download_location='primary cta') }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "fr"
doc = pq(
render(
"{{ download_firefox(download_location='primary cta') }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)}
)
)
links = doc('.download-list a')
links = doc(".download-list a")
for link in links:
link = pq(link)
assert link.attr('data-download-location') == 'primary cta'
assert link.attr("data-download-location") == "primary cta"
doc = pq(render("{{ download_firefox() }}", {'request': get_request,
'fluent_l10n': self.get_l10n(get_request.locale)}))
doc = pq(render("{{ download_firefox() }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)}))
links = doc('.download-list a')
links = doc(".download-list a")
for link in links[1:5]:
link = pq(link)
assert link.attr('data-download-location') is None
assert link.attr("data-download-location") is None
def test_download_nosnippet_attribute(self):
"""
Unsupported messaging should be well formed <div>'s with data-nosnippet attribute (issue #8739).
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'fr'
doc = pq(render("{{ download_firefox() }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "fr"
doc = pq(render("{{ download_firefox() }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)}))
unrecognised_message = doc('.unrecognized-download').empty().outerHtml()
unrecognised_message = doc(".unrecognized-download").empty().outerHtml()
assert unrecognised_message == '<div class="unrecognized-download" data-nosnippet="true"></div>'
def test_button_has_data_attr_if_not_direct(self):
@ -148,21 +147,18 @@ class TestDownloadButtons(TestCase):
`data-direct-link` attribute that contains the direct url.
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'fr'
doc = pq(render("{{ download_firefox() }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "fr"
doc = pq(render("{{ download_firefox() }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)}))
# The first 8 links should be for desktop.
links = doc('.download-list a')
links = doc(".download-list a")
for link in links[:8]:
assert (
pq(link).attr('data-direct-link')
.startswith('https://download.mozilla.org'))
assert pq(link).attr("data-direct-link").startswith("https://download.mozilla.org")
# The ninth link is mobile and should not have the attr
assert pq(links[8]).attr('data-direct-link') is None
assert pq(links[8]).attr("data-direct-link") is None
def test_nightly_desktop(self):
"""
@ -170,20 +166,23 @@ class TestDownloadButtons(TestCase):
instead of the Windows 64-bit build
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'fr'
doc = pq(render("{{ download_firefox('nightly', platform='desktop') }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "fr"
doc = pq(
render(
"{{ download_firefox('nightly', platform='desktop') }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)}
)
)
list = doc('.download-list li')
list = doc(".download-list li")
assert list.length == 7
assert pq(list[0]).attr('class') == 'os_win'
assert pq(list[1]).attr('class') == 'os_win64-msi'
assert pq(list[2]).attr('class') == 'os_win64-aarch64'
assert pq(list[3]).attr('class') == 'os_win-msi'
assert pq(list[4]).attr('class') == 'os_osx'
assert pq(list[5]).attr('class') == 'os_linux64'
assert pq(list[6]).attr('class') == 'os_linux'
assert pq(list[0]).attr("class") == "os_win"
assert pq(list[1]).attr("class") == "os_win64-msi"
assert pq(list[2]).attr("class") == "os_win64-aarch64"
assert pq(list[3]).attr("class") == "os_win-msi"
assert pq(list[4]).attr("class") == "os_osx"
assert pq(list[5]).attr("class") == "os_linux64"
assert pq(list[6]).attr("class") == "os_linux"
# stub disabled for now for non-en-US locales
# bug 1339870
# assert 'stub' in pq(pq(list[1]).find('a')[0]).attr('href')
@ -191,198 +190,197 @@ class TestDownloadButtons(TestCase):
def test_aurora_desktop(self):
"""The Aurora channel should have Windows 64 build"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'fr'
doc = pq(render("{{ download_firefox('alpha', platform='desktop') }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "fr"
doc = pq(
render("{{ download_firefox('alpha', platform='desktop') }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)})
)
list = doc('.download-list li')
list = doc(".download-list li")
assert list.length == 8
assert pq(list[0]).attr('class') == 'os_win64'
assert pq(list[1]).attr('class') == 'os_win64-msi'
assert pq(list[2]).attr('class') == 'os_win64-aarch64'
assert pq(list[3]).attr('class') == 'os_win'
assert pq(list[4]).attr('class') == 'os_win-msi'
assert pq(list[5]).attr('class') == 'os_osx'
assert pq(list[6]).attr('class') == 'os_linux64'
assert pq(list[7]).attr('class') == 'os_linux'
assert pq(list[0]).attr("class") == "os_win64"
assert pq(list[1]).attr("class") == "os_win64-msi"
assert pq(list[2]).attr("class") == "os_win64-aarch64"
assert pq(list[3]).attr("class") == "os_win"
assert pq(list[4]).attr("class") == "os_win-msi"
assert pq(list[5]).attr("class") == "os_osx"
assert pq(list[6]).attr("class") == "os_linux64"
assert pq(list[7]).attr("class") == "os_linux"
def test_beta_desktop(self):
"""The Beta channel should not have Windows 64 build yet"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'fr'
doc = pq(render("{{ download_firefox('beta', platform='desktop') }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "fr"
doc = pq(
render("{{ download_firefox('beta', platform='desktop') }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)})
)
list = doc('.download-list li')
list = doc(".download-list li")
assert list.length == 8
assert pq(list[0]).attr('class') == 'os_win64'
assert pq(list[1]).attr('class') == 'os_win64-msi'
assert pq(list[2]).attr('class') == 'os_win64-aarch64'
assert pq(list[3]).attr('class') == 'os_win'
assert pq(list[4]).attr('class') == 'os_win-msi'
assert pq(list[5]).attr('class') == 'os_osx'
assert pq(list[6]).attr('class') == 'os_linux64'
assert pq(list[7]).attr('class') == 'os_linux'
assert pq(list[0]).attr("class") == "os_win64"
assert pq(list[1]).attr("class") == "os_win64-msi"
assert pq(list[2]).attr("class") == "os_win64-aarch64"
assert pq(list[3]).attr("class") == "os_win"
assert pq(list[4]).attr("class") == "os_win-msi"
assert pq(list[5]).attr("class") == "os_osx"
assert pq(list[6]).attr("class") == "os_linux64"
assert pq(list[7]).attr("class") == "os_linux"
def test_firefox_desktop(self):
"""The Release channel should not have Windows 64 build yet"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'fr'
doc = pq(render("{{ download_firefox(platform='desktop') }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "fr"
doc = pq(render("{{ download_firefox(platform='desktop') }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)}))
list = doc('.download-list li')
list = doc(".download-list li")
assert list.length == 8
assert pq(list[0]).attr('class') == 'os_win64'
assert pq(list[1]).attr('class') == 'os_win64-msi'
assert pq(list[2]).attr('class') == 'os_win64-aarch64'
assert pq(list[3]).attr('class') == 'os_win'
assert pq(list[4]).attr('class') == 'os_win-msi'
assert pq(list[5]).attr('class') == 'os_osx'
assert pq(list[6]).attr('class') == 'os_linux64'
assert pq(list[7]).attr('class') == 'os_linux'
assert pq(list[0]).attr("class") == "os_win64"
assert pq(list[1]).attr("class") == "os_win64-msi"
assert pq(list[2]).attr("class") == "os_win64-aarch64"
assert pq(list[3]).attr("class") == "os_win"
assert pq(list[4]).attr("class") == "os_win-msi"
assert pq(list[5]).attr("class") == "os_osx"
assert pq(list[6]).attr("class") == "os_linux64"
assert pq(list[7]).attr("class") == "os_linux"
def test_latest_nightly_android(self):
"""The download button should have a Google Play link"""
rf = RequestFactory()
get_request = rf.get('/fake')
doc = pq(render("{{ download_firefox('nightly', platform='android') }}",
{'request': get_request, 'fluent_l10n': self.get_l10n('fr')}))
get_request = rf.get("/fake")
doc = pq(render("{{ download_firefox('nightly', platform='android') }}", {"request": get_request, "fluent_l10n": self.get_l10n("fr")}))
list = doc('.download-list li')
list = doc(".download-list li")
assert list.length == 1
assert pq(list[0]).attr('class') == 'os_android'
assert pq(list[0]).attr("class") == "os_android"
links = doc('.download-list li a')
links = doc(".download-list li a")
assert links.length == 1
assert pq(links[0]).attr('href').startswith('https://play.google.com')
assert pq(links[0]).attr("href").startswith("https://play.google.com")
list = doc('.download-other .arch')
list = doc(".download-other .arch")
assert list.length == 0
def test_beta_mobile(self):
"""The download button should have a Google Play link"""
rf = RequestFactory()
get_request = rf.get('/fake')
doc = pq(render("{{ download_firefox('beta', platform='android') }}",
{'request': get_request, 'fluent_l10n': self.get_l10n('fr')}))
get_request = rf.get("/fake")
doc = pq(render("{{ download_firefox('beta', platform='android') }}", {"request": get_request, "fluent_l10n": self.get_l10n("fr")}))
list = doc('.download-list li')
list = doc(".download-list li")
assert list.length == 1
assert pq(list[0]).attr('class') == 'os_android'
assert pq(list[0]).attr("class") == "os_android"
links = doc('.download-list li a')
links = doc(".download-list li a")
assert links.length == 1
assert pq(links[0]).attr('href').startswith('https://play.google.com')
assert pq(links[0]).attr("href").startswith("https://play.google.com")
list = doc('.download-other .arch')
list = doc(".download-other .arch")
assert list.length == 0
def test_firefox_mobile(self):
"""The download button should have a Google Play link"""
rf = RequestFactory()
get_request = rf.get('/fake')
doc = pq(render("{{ download_firefox(platform='android') }}",
{'request': get_request, 'fluent_l10n': self.get_l10n('fr')}))
get_request = rf.get("/fake")
doc = pq(render("{{ download_firefox(platform='android') }}", {"request": get_request, "fluent_l10n": self.get_l10n("fr")}))
list = doc('.download-list li')
list = doc(".download-list li")
assert list.length == 1
assert pq(list[0]).attr('class') == 'os_android'
assert pq(list[0]).attr("class") == "os_android"
links = doc('.download-list li a')
links = doc(".download-list li a")
assert links.length == 1
assert pq(links[0]).attr('href').startswith('https://play.google.com')
assert pq(links[0]).attr("href").startswith("https://play.google.com")
list = doc('.download-other .arch')
list = doc(".download-other .arch")
assert list.length == 0
def test_ios(self):
rf = RequestFactory()
get_request = rf.get('/fake')
doc = pq(render("{{ download_firefox(platform='ios') }}",
{'request': get_request, 'fluent_l10n': self.get_l10n('fr')}))
get_request = rf.get("/fake")
doc = pq(render("{{ download_firefox(platform='ios') }}", {"request": get_request, "fluent_l10n": self.get_l10n("fr")}))
list = doc('.download-list li')
list = doc(".download-list li")
assert list.length == 1
assert pq(list[0]).attr('class') == 'os_ios'
assert pq(list[0]).attr("class") == "os_ios"
class TestDownloadThanksButton(TestCase):
def get_l10n(self, locale):
return fluent_l10n([locale, 'en'], settings.FLUENT_DEFAULT_FILES)
return fluent_l10n([locale, "en"], settings.FLUENT_DEFAULT_FILES)
def test_download_firefox_thanks_button(self):
"""
Download link should point to /firefox/download/thanks/
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'en-US'
doc = pq(render("{{ download_firefox_thanks() }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "en-US"
doc = pq(render("{{ download_firefox_thanks() }}", {"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)}))
button = doc('.c-button-download-thanks')
links = doc('.c-button-download-thanks > .download-link')
button = doc(".c-button-download-thanks")
links = doc(".c-button-download-thanks > .download-link")
assert links.length == 1
link = pq(links)
href = link.attr('href')
href = link.attr("href")
assert href == '/firefox/download/thanks/'
assert button.attr('id') == 'download-button-thanks'
assert link.attr('data-link-type') == 'download'
assert href == "/firefox/download/thanks/"
assert button.attr("id") == "download-button-thanks"
assert link.attr("data-link-type") == "download"
# Direct attribute for legacy IE browsers should always be win 32bit
assert link.attr('data-direct-link') == 'https://download.mozilla.org/?product=firefox-stub&os=win&lang=en-US'
assert link.attr("data-direct-link") == "https://download.mozilla.org/?product=firefox-stub&os=win&lang=en-US"
def test_download_firefox_thanks_attributes(self):
"""
Download link should support custom attributes
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'en-US'
doc = pq(render("{{ download_firefox_thanks(dom_id='test-download', button_class='test-css-class', "
"download_location='primary cta', locale_in_transition=True) }}",
{'request': get_request, 'fluent_l10n': self.get_l10n(get_request.locale)}))
get_request = rf.get("/fake")
get_request.locale = "en-US"
doc = pq(
render(
"{{ download_firefox_thanks(dom_id='test-download', button_class='test-css-class', "
"download_location='primary cta', locale_in_transition=True) }}",
{"request": get_request, "fluent_l10n": self.get_l10n(get_request.locale)},
)
)
button = doc('.c-button-download-thanks')
links = doc('.c-button-download-thanks > .download-link')
button = doc(".c-button-download-thanks")
links = doc(".c-button-download-thanks > .download-link")
assert links.length == 1
link = pq(links)
href = link.attr('href')
href = link.attr("href")
assert href == '/en-US/firefox/download/thanks/'
assert button.attr('id') == 'test-download'
assert link.attr('data-download-location') == 'primary cta'
assert 'test-css-class' in link.attr('class')
assert href == "/en-US/firefox/download/thanks/"
assert button.attr("id") == "test-download"
assert link.attr("data-download-location") == "primary cta"
assert "test-css-class" in link.attr("class")
class TestDownloadList(TestCase):
def latest_version(self):
from product_details import product_details
return product_details.firefox_versions['LATEST_FIREFOX_VERSION']
return product_details.firefox_versions["LATEST_FIREFOX_VERSION"]
def check_desktop_links(self, links):
"""Desktop links should have the correct firefox version"""
# valid product strings
keys = [
'firefox-%s' % self.latest_version(),
'firefox-stub',
'firefox-latest-ssl',
'firefox-msi-latest-ssl',
'firefox-beta-stub',
'firefox-beta-latest-ssl',
"firefox-%s" % self.latest_version(),
"firefox-stub",
"firefox-latest-ssl",
"firefox-msi-latest-ssl",
"firefox-beta-stub",
"firefox-beta-latest-ssl",
]
for link in links:
url = pq(link).attr('href')
url = pq(link).attr("href")
assert any(key in url for key in keys)
def test_firefox_desktop_list_release(self):
@ -390,153 +388,138 @@ class TestDownloadList(TestCase):
All release download links must be directly to https://download.mozilla.org.
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'en-US'
doc = pq(render("{{ download_firefox_desktop_list() }}",
{'request': get_request}))
get_request = rf.get("/fake")
get_request.locale = "en-US"
doc = pq(render("{{ download_firefox_desktop_list() }}", {"request": get_request}))
# Check that links classes are ordered as expected.
list = doc('.download-platform-list li')
list = doc(".download-platform-list li")
assert list.length == 8
assert pq(list[0]).attr('class') == 'os_win64'
assert pq(list[1]).attr('class') == 'os_win64-msi'
assert pq(list[2]).attr('class') == 'os_win64-aarch64'
assert pq(list[3]).attr('class') == 'os_osx'
assert pq(list[4]).attr('class') == 'os_linux64'
assert pq(list[5]).attr('class') == 'os_linux'
assert pq(list[6]).attr('class') == 'os_win'
assert pq(list[7]).attr('class') == 'os_win-msi'
assert pq(list[0]).attr("class") == "os_win64"
assert pq(list[1]).attr("class") == "os_win64-msi"
assert pq(list[2]).attr("class") == "os_win64-aarch64"
assert pq(list[3]).attr("class") == "os_osx"
assert pq(list[4]).attr("class") == "os_linux64"
assert pq(list[5]).attr("class") == "os_linux"
assert pq(list[6]).attr("class") == "os_win"
assert pq(list[7]).attr("class") == "os_win-msi"
links = doc('.download-platform-list a')
links = doc(".download-platform-list a")
# Check desktop links have the correct version
self.check_desktop_links(links)
for link in links:
link = pq(link)
href = link.attr('href')
assert href.startswith('https://download.mozilla.org')
self.assertListEqual(parse_qs(urlparse(href).query)['lang'], ['en-US'])
href = link.attr("href")
assert href.startswith("https://download.mozilla.org")
self.assertListEqual(parse_qs(urlparse(href).query)["lang"], ["en-US"])
def test_firefox_desktop_list_aurora(self):
"""
All aurora download links must be directly to https://download.mozilla.org.
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'en-US'
doc = pq(render("{{ download_firefox_desktop_list(channel='alpha') }}",
{'request': get_request}))
get_request = rf.get("/fake")
get_request.locale = "en-US"
doc = pq(render("{{ download_firefox_desktop_list(channel='alpha') }}", {"request": get_request}))
# Check that links classes are ordered as expected.
list = doc('.download-platform-list li')
list = doc(".download-platform-list li")
assert list.length == 8
assert pq(list[0]).attr('class') == 'os_win64'
assert pq(list[1]).attr('class') == 'os_win64-msi'
assert pq(list[2]).attr('class') == 'os_win64-aarch64'
assert pq(list[3]).attr('class') == 'os_osx'
assert pq(list[4]).attr('class') == 'os_linux64'
assert pq(list[5]).attr('class') == 'os_linux'
assert pq(list[6]).attr('class') == 'os_win'
assert pq(list[7]).attr('class') == 'os_win-msi'
assert pq(list[0]).attr("class") == "os_win64"
assert pq(list[1]).attr("class") == "os_win64-msi"
assert pq(list[2]).attr("class") == "os_win64-aarch64"
assert pq(list[3]).attr("class") == "os_osx"
assert pq(list[4]).attr("class") == "os_linux64"
assert pq(list[5]).attr("class") == "os_linux"
assert pq(list[6]).attr("class") == "os_win"
assert pq(list[7]).attr("class") == "os_win-msi"
links = doc('.download-platform-list a')
links = doc(".download-platform-list a")
for link in links:
link = pq(link)
href = link.attr('href')
assert href.startswith('https://download.mozilla.org')
self.assertListEqual(parse_qs(urlparse(href).query)['lang'], ['en-US'])
href = link.attr("href")
assert href.startswith("https://download.mozilla.org")
self.assertListEqual(parse_qs(urlparse(href).query)["lang"], ["en-US"])
def test_firefox_desktop_list_nightly(self):
"""
All nightly download links must be directly to https://download.mozilla.org.
"""
rf = RequestFactory()
get_request = rf.get('/fake')
get_request.locale = 'en-US'
doc = pq(render("{{ download_firefox_desktop_list(channel='nightly') }}",
{'request': get_request}))
get_request = rf.get("/fake")
get_request.locale = "en-US"
doc = pq(render("{{ download_firefox_desktop_list(channel='nightly') }}", {"request": get_request}))
# Check that links classes are ordered as expected.
list = doc('.download-platform-list li')
list = doc(".download-platform-list li")
assert list.length == 7
assert pq(list[0]).attr('class') == 'os_win'
assert pq(list[1]).attr('class') == 'os_win64-msi'
assert pq(list[2]).attr('class') == 'os_win64-aarch64'
assert pq(list[3]).attr('class') == 'os_osx'
assert pq(list[4]).attr('class') == 'os_linux64'
assert pq(list[5]).attr('class') == 'os_linux'
assert pq(list[6]).attr('class') == 'os_win-msi'
assert pq(list[0]).attr("class") == "os_win"
assert pq(list[1]).attr("class") == "os_win64-msi"
assert pq(list[2]).attr("class") == "os_win64-aarch64"
assert pq(list[3]).attr("class") == "os_osx"
assert pq(list[4]).attr("class") == "os_linux64"
assert pq(list[5]).attr("class") == "os_linux"
assert pq(list[6]).attr("class") == "os_win-msi"
links = doc('.download-platform-list a')
links = doc(".download-platform-list a")
for link in links:
link = pq(link)
href = link.attr('href')
assert href.startswith('https://download.mozilla.org')
self.assertListEqual(parse_qs(urlparse(href).query)['lang'], ['en-US'])
href = link.attr("href")
assert href.startswith("https://download.mozilla.org")
self.assertListEqual(parse_qs(urlparse(href).query)["lang"], ["en-US"])
class TestFirefoxURL(TestCase):
rf = RequestFactory()
def _render(self, platform, page, channel=None):
req = self.rf.get('/')
req = self.rf.get("/")
if channel:
tmpl = "{{ firefox_url('%s', '%s', '%s') }}" % (platform, page, channel)
else:
tmpl = "{{ firefox_url('%s', '%s') }}" % (platform, page)
return render(tmpl, {'request': req})
return render(tmpl, {"request": req})
def test_firefox_all(self):
"""Should return a reversed path for the Firefox all downloads page"""
assert self._render('desktop', 'all').endswith('/firefox/all/#product-desktop-release')
assert self._render('desktop', 'all', 'release').endswith('/firefox/all/#product-desktop-release')
assert self._render('desktop', 'all', 'beta').endswith('/firefox/all/#product-desktop-beta')
assert self._render('desktop', 'all', 'alpha').endswith('/firefox/all/#product-desktop-developer')
assert self._render('desktop', 'all', 'developer').endswith('/firefox/all/#product-desktop-developer')
assert self._render('desktop', 'all', 'nightly').endswith('/firefox/all/#product-desktop-nightly')
assert self._render('desktop', 'all', 'esr').endswith('/firefox/all/#product-desktop-esr')
assert self._render('desktop', 'all', 'organizations').endswith('/firefox/all/#product-desktop-esr')
assert self._render('android', 'all').endswith('/firefox/all/#product-android-release')
assert self._render('android', 'all', 'release').endswith('/firefox/all/#product-android-release')
assert self._render('android', 'all', 'beta').endswith('/firefox/all/#product-android-beta')
assert self._render('android', 'all', 'nightly').endswith('/firefox/all/#product-android-nightly')
assert self._render("desktop", "all").endswith("/firefox/all/#product-desktop-release")
assert self._render("desktop", "all", "release").endswith("/firefox/all/#product-desktop-release")
assert self._render("desktop", "all", "beta").endswith("/firefox/all/#product-desktop-beta")
assert self._render("desktop", "all", "alpha").endswith("/firefox/all/#product-desktop-developer")
assert self._render("desktop", "all", "developer").endswith("/firefox/all/#product-desktop-developer")
assert self._render("desktop", "all", "nightly").endswith("/firefox/all/#product-desktop-nightly")
assert self._render("desktop", "all", "esr").endswith("/firefox/all/#product-desktop-esr")
assert self._render("desktop", "all", "organizations").endswith("/firefox/all/#product-desktop-esr")
assert self._render("android", "all").endswith("/firefox/all/#product-android-release")
assert self._render("android", "all", "release").endswith("/firefox/all/#product-android-release")
assert self._render("android", "all", "beta").endswith("/firefox/all/#product-android-beta")
assert self._render("android", "all", "nightly").endswith("/firefox/all/#product-android-nightly")
def test_firefox_sysreq(self):
"""Should return a reversed path for the Firefox sysreq page"""
assert self._render('desktop', 'sysreq').endswith('/firefox/system-requirements/')
assert (
self._render('desktop', 'sysreq', 'release')
.endswith('/firefox/system-requirements/'))
assert (
self._render('desktop', 'sysreq', 'beta')
.endswith('/firefox/beta/system-requirements/'))
assert (
self._render('desktop', 'sysreq', 'alpha')
.endswith('/firefox/developer/system-requirements/'))
assert (
self._render('desktop', 'sysreq', 'esr')
.endswith('/firefox/organizations/system-requirements/'))
assert (
self._render('desktop', 'sysreq', 'organizations')
.endswith('/firefox/organizations/system-requirements/'))
assert self._render("desktop", "sysreq").endswith("/firefox/system-requirements/")
assert self._render("desktop", "sysreq", "release").endswith("/firefox/system-requirements/")
assert self._render("desktop", "sysreq", "beta").endswith("/firefox/beta/system-requirements/")
assert self._render("desktop", "sysreq", "alpha").endswith("/firefox/developer/system-requirements/")
assert self._render("desktop", "sysreq", "esr").endswith("/firefox/organizations/system-requirements/")
assert self._render("desktop", "sysreq", "organizations").endswith("/firefox/organizations/system-requirements/")
def test_desktop_notes(self):
"""Should return a reversed path for the desktop notes page"""
assert self._render('desktop', 'notes').endswith('/firefox/notes/')
assert self._render('desktop', 'notes', 'release').endswith('/firefox/notes/')
assert self._render('desktop', 'notes', 'beta').endswith('/firefox/beta/notes/')
assert self._render('desktop', 'notes', 'alpha').endswith('/firefox/developer/notes/')
assert self._render('desktop', 'notes', 'esr').endswith('/firefox/organizations/notes/')
assert (
self._render('desktop', 'notes', 'organizations')
.endswith('/firefox/organizations/notes/'))
assert self._render("desktop", "notes").endswith("/firefox/notes/")
assert self._render("desktop", "notes", "release").endswith("/firefox/notes/")
assert self._render("desktop", "notes", "beta").endswith("/firefox/beta/notes/")
assert self._render("desktop", "notes", "alpha").endswith("/firefox/developer/notes/")
assert self._render("desktop", "notes", "esr").endswith("/firefox/organizations/notes/")
assert self._render("desktop", "notes", "organizations").endswith("/firefox/organizations/notes/")
def test_android_notes(self):
"""Should return a reversed path for the Android notes page"""
assert self._render('android', 'notes').endswith('/firefox/android/notes/')
assert self._render('android', 'notes', 'release').endswith('/firefox/android/notes/')
assert self._render('android', 'notes', 'beta').endswith('/firefox/android/beta/notes/')
assert self._render('android', 'notes', 'alpha').endswith('/firefox/android/aurora/notes/')
assert self._render("android", "notes").endswith("/firefox/android/notes/")
assert self._render("android", "notes", "release").endswith("/firefox/android/notes/")
assert self._render("android", "notes", "beta").endswith("/firefox/android/beta/notes/")
assert self._render("android", "notes", "alpha").endswith("/firefox/android/aurora/notes/")

Просмотреть файл

@ -20,7 +20,7 @@ from bedrock.mozorg.tests import TestCase
@override_settings(
STUB_ATTRIBUTION_HMAC_KEY='achievers',
STUB_ATTRIBUTION_HMAC_KEY="achievers",
STUB_ATTRIBUTION_RATE=1,
STUB_ATTRIBUTION_MAX_LEN=600,
)
@ -28,268 +28,254 @@ class TestStubAttributionCode(TestCase):
def _get_request(self, params):
rf = RequestFactory()
return rf.get(
'/',
"/",
params,
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
HTTP_ACCEPT='application/json',
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
HTTP_ACCEPT="application/json",
)
def test_not_ajax_request(self):
req = RequestFactory().get('/', {'source': 'malibu'})
req = RequestFactory().get("/", {"source": "malibu"})
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 400)
assert 'cache-control' not in resp
assert "cache-control" not in resp
data = json.loads(resp.content)
self.assertEqual(data['error'], 'Resource only available via XHR')
self.assertEqual(data["error"], "Resource only available via XHR")
def test_no_valid_param_names(self):
final_params = {
'source': 'www.mozilla.org',
'medium': '(none)',
'campaign': '(not set)',
'content': '(not set)',
'experiment': '(not set)',
'variation': '(not set)',
'ua': '(not set)',
'visit_id': '(not set)',
"source": "www.mozilla.org",
"medium": "(none)",
"campaign": "(not set)",
"content": "(not set)",
"experiment": "(not set)",
"variation": "(not set)",
"ua": "(not set)",
"visit_id": "(not set)",
}
req = self._get_request({'dude': 'abides'})
req = self._get_request({"dude": "abides"})
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 200)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
data = json.loads(resp.content)
# will it blend?
attrs = parse_qs(
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
)
attrs = parse_qs(querystringsafe_base64.decode(data["attribution_code"].encode()).decode())
# parse_qs returns a dict with lists for values
attrs = {k: v[0] for k, v in attrs.items()}
self.assertDictEqual(attrs, final_params)
self.assertEqual(
data['attribution_sig'],
'135b2245f6b70978bc8142a91521facdb31d70a1bfbdefdc1bd1dee92ce21a22',
data["attribution_sig"],
"135b2245f6b70978bc8142a91521facdb31d70a1bfbdefdc1bd1dee92ce21a22",
)
def test_no_valid_param_data(self):
params = {
'utm_source': 'br@ndt',
'utm_medium': 'ae<t>her',
'experiment': 'dfb</p>s',
'variation': 'ef&bvcv',
'visit_id': '14</p>4538.1610<t>957',
"utm_source": "br@ndt",
"utm_medium": "ae<t>her",
"experiment": "dfb</p>s",
"variation": "ef&bvcv",
"visit_id": "14</p>4538.1610<t>957",
}
final_params = {
'source': 'www.mozilla.org',
'medium': '(none)',
'campaign': '(not set)',
'content': '(not set)',
'experiment': '(not set)',
'variation': '(not set)',
'ua': '(not set)',
'visit_id': '(not set)',
"source": "www.mozilla.org",
"medium": "(none)",
"campaign": "(not set)",
"content": "(not set)",
"experiment": "(not set)",
"variation": "(not set)",
"ua": "(not set)",
"visit_id": "(not set)",
}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 200)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
data = json.loads(resp.content)
# will it blend?
attrs = parse_qs(
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
)
attrs = parse_qs(querystringsafe_base64.decode(data["attribution_code"].encode()).decode())
# parse_qs returns a dict with lists for values
attrs = {k: v[0] for k, v in attrs.items()}
self.assertDictEqual(attrs, final_params)
self.assertEqual(
data['attribution_sig'],
'135b2245f6b70978bc8142a91521facdb31d70a1bfbdefdc1bd1dee92ce21a22',
data["attribution_sig"],
"135b2245f6b70978bc8142a91521facdb31d70a1bfbdefdc1bd1dee92ce21a22",
)
def test_some_valid_param_data(self):
params = {'utm_source': 'brandt', 'utm_content': 'ae<t>her'}
params = {"utm_source": "brandt", "utm_content": "ae<t>her"}
final_params = {
'source': 'brandt',
'medium': '(direct)',
'campaign': '(not set)',
'content': '(not set)',
'experiment': '(not set)',
'variation': '(not set)',
'ua': '(not set)',
'visit_id': '(not set)',
"source": "brandt",
"medium": "(direct)",
"campaign": "(not set)",
"content": "(not set)",
"experiment": "(not set)",
"variation": "(not set)",
"ua": "(not set)",
"visit_id": "(not set)",
}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 200)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
data = json.loads(resp.content)
# will it blend?
attrs = parse_qs(
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
)
attrs = parse_qs(querystringsafe_base64.decode(data["attribution_code"].encode()).decode())
# parse_qs returns a dict with lists for values
attrs = {k: v[0] for k, v in attrs.items()}
self.assertDictEqual(attrs, final_params)
self.assertEqual(
data['attribution_sig'],
'b53097f17741b75cdd5b737d3c8ba03349a6093148adeada2ee69adf4fe87322',
data["attribution_sig"],
"b53097f17741b75cdd5b737d3c8ba03349a6093148adeada2ee69adf4fe87322",
)
def test_campaign_data_too_long(self):
"""If the code is too long then the utm_campaign value should be truncated"""
params = {
'utm_source': 'brandt',
'utm_medium': 'aether',
'utm_content': 'A144_A000_0000000',
'utm_campaign': 'The%7cDude%7cabides%7cI%7cdont%7cknow%7cabout%7cyou%7c'
'but%7cI%7ctake%7ccomfort%7cin%7cthat' * 6,
'experiment': '(not set)',
'variation': '(not set)',
'ua': 'chrome',
'visit_id': '1456954538.1610960957',
"utm_source": "brandt",
"utm_medium": "aether",
"utm_content": "A144_A000_0000000",
"utm_campaign": "The%7cDude%7cabides%7cI%7cdont%7cknow%7cabout%7cyou%7cbut%7cI%7ctake%7ccomfort%7cin%7cthat" * 6,
"experiment": "(not set)",
"variation": "(not set)",
"ua": "chrome",
"visit_id": "1456954538.1610960957",
}
final_params = {
'source': 'brandt',
'medium': 'aether',
'campaign': 'The|Dude|abides|I|dont|know|about|you|but|I|take|comfort|in'
'|thatThe|Dude|abides|I|dont|know|about|you|but|I|take|comfort|in|thatThe'
'|Dude|abides|I|dont|know|about|you|but|I|take|comfort|in|thatThe|Dude|abides'
'|I|dont|know|about|you|but|I|take|comfort|in|thatThe|Dude|abides|I|dont|know'
'|about|you|but|I|take|comfort|in|thatT_',
'content': 'A144_A000_0000000',
'experiment': '(not set)',
'variation': '(not set)',
'ua': 'chrome',
'visit_id': '1456954538.1610960957',
"source": "brandt",
"medium": "aether",
"campaign": "The|Dude|abides|I|dont|know|about|you|but|I|take|comfort|in"
"|thatThe|Dude|abides|I|dont|know|about|you|but|I|take|comfort|in|thatThe"
"|Dude|abides|I|dont|know|about|you|but|I|take|comfort|in|thatThe|Dude|abides"
"|I|dont|know|about|you|but|I|take|comfort|in|thatThe|Dude|abides|I|dont|know"
"|about|you|but|I|take|comfort|in|thatT_",
"content": "A144_A000_0000000",
"experiment": "(not set)",
"variation": "(not set)",
"ua": "chrome",
"visit_id": "1456954538.1610960957",
}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 200)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
data = json.loads(resp.content)
# will it blend?
code = querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
code = querystringsafe_base64.decode(data["attribution_code"].encode()).decode()
assert len(code) <= 600
attrs = parse_qs(code)
# parse_qs returns a dict with lists for values
attrs = {k: v[0] for k, v in attrs.items()}
self.assertDictEqual(attrs, final_params)
self.assertEqual(
data['attribution_sig'],
'3c1611db912c51a96418eb7806fbaf1400b8d05fbf6ee4f2f1fb3c0ba74a89f4',
data["attribution_sig"],
"3c1611db912c51a96418eb7806fbaf1400b8d05fbf6ee4f2f1fb3c0ba74a89f4",
)
def test_other_data_too_long_not_campaign(self):
"""If the code is too long but not utm_campaign return error"""
params = {
'utm_source': 'brandt',
'utm_campaign': 'dude',
'utm_content': 'A144_A000_0000000',
'utm_medium': 'The%7cDude%7cabides%7cI%7cdont%7cknow%7cabout%7cyou%7c'
'but%7cI%7ctake%7ccomfort%7cin%7cthat' * 6,
"utm_source": "brandt",
"utm_campaign": "dude",
"utm_content": "A144_A000_0000000",
"utm_medium": "The%7cDude%7cabides%7cI%7cdont%7cknow%7cabout%7cyou%7cbut%7cI%7ctake%7ccomfort%7cin%7cthat" * 6,
}
final_params = {'error': 'Invalid code'}
final_params = {"error": "Invalid code"}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 400)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
data = json.loads(resp.content)
self.assertDictEqual(data, final_params)
def test_returns_valid_data(self):
params = {
'utm_source': 'brandt',
'utm_medium': 'aether',
'experiment': 'firefox-new',
'variation': '1',
'ua': 'chrome',
'visit_id': '1456954538.1610960957',
"utm_source": "brandt",
"utm_medium": "aether",
"experiment": "firefox-new",
"variation": "1",
"ua": "chrome",
"visit_id": "1456954538.1610960957",
}
final_params = {
'source': 'brandt',
'medium': 'aether',
'campaign': '(not set)',
'content': '(not set)',
'experiment': 'firefox-new',
'variation': '1',
'ua': 'chrome',
'visit_id': '1456954538.1610960957'
"source": "brandt",
"medium": "aether",
"campaign": "(not set)",
"content": "(not set)",
"experiment": "firefox-new",
"variation": "1",
"ua": "chrome",
"visit_id": "1456954538.1610960957",
}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 200)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
data = json.loads(resp.content)
# will it blend?
attrs = parse_qs(
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
)
attrs = parse_qs(querystringsafe_base64.decode(data["attribution_code"].encode()).decode())
# parse_qs returns a dict with lists for values
attrs = {k: v[0] for k, v in attrs.items()}
self.assertDictEqual(attrs, final_params)
self.assertEqual(
data['attribution_sig'],
'b2dc555b2914fdec9f9a1247d244520392e4f888961a6fb57a74a1cdf041261f',
data["attribution_sig"],
"b2dc555b2914fdec9f9a1247d244520392e4f888961a6fb57a74a1cdf041261f",
)
def test_handles_referrer(self):
params = {'utm_source': 'brandt', 'referrer': 'https://duckduckgo.com/privacy'}
params = {"utm_source": "brandt", "referrer": "https://duckduckgo.com/privacy"}
final_params = {
'source': 'brandt',
'medium': '(direct)',
'campaign': '(not set)',
'content': '(not set)',
'experiment': '(not set)',
'variation': '(not set)',
'ua': '(not set)',
'visit_id': '(not set)',
"source": "brandt",
"medium": "(direct)",
"campaign": "(not set)",
"content": "(not set)",
"experiment": "(not set)",
"variation": "(not set)",
"ua": "(not set)",
"visit_id": "(not set)",
}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 200)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
data = json.loads(resp.content)
# will it blend?
attrs = parse_qs(
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
)
attrs = parse_qs(querystringsafe_base64.decode(data["attribution_code"].encode()).decode())
# parse_qs returns a dict with lists for values
attrs = {k: v[0] for k, v in attrs.items()}
self.assertDictEqual(attrs, final_params)
self.assertEqual(
data['attribution_sig'],
'b53097f17741b75cdd5b737d3c8ba03349a6093148adeada2ee69adf4fe87322',
data["attribution_sig"],
"b53097f17741b75cdd5b737d3c8ba03349a6093148adeada2ee69adf4fe87322",
)
def test_handles_referrer_no_source(self):
params = {
'referrer': 'https://example.com:5000/searchin',
'utm_medium': 'aether',
"referrer": "https://example.com:5000/searchin",
"utm_medium": "aether",
}
final_params = {
'source': 'example.com:5000',
'medium': 'referral',
'campaign': '(not set)',
'content': '(not set)',
'experiment': '(not set)',
'variation': '(not set)',
'ua': '(not set)',
'visit_id': '(not set)',
"source": "example.com:5000",
"medium": "referral",
"campaign": "(not set)",
"content": "(not set)",
"experiment": "(not set)",
"variation": "(not set)",
"ua": "(not set)",
"visit_id": "(not set)",
}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 200)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
data = json.loads(resp.content)
# will it blend?
attrs = parse_qs(
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
)
attrs = parse_qs(querystringsafe_base64.decode(data["attribution_code"].encode()).decode())
# parse_qs returns a dict with lists for values
attrs = {k: v[0] for k, v in attrs.items()}
self.assertDictEqual(attrs, final_params)
self.assertEqual(
data['attribution_sig'],
'd075cbcbae3bcef5bda3650a259863151586e3a4709d53886ab3cc83a6963d00',
data["attribution_sig"],
"d075cbcbae3bcef5bda3650a259863151586e3a4709d53886ab3cc83a6963d00",
)
def test_handles_referrer_utf8(self):
@ -299,327 +285,321 @@ class TestStubAttributionCode(TestCase):
non-ascii domain names in the referrer. The allowed list for bouncer
doesn't include any such domains anyway, so we should just ignore them.
"""
params = {'referrer': 'http://youtubê.com/sorry/'}
params = {"referrer": "http://youtubê.com/sorry/"}
final_params = {
'source': 'www.mozilla.org',
'medium': '(none)',
'campaign': '(not set)',
'content': '(not set)',
'experiment': '(not set)',
'variation': '(not set)',
'ua': '(not set)',
'visit_id': '(not set)',
"source": "www.mozilla.org",
"medium": "(none)",
"campaign": "(not set)",
"content": "(not set)",
"experiment": "(not set)",
"variation": "(not set)",
"ua": "(not set)",
"visit_id": "(not set)",
}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 200)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
data = json.loads(resp.content)
# will it blend?
attrs = parse_qs(
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
)
attrs = parse_qs(querystringsafe_base64.decode(data["attribution_code"].encode()).decode())
# parse_qs returns a dict with lists for values
attrs = {k: v[0] for k, v in attrs.items()}
self.assertDictEqual(attrs, final_params)
self.assertEqual(
data['attribution_sig'],
'135b2245f6b70978bc8142a91521facdb31d70a1bfbdefdc1bd1dee92ce21a22',
data["attribution_sig"],
"135b2245f6b70978bc8142a91521facdb31d70a1bfbdefdc1bd1dee92ce21a22",
)
@override_settings(STUB_ATTRIBUTION_RATE=0.2)
def test_rate_limit(self):
params = {'utm_source': 'brandt', 'utm_medium': 'aether'}
params = {"utm_source": "brandt", "utm_medium": "aether"}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 200)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
@override_settings(STUB_ATTRIBUTION_RATE=0)
def test_rate_limit_disabled(self):
params = {'utm_source': 'brandt', 'utm_medium': 'aether'}
params = {"utm_source": "brandt", "utm_medium": "aether"}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 429)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
@override_settings(STUB_ATTRIBUTION_HMAC_KEY='')
@override_settings(STUB_ATTRIBUTION_HMAC_KEY="")
def test_no_hmac_key_set(self):
params = {'utm_source': 'brandt', 'utm_medium': 'aether'}
params = {"utm_source": "brandt", "utm_medium": "aether"}
req = self._get_request(params)
resp = views.stub_attribution_code(req)
self.assertEqual(resp.status_code, 403)
assert resp['cache-control'] == 'max-age=300'
assert resp["cache-control"] == "max-age=300"
class TestSendToDeviceView(TestCase):
def setUp(self):
patcher = patch('bedrock.firefox.views.basket.subscribe')
patcher = patch("bedrock.firefox.views.basket.subscribe")
self.mock_subscribe = patcher.start()
self.addCleanup(patcher.stop)
patcher = patch('bedrock.firefox.views.basket.request')
patcher = patch("bedrock.firefox.views.basket.request")
self.mock_send_sms = patcher.start()
self.addCleanup(patcher.stop)
def _request(self, data, expected_status=200, locale='en-US'):
req = RequestFactory().post('/', data)
def _request(self, data, expected_status=200, locale="en-US"):
req = RequestFactory().post("/", data)
req.locale = locale
resp = views.send_to_device_ajax(req)
assert resp.status_code == expected_status
return json.loads(resp.content)
def test_phone_or_email_required(self):
resp_data = self._request({'platform': 'android'})
assert not resp_data['success']
assert 's2d-email' in resp_data['errors']
resp_data = self._request({"platform": "android"})
assert not resp_data["success"]
assert "s2d-email" in resp_data["errors"]
assert not self.mock_send_sms.called
assert not self.mock_subscribe.called
def test_send_android_email(self):
resp_data = self._request(
{
'platform': 'android',
's2d-email': 'dude@example.com',
'source-url': 'https://nihilism.info',
"platform": "android",
"s2d-email": "dude@example.com",
"source-url": "https://nihilism.info",
}
)
assert resp_data['success']
assert resp_data["success"]
self.mock_subscribe.assert_called_with(
'dude@example.com',
views.SEND_TO_DEVICE_MESSAGE_SETS['default']['email']['android'],
source_url='https://nihilism.info',
lang='en-US',
"dude@example.com",
views.SEND_TO_DEVICE_MESSAGE_SETS["default"]["email"]["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',
's2d-email': 'dude@example.com',
'source-url': 'https://nihilism.info',
"platform": "android",
"s2d-email": "dude@example.com",
"source-url": "https://nihilism.info",
},
400,
)
assert not resp_data['success']
assert 'system' in resp_data['errors']
assert not resp_data["success"]
assert "system" in resp_data["errors"]
def test_send_android_bad_email(self):
resp_data = self._request(
{
'platform': 'android',
's2d-email': '@example.com',
'source-url': 'https://nihilism.info',
"platform": "android",
"s2d-email": "@example.com",
"source-url": "https://nihilism.info",
}
)
assert not resp_data['success']
assert 'email' in resp_data['errors']
assert not resp_data["success"]
assert "email" in resp_data["errors"]
assert not self.mock_subscribe.called
# an invalid value for 'message-set' should revert to 'default' message set
def test_invalid_message_set(self):
resp_data = self._request(
{
'platform': 'ios',
's2d-email': 'dude@example.com',
'message-set': 'the-dude-is-not-in',
"platform": "ios",
"s2d-email": "dude@example.com",
"message-set": "the-dude-is-not-in",
}
)
assert resp_data['success']
assert resp_data["success"]
self.mock_subscribe.assert_called_with(
'dude@example.com',
views.SEND_TO_DEVICE_MESSAGE_SETS['default']['email']['ios'],
"dude@example.com",
views.SEND_TO_DEVICE_MESSAGE_SETS["default"]["email"]["ios"],
source_url=None,
lang='en-US',
lang="en-US",
)
# /firefox/android/ embedded widget (bug 1221328)
def test_android_embedded_email(self):
resp_data = self._request(
{
'platform': 'android',
's2d-email': 'dude@example.com',
'message-set': 'fx-android',
"platform": "android",
"s2d-email": "dude@example.com",
"message-set": "fx-android",
}
)
assert resp_data['success']
assert resp_data["success"]
self.mock_subscribe.assert_called_with(
'dude@example.com',
views.SEND_TO_DEVICE_MESSAGE_SETS['fx-android']['email']['android'],
"dude@example.com",
views.SEND_TO_DEVICE_MESSAGE_SETS["fx-android"]["email"]["android"],
source_url=None,
lang='en-US',
lang="en-US",
)
# /firefox/mobile-download/desktop
def test_fx_mobile_download_desktop_email(self):
resp_data = self._request(
{
's2d-email': 'dude@example.com',
'message-set': 'fx-mobile-download-desktop',
"s2d-email": "dude@example.com",
"message-set": "fx-mobile-download-desktop",
}
)
assert resp_data['success']
assert resp_data["success"]
self.mock_subscribe.assert_called_with(
'dude@example.com',
views.SEND_TO_DEVICE_MESSAGE_SETS['fx-mobile-download-desktop']['email'][
'all'
],
"dude@example.com",
views.SEND_TO_DEVICE_MESSAGE_SETS["fx-mobile-download-desktop"]["email"]["all"],
source_url=None,
lang='en-US',
lang="en-US",
)
@override_settings(DEV=False)
@patch('bedrock.firefox.views.l10n_utils.render', return_value=HttpResponse())
@patch("bedrock.firefox.views.l10n_utils.render", return_value=HttpResponse())
class TestFirefoxNew(TestCase):
@patch.object(views, 'ftl_file_is_active', lambda *x: True)
@patch.object(views, "ftl_file_is_active", lambda *x: True)
def test_download_template(self, render_mock):
req = RequestFactory().get('/firefox/new/')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/new/")
req.locale = "en-US"
view = views.NewView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/desktop/download.html']
assert template == ["firefox/new/desktop/download.html"]
@patch.object(views, 'ftl_file_is_active', lambda *x: True)
@patch.object(views, "ftl_file_is_active", lambda *x: True)
def test_thanks_template(self, render_mock):
req = RequestFactory().get('/firefox/download/thanks/')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/download/thanks/")
req.locale = "en-US"
view = views.DownloadThanksView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/desktop/thanks.html']
assert template == ["firefox/new/desktop/thanks.html"]
@patch.object(views, 'ftl_file_is_active', lambda *x: False)
@patch.object(views, "ftl_file_is_active", lambda *x: False)
def test_download_basic_template(self, render_mock):
req = RequestFactory().get('/firefox/new/')
req.locale = 'de'
req = RequestFactory().get("/firefox/new/")
req.locale = "de"
view = views.NewView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/basic/base_download.html']
assert template == ["firefox/new/basic/base_download.html"]
@patch.object(views, 'ftl_file_is_active', lambda *x: False)
@patch.object(views, "ftl_file_is_active", lambda *x: False)
def test_thanks_basic_template(self, render_mock):
req = RequestFactory().get('/firefox/download/thanks/')
req.locale = 'de'
req = RequestFactory().get("/firefox/download/thanks/")
req.locale = "de"
view = views.DownloadThanksView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/basic/thanks.html']
assert template == ["firefox/new/basic/thanks.html"]
def test_thanks_redirect(self, render_mock):
req = RequestFactory().get('/firefox/new/?scene=2&dude=abides')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/new/?scene=2&dude=abides")
req.locale = "en-US"
view = views.NewView.as_view()
resp = view(req)
assert resp.status_code == 301
assert resp['location'].endswith(
'/firefox/download/thanks/?scene=2&dude=abides'
)
assert resp["location"].endswith("/firefox/download/thanks/?scene=2&dude=abides")
# begin AMO experiment - issue 10207
@patch.dict(os.environ, SWITCH_NEW_REDESIGN='True')
@patch.dict(os.environ, SWITCH_NEW_REDESIGN="True")
def test_thanks_amo_experiment_en(self, render_mock):
req = RequestFactory().get('/firefox/download/thanks/?xv=amo')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/download/thanks/?xv=amo")
req.locale = "en-US"
view = views.DownloadThanksView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/desktop/thanks-amo-exp.html']
assert template == ["firefox/new/desktop/thanks-amo-exp.html"]
@patch.dict(os.environ, SWITCH_NEW_REDESIGN='True')
@patch.dict(os.environ, SWITCH_NEW_REDESIGN="True")
def test_thanks_amo_experiment_locales(self, render_mock):
req = RequestFactory().get('/firefox/download/thanks/?xv=amo')
req.locale = 'de'
req = RequestFactory().get("/firefox/download/thanks/?xv=amo")
req.locale = "de"
view = views.DownloadThanksView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/desktop/thanks.html']
assert template == ["firefox/new/desktop/thanks.html"]
# begin yandex - issue 5635
@patch.dict(os.environ, SWITCH_FIREFOX_YANDEX='True')
@patch.dict(os.environ, SWITCH_FIREFOX_YANDEX="True")
def test_yandex_download(self, render_mock):
req = RequestFactory().get('/firefox/new/')
req.locale = 'ru'
req = RequestFactory().get("/firefox/new/")
req.locale = "ru"
view = views.NewView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/desktop/download_yandex.html']
assert template == ["firefox/new/desktop/download_yandex.html"]
@patch.dict(os.environ, SWITCH_FIREFOX_YANDEX='True')
@patch.object(views, 'ftl_file_is_active', lambda *x: True)
@patch.dict(os.environ, SWITCH_FIREFOX_YANDEX="True")
@patch.object(views, "ftl_file_is_active", lambda *x: True)
def test_yandex_show_to_ru(self, render_mock):
req = RequestFactory().get('/firefox/new/')
req.locale = 'ru'
req = RequestFactory().get("/firefox/new/")
req.locale = "ru"
view = views.NewView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/desktop/download_yandex.html']
assert template == ["firefox/new/desktop/download_yandex.html"]
@patch.dict(os.environ, SWITCH_FIREFOX_YANDEX='True')
@patch.object(views, 'ftl_file_is_active', lambda *x: True)
@patch.dict(os.environ, SWITCH_FIREFOX_YANDEX="True")
@patch.object(views, "ftl_file_is_active", lambda *x: True)
def test_yandex_hide_not_ru(self, render_mock):
req = RequestFactory().get('/firefox/new/')
req.locale = 'de'
req = RequestFactory().get("/firefox/new/")
req.locale = "de"
view = views.NewView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/desktop/download.html']
assert template == ["firefox/new/desktop/download.html"]
@patch.dict(os.environ, SWITCH_FIREFOX_YANDEX='False')
@patch.object(views, 'ftl_file_is_active', lambda *x: True)
@patch.dict(os.environ, SWITCH_FIREFOX_YANDEX="False")
@patch.object(views, "ftl_file_is_active", lambda *x: True)
def test_yandex_hide_switch_off(self, render_mock):
req = RequestFactory().get('/firefox/new/')
req.locale = 'ru'
req = RequestFactory().get("/firefox/new/")
req.locale = "ru"
view = views.NewView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/desktop/download.html']
assert template == ["firefox/new/desktop/download.html"]
# end yandex - issue 5635
@patch.dict(os.environ, EXP_CONFIG_FX_NEW='de:100')
@patch.dict(os.environ, EXP_CONFIG_FX_NEW="de:100")
def test_experiment_redirect(self, render_mock):
req = RequestFactory().get('/firefox/new/')
req.locale = 'de'
req = RequestFactory().get("/firefox/new/")
req.locale = "de"
view = views.NewView.as_view()
resp = view(req)
assert resp.status_code == 302
assert resp['location'].endswith('/exp/firefox/new/')
assert resp['cache-control'] == 'max-age=0, no-cache, no-store, must-revalidate'
req.locale = 'en-US'
assert resp["location"].endswith("/exp/firefox/new/")
assert resp["cache-control"] == "max-age=0, no-cache, no-store, must-revalidate"
req.locale = "en-US"
resp = view(req)
assert resp.status_code == 200
assert 'cache-control' not in resp
assert "cache-control" not in resp
@patch.dict(os.environ, EXP_CONFIG_FX_NEW='de:100')
@patch.dict(os.environ, EXP_CONFIG_FX_NEW="de:100")
def test_experiment_redirect_query(self, render_mock):
req = RequestFactory().get('/firefox/new/?dude=abide&walter=bowl')
req.locale = 'de'
req = RequestFactory().get("/firefox/new/?dude=abide&walter=bowl")
req.locale = "de"
view = views.NewView.as_view()
resp = view(req)
assert resp.status_code == 302
assert resp['location'].endswith('/exp/firefox/new/?dude=abide&walter=bowl')
assert resp["location"].endswith("/exp/firefox/new/?dude=abide&walter=bowl")
@patch.dict(os.environ, EXP_CONFIG_FX_NEW='de:100')
@patch.dict(os.environ, EXP_CONFIG_FX_NEW="de:100")
def test_experiment_redirect_automation_param(self, render_mock):
req = RequestFactory().get('/firefox/new/?automation=true')
req.locale = 'de'
req = RequestFactory().get("/firefox/new/?automation=true")
req.locale = "de"
view = views.NewView.as_view()
resp = view(req)
assert resp.status_code == 200
assert 'cache-control' not in resp
assert "cache-control" not in resp
class TestFirefoxNewNoIndex(TestCase):
def test_download_noindex(self):
# Scene 1 of /firefox/new/ should never contain a noindex tag.
req = RequestFactory().get('/firefox/new/')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/new/")
req.locale = "en-US"
view = views.NewView.as_view()
response = view(req)
doc = pq(response.content)
@ -628,63 +608,62 @@ class TestFirefoxNewNoIndex(TestCase):
def test_thanks_canonical(self):
# Scene 2 /firefox/download/thanks/ should always contain a noindex tag.
req = RequestFactory().get('/firefox/download/thanks/')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/download/thanks/")
req.locale = "en-US"
view = views.DownloadThanksView.as_view()
response = view(req)
doc = pq(response.content)
robots = doc('meta[name="robots"]')
assert robots.length == 1
assert 'noindex' in robots.attr('content')
assert "noindex" in robots.attr("content")
@override_settings(DEV=False)
@patch('bedrock.firefox.views.l10n_utils.render', return_value=HttpResponse())
@patch("bedrock.firefox.views.l10n_utils.render", return_value=HttpResponse())
class TestFirefoxPlatform(TestCase):
@patch.object(views, 'ftl_file_is_active', lambda *x: True)
@patch.object(views, "ftl_file_is_active", lambda *x: True)
def test_linux_download_template(self, render_mock):
req = RequestFactory().get('/firefox/linux/')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/linux/")
req.locale = "en-US"
view = views.PlatformViewLinux.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/basic/download_linux.html']
assert template == ["firefox/new/basic/download_linux.html"]
@patch.object(views, 'ftl_file_is_active', lambda *x: True)
@patch.object(views, "ftl_file_is_active", lambda *x: True)
def test_mac_download_template(self, render_mock):
req = RequestFactory().get('/firefox/mac/')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/mac/")
req.locale = "en-US"
view = views.PlatformViewMac.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/basic/download_mac.html']
assert template == ["firefox/new/basic/download_mac.html"]
@patch.object(views, 'ftl_file_is_active', lambda *x: True)
@patch.object(views, "ftl_file_is_active", lambda *x: True)
def test_windows_download_template(self, render_mock):
req = RequestFactory().get('/firefox/windows/')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/windows/")
req.locale = "en-US"
view = views.PlatformViewWindows.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/new/basic/download_windows.html']
assert template == ["firefox/new/basic/download_windows.html"]
class TestFirefoxHome(TestCase):
@patch('bedrock.firefox.views.l10n_utils.render')
@patch("bedrock.firefox.views.l10n_utils.render")
def test_firefox_home(self, render_mock):
req = RequestFactory().get('/firefox/')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/")
req.locale = "en-US"
view = views.FirefoxHomeView.as_view()
view(req)
template = render_mock.call_args[0][1]
assert template == ['firefox/home/index-master.html']
assert template == ["firefox/home/index-master.html"]
class TestFirefoxWelcomePage1(TestCase):
@patch('bedrock.firefox.views.l10n_utils.render')
@patch("bedrock.firefox.views.l10n_utils.render")
def test_firefox_welcome_page1(self, render_mock):
req = RequestFactory().get('/firefox/welcome/1/')
req.locale = 'en-US'
req = RequestFactory().get("/firefox/welcome/1/")
req.locale = "en-US"
views.firefox_welcome_page1(req)
render_mock.assert_called_once_with(req, 'firefox/welcome/page1.html', ANY,
ftl_files='firefox/welcome/page1')
render_mock.assert_called_once_with(req, "firefox/welcome/page1.html", ANY, ftl_files="firefox/welcome/page1")

Просмотреть файл

@ -14,202 +14,211 @@ from bedrock.utils import views as utils_views
from bedrock.utils.views import VariationTemplateView
latest_re = r'^firefox(?:/(?P<version>%s))?/%s/$'
firstrun_re = latest_re % (version_re, 'firstrun')
whatsnew_re = latest_re % (version_re, 'whatsnew')
whatsnew_re_china = latest_re % (version_re, 'whatsnew/china')
whatsnew_re_en = latest_re % (version_re, 'whatsnew/en')
whatsnew_re_france = latest_re % (version_re, 'whatsnew/france')
whatsnew_re_all = latest_re % (version_re, 'whatsnew/all')
platform_re = '(?P<platform>android|ios)'
channel_re = '(?P<channel>beta|aurora|developer|nightly|organizations)'
releasenotes_re = latest_re % (version_re, r'(aurora|release)notes')
android_releasenotes_re = releasenotes_re.replace(r'firefox', 'firefox/android')
ios_releasenotes_re = releasenotes_re.replace(r'firefox', 'firefox/ios')
sysreq_re = latest_re % (version_re, 'system-requirements')
android_sysreq_re = sysreq_re.replace(r'firefox', 'firefox/android')
ios_sysreq_re = sysreq_re.replace(r'firefox', 'firefox/ios')
latest_re = r"^firefox(?:/(?P<version>%s))?/%s/$"
firstrun_re = latest_re % (version_re, "firstrun")
whatsnew_re = latest_re % (version_re, "whatsnew")
whatsnew_re_china = latest_re % (version_re, "whatsnew/china")
whatsnew_re_en = latest_re % (version_re, "whatsnew/en")
whatsnew_re_france = latest_re % (version_re, "whatsnew/france")
whatsnew_re_all = latest_re % (version_re, "whatsnew/all")
platform_re = "(?P<platform>android|ios)"
channel_re = "(?P<channel>beta|aurora|developer|nightly|organizations)"
releasenotes_re = latest_re % (version_re, r"(aurora|release)notes")
android_releasenotes_re = releasenotes_re.replace(r"firefox", "firefox/android")
ios_releasenotes_re = releasenotes_re.replace(r"firefox", "firefox/ios")
sysreq_re = latest_re % (version_re, "system-requirements")
android_sysreq_re = sysreq_re.replace(r"firefox", "firefox/android")
ios_sysreq_re = sysreq_re.replace(r"firefox", "firefox/ios")
urlpatterns = (
url(r'^firefox/$', views.FirefoxHomeView.as_view(), name='firefox'),
url(r'^firefox/all/$', views.firefox_all, name='firefox.all'),
page('firefox/accounts', 'firefox/accounts.html', ftl_files=['firefox/accounts']),
page('firefox/browsers', 'firefox/browsers/index.html', ftl_files=['firefox/browsers']),
page('firefox/products', 'firefox/products/index.html', ftl_files=['firefox/products']),
page('firefox/flashback', 'firefox/flashback/index.html', active_locales=['en-US', 'de', 'fr']),
page('firefox/channel/desktop', 'firefox/channel/desktop.html', ftl_files=['firefox/channel']),
page('firefox/channel/android', 'firefox/channel/android.html', ftl_files=['firefox/channel']),
page('firefox/channel/ios', 'firefox/channel/ios.html', ftl_files=['firefox/channel']),
page('firefox/developer', 'firefox/developer/index.html', ftl_files=['firefox/developer']),
page('firefox/enterprise', 'firefox/enterprise/index.html', ftl_files=['firefox/enterprise']),
page('firefox/facebookcontainer', 'firefox/facebookcontainer/index.html', ftl_files=['firefox/facebook_container']),
page('firefox/features', 'firefox/features/index.html',
ftl_files=['firefox/features/shared', 'firefox/features/index']),
page('firefox/features/adblocker', 'firefox/features/adblocker.html',
ftl_files=['firefox/features/adblocker']),
page('firefox/features/bookmarks', 'firefox/features/bookmarks.html',
ftl_files=['firefox/features/shared', 'firefox/features/bookmarks']),
page('firefox/features/fast', 'firefox/features/fast.html',
ftl_files=['firefox/features/shared', 'firefox/features/fast']),
page('firefox/features/block-fingerprinting', 'firefox/features/fingerprinting.html',
ftl_files=['firefox/features/shared', 'firefox/features/fingerprinting']),
page('firefox/features/independent', 'firefox/features/independent.html',
ftl_files=['firefox/features/shared', 'firefox/features/independent']),
page('firefox/features/memory', 'firefox/features/memory.html',
ftl_files=['firefox/features/shared', 'firefox/features/memory']),
page('firefox/features/password-manager', 'firefox/features/password-manager.html',
ftl_files=['firefox/features/shared', 'firefox/features/password-manager']),
page('firefox/features/private-browsing', 'firefox/features/private-browsing.html',
ftl_files=['firefox/features/shared', 'firefox/features/private-browsing']),
page('firefox/features/safebrowser', 'firefox/features/safebrowser.html'),
url(r'^firefox/features/translate/$', views.firefox_features_translate, name='firefox.features.translate'),
page('firefox/features/picture-in-picture', 'firefox/features/picture-in-picture.html',
ftl_files=['firefox/features/shared', 'firefox/features/picture-in-picture']),
url(r'^firefox/features/tips/$',
url(r"^firefox/$", views.FirefoxHomeView.as_view(), name="firefox"),
url(r"^firefox/all/$", views.firefox_all, name="firefox.all"),
page("firefox/accounts", "firefox/accounts.html", ftl_files=["firefox/accounts"]),
page("firefox/browsers", "firefox/browsers/index.html", ftl_files=["firefox/browsers"]),
page("firefox/products", "firefox/products/index.html", ftl_files=["firefox/products"]),
page("firefox/flashback", "firefox/flashback/index.html", active_locales=["en-US", "de", "fr"]),
page("firefox/channel/desktop", "firefox/channel/desktop.html", ftl_files=["firefox/channel"]),
page("firefox/channel/android", "firefox/channel/android.html", ftl_files=["firefox/channel"]),
page("firefox/channel/ios", "firefox/channel/ios.html", ftl_files=["firefox/channel"]),
page("firefox/developer", "firefox/developer/index.html", ftl_files=["firefox/developer"]),
page("firefox/enterprise", "firefox/enterprise/index.html", ftl_files=["firefox/enterprise"]),
page("firefox/facebookcontainer", "firefox/facebookcontainer/index.html", ftl_files=["firefox/facebook_container"]),
page("firefox/features", "firefox/features/index.html", ftl_files=["firefox/features/shared", "firefox/features/index"]),
page("firefox/features/adblocker", "firefox/features/adblocker.html", ftl_files=["firefox/features/adblocker"]),
page("firefox/features/bookmarks", "firefox/features/bookmarks.html", ftl_files=["firefox/features/shared", "firefox/features/bookmarks"]),
page("firefox/features/fast", "firefox/features/fast.html", ftl_files=["firefox/features/shared", "firefox/features/fast"]),
page(
"firefox/features/block-fingerprinting",
"firefox/features/fingerprinting.html",
ftl_files=["firefox/features/shared", "firefox/features/fingerprinting"],
),
page("firefox/features/independent", "firefox/features/independent.html", ftl_files=["firefox/features/shared", "firefox/features/independent"]),
page("firefox/features/memory", "firefox/features/memory.html", ftl_files=["firefox/features/shared", "firefox/features/memory"]),
page(
"firefox/features/password-manager",
"firefox/features/password-manager.html",
ftl_files=["firefox/features/shared", "firefox/features/password-manager"],
),
page(
"firefox/features/private-browsing",
"firefox/features/private-browsing.html",
ftl_files=["firefox/features/shared", "firefox/features/private-browsing"],
),
page("firefox/features/safebrowser", "firefox/features/safebrowser.html"),
url(r"^firefox/features/translate/$", views.firefox_features_translate, name="firefox.features.translate"),
page(
"firefox/features/picture-in-picture",
"firefox/features/picture-in-picture.html",
ftl_files=["firefox/features/shared", "firefox/features/picture-in-picture"],
),
url(
r"^firefox/features/tips/$",
VariationTemplateView.as_view(
template_name='firefox/features/tips/tips.html',
template_context_variations=['picture-in-picture', 'eyedropper', 'forget'],
), name='firefox.features.tips'),
url(r'^firefox/ios/testflight/$', views.ios_testflight, name='firefox.ios.testflight'),
page('firefox/mobile/get-app', 'firefox/mobile/get-app.html', ftl_files=['firefox/mobile']),
url('^firefox/send-to-device-post/$', views.send_to_device_ajax,
name='firefox.send-to-device-post'),
page('firefox/unsupported-systems', 'firefox/unsupported-systems.html'),
url(r'^firefox/new/$', views.NewView.as_view(), name='firefox.new'),
url(r'^firefox/download/thanks/$', views.DownloadThanksView.as_view(), name='firefox.download.thanks'),
page('firefox/nightly/firstrun', 'firefox/nightly/firstrun.html', ftl_files=['firefox/nightly/firstrun']),
url(r'^firefox/installer-help/$', views.InstallerHelpView.as_view(), name='firefox.installer-help'),
url(firstrun_re, views.FirstrunView.as_view(), name='firefox.firstrun'),
url(whatsnew_re, views.WhatsNewRedirectorView.as_view(), name='firefox.whatsnew'),
url(whatsnew_re_china, views.WhatsNewChinaView.as_view(), name='firefox.whatsnew.china'),
url(whatsnew_re_en, views.WhatsNewEnglishView.as_view(), name='firefox.whatsnew.en'),
url(whatsnew_re_france, views.WhatsNewFranceView.as_view(), name='firefox.whatsnew.france'),
url(whatsnew_re_all, views.WhatsnewView.as_view(), name='firefox.whatsnew.all'),
template_name="firefox/features/tips/tips.html",
template_context_variations=["picture-in-picture", "eyedropper", "forget"],
),
name="firefox.features.tips",
),
url(r"^firefox/ios/testflight/$", views.ios_testflight, name="firefox.ios.testflight"),
page("firefox/mobile/get-app", "firefox/mobile/get-app.html", ftl_files=["firefox/mobile"]),
url("^firefox/send-to-device-post/$", views.send_to_device_ajax, name="firefox.send-to-device-post"),
page("firefox/unsupported-systems", "firefox/unsupported-systems.html"),
url(r"^firefox/new/$", views.NewView.as_view(), name="firefox.new"),
url(r"^firefox/download/thanks/$", views.DownloadThanksView.as_view(), name="firefox.download.thanks"),
page("firefox/nightly/firstrun", "firefox/nightly/firstrun.html", ftl_files=["firefox/nightly/firstrun"]),
url(r"^firefox/installer-help/$", views.InstallerHelpView.as_view(), name="firefox.installer-help"),
url(firstrun_re, views.FirstrunView.as_view(), name="firefox.firstrun"),
url(whatsnew_re, views.WhatsNewRedirectorView.as_view(), name="firefox.whatsnew"),
url(whatsnew_re_china, views.WhatsNewChinaView.as_view(), name="firefox.whatsnew.china"),
url(whatsnew_re_en, views.WhatsNewEnglishView.as_view(), name="firefox.whatsnew.en"),
url(whatsnew_re_france, views.WhatsNewFranceView.as_view(), name="firefox.whatsnew.france"),
url(whatsnew_re_all, views.WhatsnewView.as_view(), name="firefox.whatsnew.all"),
# Release notes
url('^firefox/(?:%s/)?(?:%s/)?notes/$' % (platform_re, channel_re),
bedrock.releasenotes.views.latest_notes, name='firefox.notes'),
url('^firefox/nightly/notes/feed/$',
bedrock.releasenotes.views.nightly_feed, name='firefox.nightly.notes.feed'),
url('firefox/(?:latest/)?releasenotes/$', bedrock.releasenotes.views.latest_notes,
{'product': 'firefox'}),
url('^firefox/(?:%s/)?(?:%s/)?system-requirements/$' % (platform_re, channel_re),
url("^firefox/(?:%s/)?(?:%s/)?notes/$" % (platform_re, channel_re), bedrock.releasenotes.views.latest_notes, name="firefox.notes"),
url("^firefox/nightly/notes/feed/$", bedrock.releasenotes.views.nightly_feed, name="firefox.nightly.notes.feed"),
url("firefox/(?:latest/)?releasenotes/$", bedrock.releasenotes.views.latest_notes, {"product": "firefox"}),
url(
"^firefox/(?:%s/)?(?:%s/)?system-requirements/$" % (platform_re, channel_re),
bedrock.releasenotes.views.latest_sysreq,
{'product': 'firefox'}, name='firefox.sysreq'),
url(releasenotes_re, bedrock.releasenotes.views.release_notes, name='firefox.desktop.releasenotes'),
url(android_releasenotes_re, bedrock.releasenotes.views.release_notes,
{'product': 'Firefox for Android'}, name='firefox.android.releasenotes'),
url(ios_releasenotes_re, bedrock.releasenotes.views.release_notes,
{'product': 'Firefox for iOS'}, name='firefox.ios.releasenotes'),
url(sysreq_re, bedrock.releasenotes.views.system_requirements,
name='firefox.system_requirements'),
url(android_sysreq_re, bedrock.releasenotes.views.system_requirements,
{'product': 'Firefox for Android'}, name='firefox.android.system_requirements'),
url(ios_sysreq_re, bedrock.releasenotes.views.system_requirements,
{'product': 'Firefox for iOS'}, name='firefox.ios.system_requirements'),
url('^firefox/releases/$', bedrock.releasenotes.views.releases_index,
{'product': 'Firefox'}, name='firefox.releases.index'),
url('^firefox/stub_attribution_code/$', views.stub_attribution_code,
name='firefox.stub_attribution_code'),
url(r'^firefox/welcome/1/$', views.firefox_welcome_page1, name='firefox.welcome.page1'),
page('firefox/welcome/2', 'firefox/welcome/page2.html', ftl_files=['firefox/welcome/page2']),
page('firefox/welcome/3', 'firefox/welcome/page3.html', ftl_files=['firefox/welcome/page3']),
page('firefox/welcome/4', 'firefox/welcome/page4.html', ftl_files=['firefox/welcome/page4']),
page('firefox/welcome/5', 'firefox/welcome/page5.html', ftl_files=['firefox/welcome/page5']),
page('firefox/welcome/6', 'firefox/welcome/page6.html', ftl_files=['firefox/welcome/page6']),
page('firefox/welcome/7', 'firefox/welcome/page7.html', ftl_files=['firefox/welcome/page7']),
url(r'^firefox/welcome/8/$',
{"product": "firefox"},
name="firefox.sysreq",
),
url(releasenotes_re, bedrock.releasenotes.views.release_notes, name="firefox.desktop.releasenotes"),
url(android_releasenotes_re, bedrock.releasenotes.views.release_notes, {"product": "Firefox for Android"}, name="firefox.android.releasenotes"),
url(ios_releasenotes_re, bedrock.releasenotes.views.release_notes, {"product": "Firefox for iOS"}, name="firefox.ios.releasenotes"),
url(sysreq_re, bedrock.releasenotes.views.system_requirements, name="firefox.system_requirements"),
url(
android_sysreq_re,
bedrock.releasenotes.views.system_requirements,
{"product": "Firefox for Android"},
name="firefox.android.system_requirements",
),
url(ios_sysreq_re, bedrock.releasenotes.views.system_requirements, {"product": "Firefox for iOS"}, name="firefox.ios.system_requirements"),
url("^firefox/releases/$", bedrock.releasenotes.views.releases_index, {"product": "Firefox"}, name="firefox.releases.index"),
url("^firefox/stub_attribution_code/$", views.stub_attribution_code, name="firefox.stub_attribution_code"),
url(r"^firefox/welcome/1/$", views.firefox_welcome_page1, name="firefox.welcome.page1"),
page("firefox/welcome/2", "firefox/welcome/page2.html", ftl_files=["firefox/welcome/page2"]),
page("firefox/welcome/3", "firefox/welcome/page3.html", ftl_files=["firefox/welcome/page3"]),
page("firefox/welcome/4", "firefox/welcome/page4.html", ftl_files=["firefox/welcome/page4"]),
page("firefox/welcome/5", "firefox/welcome/page5.html", ftl_files=["firefox/welcome/page5"]),
page("firefox/welcome/6", "firefox/welcome/page6.html", ftl_files=["firefox/welcome/page6"]),
page("firefox/welcome/7", "firefox/welcome/page7.html", ftl_files=["firefox/welcome/page7"]),
url(
r"^firefox/welcome/8/$",
utils_views.VariationTemplateView.as_view(
template_name='firefox/welcome/page8.html',
ftl_files=['firefox/welcome/page8'],
template_context_variations=['text', 'image', 'animation', 'header-text'],
variation_locales=['en-US', 'en-CA', 'en-GB', 'de', 'fr']),
name='firefox.welcome.page8'),
page('firefox/welcome/9', 'firefox/welcome/page9.html', active_locales=['de', 'fr']),
page('firefox/welcome/10', 'firefox/welcome/page10.html'),
page('firefox/switch', 'firefox/switch.html', ftl_files=['firefox/switch']),
page('firefox/pocket', 'firefox/pocket.html'),
template_name="firefox/welcome/page8.html",
ftl_files=["firefox/welcome/page8"],
template_context_variations=["text", "image", "animation", "header-text"],
variation_locales=["en-US", "en-CA", "en-GB", "de", "fr"],
),
name="firefox.welcome.page8",
),
page("firefox/welcome/9", "firefox/welcome/page9.html", active_locales=["de", "fr"]),
page("firefox/welcome/10", "firefox/welcome/page10.html"),
page("firefox/switch", "firefox/switch.html", ftl_files=["firefox/switch"]),
page("firefox/pocket", "firefox/pocket.html"),
# Issue 6604, SEO firefox/new pages
url('firefox/linux', views.PlatformViewLinux.as_view(), name='firefox.linux'),
url('firefox/mac', views.PlatformViewMac.as_view(), name='firefox.mac'),
url('firefox/windows', views.PlatformViewWindows.as_view(), name='firefox.windows'),
page('firefox/browsers/compare', 'firefox/browsers/compare/index.html',
ftl_files=['firefox/browsers/compare/index', 'firefox/browsers/compare/shared']),
page('firefox/browsers/compare/brave', 'firefox/browsers/compare/brave.html',
ftl_files=['firefox/browsers/compare/brave', 'firefox/browsers/compare/shared']),
page('firefox/browsers/compare/chrome', 'firefox/browsers/compare/chrome.html',
ftl_files=['firefox/browsers/compare/chrome', 'firefox/browsers/compare/shared']),
page('firefox/browsers/compare/edge', 'firefox/browsers/compare/edge.html',
ftl_files=['firefox/browsers/compare/edge', 'firefox/browsers/compare/shared']),
page('firefox/browsers/compare/ie', 'firefox/browsers/compare/ie.html',
ftl_files=['firefox/browsers/compare/ie', 'firefox/browsers/compare/shared']),
page('firefox/browsers/compare/opera', 'firefox/browsers/compare/opera.html',
ftl_files=['firefox/browsers/compare/opera', 'firefox/browsers/compare/shared']),
page('firefox/browsers/compare/safari', 'firefox/browsers/compare/safari.html',
ftl_files=['firefox/browsers/compare/safari', 'firefox/browsers/compare/shared']),
url("firefox/linux", views.PlatformViewLinux.as_view(), name="firefox.linux"),
url("firefox/mac", views.PlatformViewMac.as_view(), name="firefox.mac"),
url("firefox/windows", views.PlatformViewWindows.as_view(), name="firefox.windows"),
page(
"firefox/browsers/compare",
"firefox/browsers/compare/index.html",
ftl_files=["firefox/browsers/compare/index", "firefox/browsers/compare/shared"],
),
page(
"firefox/browsers/compare/brave",
"firefox/browsers/compare/brave.html",
ftl_files=["firefox/browsers/compare/brave", "firefox/browsers/compare/shared"],
),
page(
"firefox/browsers/compare/chrome",
"firefox/browsers/compare/chrome.html",
ftl_files=["firefox/browsers/compare/chrome", "firefox/browsers/compare/shared"],
),
page(
"firefox/browsers/compare/edge",
"firefox/browsers/compare/edge.html",
ftl_files=["firefox/browsers/compare/edge", "firefox/browsers/compare/shared"],
),
page(
"firefox/browsers/compare/ie",
"firefox/browsers/compare/ie.html",
ftl_files=["firefox/browsers/compare/ie", "firefox/browsers/compare/shared"],
),
page(
"firefox/browsers/compare/opera",
"firefox/browsers/compare/opera.html",
ftl_files=["firefox/browsers/compare/opera", "firefox/browsers/compare/shared"],
),
page(
"firefox/browsers/compare/safari",
"firefox/browsers/compare/safari.html",
ftl_files=["firefox/browsers/compare/safari", "firefox/browsers/compare/shared"],
),
# Issue 10182
url(r'^firefox/browsers/mobile/$', views.FirefoxMobileView.as_view(), name='firefox.browsers.mobile.index'),
page('firefox/browsers/mobile/android', 'firefox/browsers/mobile/android.html',
ftl_files=['firefox/browsers/mobile/android']),
page('firefox/browsers/mobile/ios', 'firefox/browsers/mobile/ios.html',
ftl_files=['firefox/browsers/mobile/ios']),
page('firefox/browsers/mobile/focus', 'firefox/browsers/mobile/focus.html',
ftl_files=['firefox/browsers/mobile/focus']),
page('firefox/browsers/mobile/compare', 'firefox/browsers/mobile/compare.html',
ftl_files=['firefox/browsers/mobile/compare', 'firefox/browsers/compare/shared']),
url(r"^firefox/browsers/mobile/$", views.FirefoxMobileView.as_view(), name="firefox.browsers.mobile.index"),
page("firefox/browsers/mobile/android", "firefox/browsers/mobile/android.html", ftl_files=["firefox/browsers/mobile/android"]),
page("firefox/browsers/mobile/ios", "firefox/browsers/mobile/ios.html", ftl_files=["firefox/browsers/mobile/ios"]),
page("firefox/browsers/mobile/focus", "firefox/browsers/mobile/focus.html", ftl_files=["firefox/browsers/mobile/focus"]),
page(
"firefox/browsers/mobile/compare",
"firefox/browsers/mobile/compare.html",
ftl_files=["firefox/browsers/mobile/compare", "firefox/browsers/compare/shared"],
),
# Issue 8641
page('firefox/browsers/best-browser', 'firefox/browsers/best-browser.html', ftl_files=['firefox/browsers/best-browser']),
page('firefox/browsers/browser-history', 'firefox/browsers/browser-history.html', ftl_files=['firefox/browsers/history/browser-history']),
page('firefox/browsers/incognito-browser', 'firefox/browsers/incognito-browser.html'),
page('firefox/browsers/update-your-browser', 'firefox/browsers/update-browser.html'),
page('firefox/browsers/what-is-a-browser', 'firefox/browsers/what-is-a-browser.html', ftl_files=['firefox/browsers/history/what-is-a-browser']),
page('firefox/browsers/windows-64-bit', 'firefox/browsers/windows-64-bit.html', ftl_files=['firefox/browsers/windows-64-bit']),
page("firefox/browsers/best-browser", "firefox/browsers/best-browser.html", ftl_files=["firefox/browsers/best-browser"]),
page("firefox/browsers/browser-history", "firefox/browsers/browser-history.html", ftl_files=["firefox/browsers/history/browser-history"]),
page("firefox/browsers/incognito-browser", "firefox/browsers/incognito-browser.html"),
page("firefox/browsers/update-your-browser", "firefox/browsers/update-browser.html"),
page("firefox/browsers/what-is-a-browser", "firefox/browsers/what-is-a-browser.html", ftl_files=["firefox/browsers/history/what-is-a-browser"]),
page("firefox/browsers/windows-64-bit", "firefox/browsers/windows-64-bit.html", ftl_files=["firefox/browsers/windows-64-bit"]),
# Lockwise
page('firefox/lockwise', 'firefox/products/lockwise.html', ftl_files=['firefox/products/lockwise']),
page("firefox/lockwise", "firefox/products/lockwise.html", ftl_files=["firefox/products/lockwise"]),
# Issue 7765, 7709
page('firefox/privacy', 'firefox/privacy/index.html', ftl_files=['firefox/privacy-hub']),
page('firefox/privacy/products', 'firefox/privacy/products.html', ftl_files=['firefox/privacy-hub']),
page('firefox/privacy/safe-passwords', 'firefox/privacy/passwords.html', ftl_files=['firefox/privacy-hub', 'firefox/privacy/passwords']),
page("firefox/privacy", "firefox/privacy/index.html", ftl_files=["firefox/privacy-hub"]),
page("firefox/privacy/products", "firefox/privacy/products.html", ftl_files=["firefox/privacy-hub"]),
page("firefox/privacy/safe-passwords", "firefox/privacy/passwords.html", ftl_files=["firefox/privacy-hub", "firefox/privacy/passwords"]),
# Issue 8432
page('firefox/set-as-default/thanks', 'firefox/set-as-default/thanks.html', ftl_files='firefox/set-as-default/thanks'),
page("firefox/set-as-default/thanks", "firefox/set-as-default/thanks.html", ftl_files="firefox/set-as-default/thanks"),
# Default browser campaign
page('firefox/set-as-default', 'firefox/set-as-default/landing.html', ftl_files='firefox/set-as-default/landing'),
page("firefox/set-as-default", "firefox/set-as-default/landing.html", ftl_files="firefox/set-as-default/landing"),
# Issue 8536
page('firefox/retention/thank-you', 'firefox/retention/thank-you.html', ftl_files='firefox/retention/thank-you'),
page("firefox/retention/thank-you", "firefox/retention/thank-you.html", ftl_files="firefox/retention/thank-you"),
# Unfck campaign
page('firefox/unfck', 'firefox/campaign/unfck/index.html', active_locales=['de', 'en-US', 'fr']),
page("firefox/unfck", "firefox/campaign/unfck/index.html", active_locales=["de", "en-US", "fr"]),
# Issue #9490 - Evergreen Content for SEO
page('firefox/more', 'firefox/more.html', ftl_files='firefox/more'),
page('firefox/browsers/quantum', 'firefox/browsers/quantum.html', ftl_files='firefox/browsers/quantum'),
page('firefox/faq', 'firefox/faq.html', ftl_files='firefox/faq'),
page('firefox/browsers/chromebook', 'firefox/browsers/chromebook.html', ftl_files='firefox/browsers/chromebook'),
page('firefox/sync', 'firefox/sync.html', ftl_files='firefox/sync'),
page('firefox/privacy/book', 'firefox/privacy/book.html', ftl_files='firefox/privacy/book'),
page("firefox/more", "firefox/more.html", ftl_files="firefox/more"),
page("firefox/browsers/quantum", "firefox/browsers/quantum.html", ftl_files="firefox/browsers/quantum"),
page("firefox/faq", "firefox/faq.html", ftl_files="firefox/faq"),
page("firefox/browsers/chromebook", "firefox/browsers/chromebook.html", ftl_files="firefox/browsers/chromebook"),
page("firefox/sync", "firefox/sync.html", ftl_files="firefox/sync"),
page("firefox/privacy/book", "firefox/privacy/book.html", ftl_files="firefox/privacy/book"),
# Issue 9957
page('firefox/more/misinformation', 'firefox/more/misinformation.html', ftl_files='firefox/more/misinformation'),
page("firefox/more/misinformation", "firefox/more/misinformation.html", ftl_files="firefox/more/misinformation"),
)
# Contentful
if settings.DEV:
urlpatterns += (
path('firefox/more/<content_id>/', views.FirefoxContenful.as_view()),
)
urlpatterns += (path("firefox/more/<content_id>/", views.FirefoxContenful.as_view()),)

Просмотреть файл

@ -11,8 +11,7 @@ def is_current_or_newer(user_version):
"""
Return true if the version (X.Y only) is for the latest Firefox or newer.
"""
latest = Version(product_details.firefox_versions[
'LATEST_FIREFOX_VERSION'])
latest = Version(product_details.firefox_versions["LATEST_FIREFOX_VERSION"])
user = Version(user_version)
# check for ESR
@ -21,6 +20,6 @@ def is_current_or_newer(user_version):
# similar to the way comparison is done in the Version class,
# but only using the major and minor versions.
latest_int = int('%d%02d' % (latest.major, latest.minor1))
user_int = int('%d%02d' % (user.major or 0, user.minor1 or 0))
latest_int = int("%d%02d" % (latest.major, latest.minor1))
user_int = int("%d%02d" % (user.major or 0, user.minor1 or 0))
return user_int >= latest_int

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,17 +1,15 @@
from bedrock.redirects.util import redirect
redirectpatterns = (
redirect(r'^foundation/?$', 'https://foundation.mozilla.org/'),
redirect(r'^foundation/about/?$', 'https://foundation.mozilla.org/about/'),
redirect(r'^foundation/documents/?$', 'https://foundation.mozilla.org/about/public-records/'),
redirect(r'^foundation/issues/?$', 'https://foundation.mozilla.org/initiatives/'),
redirect(r'^foundation/leadership-network/?$', 'https://foundation.mozilla.org/'),
redirect(r'^foundation/advocacy/?$', 'https://foundation.mozilla.org/'),
redirect(r'^foundation/trademarks/?$', '/foundation/trademarks/policy/'),
redirect(r'^foundation/trademarks/faq/?$', '/foundation/trademarks/policy/'),
redirect(r'^foundation/documents/domain-name-license\.pdf$', '/foundation/trademarks/policy/'),
redirect(r'^foundation/trademarks/poweredby/faq/?$', '/foundation/trademarks/policy/'),
redirect(r'^foundation/trademarks/l10n-website-policy/?$', '/foundation/trademarks/policy/'),
redirect(r"^foundation/?$", "https://foundation.mozilla.org/"),
redirect(r"^foundation/about/?$", "https://foundation.mozilla.org/about/"),
redirect(r"^foundation/documents/?$", "https://foundation.mozilla.org/about/public-records/"),
redirect(r"^foundation/issues/?$", "https://foundation.mozilla.org/initiatives/"),
redirect(r"^foundation/leadership-network/?$", "https://foundation.mozilla.org/"),
redirect(r"^foundation/advocacy/?$", "https://foundation.mozilla.org/"),
redirect(r"^foundation/trademarks/?$", "/foundation/trademarks/policy/"),
redirect(r"^foundation/trademarks/faq/?$", "/foundation/trademarks/policy/"),
redirect(r"^foundation/documents/domain-name-license\.pdf$", "/foundation/trademarks/policy/"),
redirect(r"^foundation/trademarks/poweredby/faq/?$", "/foundation/trademarks/policy/"),
redirect(r"^foundation/trademarks/l10n-website-policy/?$", "/foundation/trademarks/policy/"),
)

Просмотреть файл

@ -6,84 +6,63 @@ from bedrock.mozorg.util import page
from bedrock.redirects.util import redirect
urlpatterns = (
# Issue 9727 /foundation/annualreport/2019/
redirect(r'^annualreport/$', 'foundation.annualreport.2019.index',
name='foundation.annualreport', locale_prefix=False),
redirect(r"^annualreport/$", "foundation.annualreport.2019.index", name="foundation.annualreport", locale_prefix=False),
# Older annual report financial faqs - these are linked from blog posts
# was e.g.: http://www.mozilla.org/foundation/documents/mozilla-2008-financial-faq.html
page('documents/mozilla-2006-financial-faq', 'foundation/documents/mozilla-2006-financial-faq.html'),
page('documents/mozilla-2007-financial-faq', 'foundation/documents/mozilla-2007-financial-faq.html'),
page('documents/mozilla-2008-financial-faq', 'foundation/documents/mozilla-2008-financial-faq.html'),
page("documents/mozilla-2006-financial-faq", "foundation/documents/mozilla-2006-financial-faq.html"),
page("documents/mozilla-2007-financial-faq", "foundation/documents/mozilla-2007-financial-faq.html"),
page("documents/mozilla-2008-financial-faq", "foundation/documents/mozilla-2008-financial-faq.html"),
# ported from PHP in Bug 960689
page('documents/bylaws-amendment-1', 'foundation/documents/bylaws-amendment-1.html'),
page('documents/bylaws-amendment-2', 'foundation/documents/bylaws-amendment-2.html'),
page('documents/articles-of-incorporation', 'foundation/documents/articles-of-incorporation.html'),
page('documents/articles-of-incorporation/amendment', 'foundation/documents/articles-of-incorporation-amendment.html'),
page('documents/bylaws', 'foundation/documents/bylaws.html'),
page("documents/bylaws-amendment-1", "foundation/documents/bylaws-amendment-1.html"),
page("documents/bylaws-amendment-2", "foundation/documents/bylaws-amendment-2.html"),
page("documents/articles-of-incorporation", "foundation/documents/articles-of-incorporation.html"),
page("documents/articles-of-incorporation/amendment", "foundation/documents/articles-of-incorporation-amendment.html"),
page("documents/bylaws", "foundation/documents/bylaws.html"),
# was https://www.mozilla.org/foundation/annualreport/2009/
page('annualreport/2009', 'foundation/annualreport/2009/index.html'),
page("annualreport/2009", "foundation/annualreport/2009/index.html"),
# was .html
page('annualreport/2009/a-competitive-world', 'foundation/annualreport/2009/a-competitive-world.html'),
page("annualreport/2009/a-competitive-world", "foundation/annualreport/2009/a-competitive-world.html"),
# was .html
page('annualreport/2009/broadening-our-scope', 'foundation/annualreport/2009/broadening-our-scope.html'),
page("annualreport/2009/broadening-our-scope", "foundation/annualreport/2009/broadening-our-scope.html"),
# was .html
page('annualreport/2009/sustainability', 'foundation/annualreport/2009/sustainability.html'),
page("annualreport/2009/sustainability", "foundation/annualreport/2009/sustainability.html"),
# was https://www.mozilla.org/foundation/annualreport/2009/faq.html
# changing to https://www.mozilla.org/foundation/annualreport/2009/faq/
page('annualreport/2009/faq', 'foundation/annualreport/2009/faq.html'),
page('annualreport/2010', 'foundation/annualreport/2010/index.html'),
page('annualreport/2010/ahead', 'foundation/annualreport/2010/ahead.html'),
page('annualreport/2010/opportunities', 'foundation/annualreport/2010/opportunities.html'),
page('annualreport/2010/people', 'foundation/annualreport/2010/people.html'),
page('annualreport/2010/faq', 'foundation/annualreport/2010/faq.html'),
page('annualreport/2011', 'foundation/annualreport/2011.html'),
page('annualreport/2011/faq', 'foundation/annualreport/2011faq.html'),
page('annualreport/2012', 'foundation/annualreport/2012/index.html'),
page('annualreport/2012/faq', 'foundation/annualreport/2012/faq.html'),
page('annualreport/2013', 'foundation/annualreport/2013/index.html'),
page('annualreport/2013/faq', 'foundation/annualreport/2013/faq.html'),
page('annualreport/2014', 'foundation/annualreport/2014/index.html'),
page('annualreport/2014/faq', 'foundation/annualreport/2014/faq.html'),
page('annualreport/2015', 'foundation/annualreport/2015/index.html'),
page('annualreport/2015/faq', 'foundation/annualreport/2015/faq.html'),
page('annualreport/2016', 'foundation/annualreport/2016/index.html'),
page('annualreport/2017', 'foundation/annualreport/2017/index.html'),
page('annualreport/2018', 'foundation/annualreport/2018/index.html'),
page('annualreport/2019', 'foundation/annualreport/2019/index.html'),
page('feed-icon-guidelines', 'foundation/feed-icon-guidelines/index.html'),
page('feed-icon-guidelines/faq', 'foundation/feed-icon-guidelines/faq.html'),
page('licensing', 'foundation/licensing.html'),
page('licensing/website-content', 'foundation/licensing/website-content.html'),
page('licensing/website-markup', 'foundation/licensing/website-markup.html'),
page('licensing/binary-components', 'foundation/licensing/binary-components/index.html'),
page('licensing/binary-components/rationale', 'foundation/licensing/binary-components/rationale.html'),
page('moco', 'foundation/moco.html'),
page('openwebfund/more', 'foundation/openwebfund/more.html'),
page('openwebfund/thanks', 'foundation/openwebfund/thanks.html'),
page('trademarks/policy', 'foundation/trademarks/policy.html'),
page('trademarks/list', 'foundation/trademarks/list.html'),
page('trademarks/distribution-policy', 'foundation/trademarks/distribution-policy.html'),
page('trademarks/community-edition-permitted-changes', 'foundation/trademarks/community-edition-permitted-changes.html'),
page('trademarks/community-edition-policy', 'foundation/trademarks/community-edition-policy.html'),
page('reimagine-open', 'foundation/reimagine-open.html'),
page("annualreport/2009/faq", "foundation/annualreport/2009/faq.html"),
page("annualreport/2010", "foundation/annualreport/2010/index.html"),
page("annualreport/2010/ahead", "foundation/annualreport/2010/ahead.html"),
page("annualreport/2010/opportunities", "foundation/annualreport/2010/opportunities.html"),
page("annualreport/2010/people", "foundation/annualreport/2010/people.html"),
page("annualreport/2010/faq", "foundation/annualreport/2010/faq.html"),
page("annualreport/2011", "foundation/annualreport/2011.html"),
page("annualreport/2011/faq", "foundation/annualreport/2011faq.html"),
page("annualreport/2012", "foundation/annualreport/2012/index.html"),
page("annualreport/2012/faq", "foundation/annualreport/2012/faq.html"),
page("annualreport/2013", "foundation/annualreport/2013/index.html"),
page("annualreport/2013/faq", "foundation/annualreport/2013/faq.html"),
page("annualreport/2014", "foundation/annualreport/2014/index.html"),
page("annualreport/2014/faq", "foundation/annualreport/2014/faq.html"),
page("annualreport/2015", "foundation/annualreport/2015/index.html"),
page("annualreport/2015/faq", "foundation/annualreport/2015/faq.html"),
page("annualreport/2016", "foundation/annualreport/2016/index.html"),
page("annualreport/2017", "foundation/annualreport/2017/index.html"),
page("annualreport/2018", "foundation/annualreport/2018/index.html"),
page("annualreport/2019", "foundation/annualreport/2019/index.html"),
page("feed-icon-guidelines", "foundation/feed-icon-guidelines/index.html"),
page("feed-icon-guidelines/faq", "foundation/feed-icon-guidelines/faq.html"),
page("licensing", "foundation/licensing.html"),
page("licensing/website-content", "foundation/licensing/website-content.html"),
page("licensing/website-markup", "foundation/licensing/website-markup.html"),
page("licensing/binary-components", "foundation/licensing/binary-components/index.html"),
page("licensing/binary-components/rationale", "foundation/licensing/binary-components/rationale.html"),
page("moco", "foundation/moco.html"),
page("openwebfund/more", "foundation/openwebfund/more.html"),
page("openwebfund/thanks", "foundation/openwebfund/thanks.html"),
page("trademarks/policy", "foundation/trademarks/policy.html"),
page("trademarks/list", "foundation/trademarks/list.html"),
page("trademarks/distribution-policy", "foundation/trademarks/distribution-policy.html"),
page("trademarks/community-edition-permitted-changes", "foundation/trademarks/community-edition-permitted-changes.html"),
page("trademarks/community-edition-policy", "foundation/trademarks/community-edition-policy.html"),
page("reimagine-open", "foundation/reimagine-open.html"),
)

Просмотреть файл

@ -19,108 +19,72 @@ class FraudReportForm(forms.Form):
max_length=2000,
required=True,
error_messages={
'required': _lazy(u'Please enter a URL.'),
"required": _lazy("Please enter a URL."),
},
widget=forms.TextInput(
attrs={
'size': 40,
'placeholder': _lazy(u'http://offendingsite.com'),
'class': 'required fill-width',
'required': 'required',
'aria-required': 'true',
"size": 40,
"placeholder": _lazy("http://offendingsite.com"),
"class": "required fill-width",
"required": "required",
"aria-required": "true",
}
)
),
)
input_category = forms.ChoiceField(
choices=(
('Charging for software',
_lazy(u'Charging for software')),
('Collecting personal information',
_lazy(u'Collecting personal information')),
('Domain name violation',
_lazy(u'Domain name violation')),
('Logo misuse/modification',
_lazy(u'Logo misuse/modification')),
('Distributing modified Firefox/malware',
_lazy(u'Distributing modified Firefox/malware')),
("Charging for software", _lazy("Charging for software")),
("Collecting personal information", _lazy("Collecting personal information")),
("Domain name violation", _lazy("Domain name violation")),
("Logo misuse/modification", _lazy("Logo misuse/modification")),
("Distributing modified Firefox/malware", _lazy("Distributing modified Firefox/malware")),
),
required=True,
error_messages={
'required': _lazy('Please select a category.'),
"required": _lazy("Please select a category."),
},
widget=forms.Select(
attrs={
'title': _lazy(u'Category'),
'class': 'required',
'required': 'required',
'aria-required': 'true',
"title": _lazy("Category"),
"class": "required",
"required": "required",
"aria-required": "true",
}
)
),
)
input_product = forms.ChoiceField(
choices=(
('Firefox', _lazy(u'Firefox')),
('SeaMonkey', _lazy(u'SeaMonkey')),
('Thunderbird', _lazy(u'Thunderbird')),
('Other Mozilla Product/Project', _lazy(u'Other Mozilla Product/Project (specify)')),
("Firefox", _lazy("Firefox")),
("SeaMonkey", _lazy("SeaMonkey")),
("Thunderbird", _lazy("Thunderbird")),
("Other Mozilla Product/Project", _lazy("Other Mozilla Product/Project (specify)")),
),
required=True,
error_messages={
'required': _lazy('Please select a product.'),
"required": _lazy("Please select a product."),
},
widget=forms.Select(
attrs={
'title': _lazy(u'Product'),
'class': 'required',
'required': 'required',
'aria-required': 'true',
"title": _lazy("Product"),
"class": "required",
"required": "required",
"aria-required": "true",
}
)
)
input_specific_product = forms.CharField(
max_length=254,
required=False,
widget=forms.TextInput(
attrs={
'size': 20,
'class': 'fill-width'
}
)
)
input_details = forms.CharField(
required=False,
widget=forms.Textarea(
attrs={
'rows': '',
'cols': '',
'class': 'fill-width'
}
)
),
)
input_specific_product = forms.CharField(max_length=254, required=False, widget=forms.TextInput(attrs={"size": 20, "class": "fill-width"}))
input_details = forms.CharField(required=False, widget=forms.Textarea(attrs={"rows": "", "cols": "", "class": "fill-width"}))
input_attachment = forms.ImageField(required=False)
input_attachment_desc = forms.CharField(
max_length=254,
required=False,
widget=forms.Textarea(
attrs={
'rows': '',
'cols': '',
'class': 'fill-width'
}
)
max_length=254, required=False, widget=forms.Textarea(attrs={"rows": "", "cols": "", "class": "fill-width"})
)
input_email = forms.EmailField(
max_length=254,
required=False,
error_messages={
'invalid': _lazy(u'Please enter a valid email address'),
"invalid": _lazy("Please enter a valid email address"),
},
widget=forms.TextInput(
attrs={
'size': 20,
'class': 'fill-width'
}
)
widget=forms.TextInput(attrs={"size": 20, "class": "fill-width"}),
)
# honeypot
office_fax = forms.CharField(widget=HoneyPotWidget, required=False)
@ -130,14 +94,12 @@ class FraudReportForm(forms.Form):
if attachment:
if attachment.size > FRAUD_REPORT_FILE_SIZE_LIMIT:
raise forms.ValidationError(
_("Attachment must not exceed 5MB"))
raise forms.ValidationError(_("Attachment must not exceed 5MB"))
return attachment
def clean_office_fax(self):
honeypot = self.cleaned_data.pop('office_fax', None)
honeypot = self.cleaned_data.pop("office_fax", None)
if honeypot:
raise forms.ValidationError(
_('Your submission could not be processed'))
raise forms.ValidationError(_("Your submission could not be processed"))

Просмотреть файл

@ -3,11 +3,9 @@ from bedrock.redirects.util import redirect
redirectpatterns = (
# bug 1243240
redirect(r'^about/legal/report-abuse/?$', 'legal.report-infringement'),
redirect(r"^about/legal/report-abuse/?$", "legal.report-infringement"),
# bug 1321033
redirect(r'^about/legal/terms/firefox-hello', 'privacy.archive.hello-2014-11'),
redirect(r"^about/legal/terms/firefox-hello", "privacy.archive.hello-2014-11"),
# issue 5816, Issue 8418
redirect(r'^about/logo', 'https://mozilla.design/')
redirect(r"^about/logo", "https://mozilla.design/"),
)

Просмотреть файл

@ -21,17 +21,17 @@ from bedrock.mozorg.tests import TestCase
class TestFraudReport(TestCase):
def setUp(self):
self.factory = RequestFactory()
with self.activate('en-US'):
self.url = reverse('legal.fraud-report')
with self.activate("en-US"):
self.url = reverse("legal.fraud-report")
self.data = {
'input_url': 'http://www.test.com/',
'input_category': 'Charging for software',
'input_product': 'Firefox',
'input_specific_product': '',
'input_details': 'test details',
'input_attachment_desc': 'test attachment',
'input_email': 'foo@bar.com',
"input_url": "http://www.test.com/",
"input_category": "Charging for software",
"input_product": "Firefox",
"input_specific_product": "",
"input_details": "test details",
"input_attachment_desc": "test attachment",
"input_email": "foo@bar.com",
}
def tearDown(self):
@ -39,13 +39,13 @@ class TestFraudReport(TestCase):
def _create_image_file(self):
io = BytesIO()
image = Image.new('RGB', (200, 200), 'white')
image.save(io, 'PNG')
image = Image.new("RGB", (200, 200), "white")
image.save(io, "PNG")
io.seek(0)
return SimpleUploadedFile('image.png', io.read(), 'image/png')
return SimpleUploadedFile("image.png", io.read(), "image/png")
def _create_text_file(self):
return SimpleUploadedFile('stuff.txt', b'This is not an image', 'text/plain')
return SimpleUploadedFile("stuff.txt", b"This is not an image", "text/plain")
def test_view_post_valid_data(self):
"""
@ -60,7 +60,7 @@ class TestFraudReport(TestCase):
response = legal_views.fraud_report(request)
assert response.status_code == 302
assert response['Location'] == '/en-US/about/legal/defend-mozilla-trademarks/?submitted=True'
assert response["Location"] == "/en-US/about/legal/defend-mozilla-trademarks/?submitted=True"
def test_view_post_missing_data(self):
"""
@ -68,7 +68,7 @@ class TestFraudReport(TestCase):
errors in the template.
"""
self.data.update(input_url='') # remove required url
self.data.update(input_url="") # remove required url
request = self.factory.post(self.url, self.data)
@ -78,7 +78,7 @@ class TestFraudReport(TestCase):
response = legal_views.fraud_report(request)
assert response.status_code == 200
self.assertIn(b'Please enter a URL.', response.content)
self.assertIn(b"Please enter a URL.", response.content)
def test_view_post_honeypot(self):
"""
@ -86,7 +86,7 @@ class TestFraudReport(TestCase):
contain general form error message.
"""
self.data['office_fax'] = 'spammer'
self.data["office_fax"] = "spammer"
request = self.factory.post(self.url, self.data)
@ -96,7 +96,7 @@ class TestFraudReport(TestCase):
response = legal_views.fraud_report(request)
assert response.status_code == 200
self.assertIn(b'An error has occurred', response.content)
self.assertIn(b"An error has occurred", response.content)
def test_form_valid_data(self):
"""
@ -112,7 +112,7 @@ class TestFraudReport(TestCase):
With incorrect data (missing url), form should not be valid and should
have url in the errors hash.
"""
self.data.update(input_url='') # remove required url
self.data.update(input_url="") # remove required url
form = FraudReportForm(self.data)
@ -120,13 +120,13 @@ class TestFraudReport(TestCase):
assert not form.is_valid()
# make sure url errors are in form
self.assertIn('input_url', form.errors)
self.assertIn("input_url", form.errors)
def test_form_honeypot(self):
"""
Form with honeypot text box filled should not be valid.
"""
self.data['office_fax'] = 'spammer!'
self.data["office_fax"] = "spammer!"
form = FraudReportForm(self.data)
@ -138,7 +138,7 @@ class TestFraudReport(TestCase):
"""
# attachment within size limit
mock_attachment = self._create_image_file()
form = FraudReportForm(self.data, {'input_attachment': mock_attachment})
form = FraudReportForm(self.data, {"input_attachment": mock_attachment})
# make sure form is valid
assert form.is_valid()
@ -149,11 +149,11 @@ class TestFraudReport(TestCase):
"""
# attachment within size limit
mock_attachment = self._create_text_file()
form = FraudReportForm(self.data, {'input_attachment': mock_attachment})
form = FraudReportForm(self.data, {"input_attachment": mock_attachment})
# make sure form is not valid
assert not form.is_valid()
# make sure attachment errors are in form
self.assertIn('input_attachment', form.errors)
self.assertIn("input_attachment", form.errors)
def test_form_invalid_attachement_size(self):
"""
@ -162,16 +162,16 @@ class TestFraudReport(TestCase):
"""
# attachment within size limit
mock_attachment = self._create_image_file()
form = FraudReportForm(self.data, {'input_attachment': mock_attachment})
with patch.object(legal_forms, 'FRAUD_REPORT_FILE_SIZE_LIMIT', 100):
form = FraudReportForm(self.data, {"input_attachment": mock_attachment})
with patch.object(legal_forms, "FRAUD_REPORT_FILE_SIZE_LIMIT", 100):
# make sure form is not valid
assert not form.is_valid()
# make sure attachment errors are in form
self.assertIn('input_attachment', form.errors)
self.assertIn("input_attachment", form.errors)
@patch('bedrock.legal.views.render_to_string', return_value='rendered')
@patch('bedrock.legal.views.EmailMessage')
@patch("bedrock.legal.views.render_to_string", return_value="rendered")
@patch("bedrock.legal.views.EmailMessage")
def test_email(self, mock_email_message, mock_render_to_string):
"""
Make sure email is sent with expected values.
@ -182,7 +182,7 @@ class TestFraudReport(TestCase):
form = FraudReportForm(self.data)
# submit form
request = self.factory.get('/')
request = self.factory.get("/")
submit_form(request, form)
# make sure email was sent
@ -190,44 +190,41 @@ class TestFraudReport(TestCase):
# make sure email values are correct
mock_email_message.assert_called_once_with(
legal_views.FRAUD_REPORT_EMAIL_SUBJECT % (self.data['input_url'],
self.data['input_category']),
'rendered',
legal_views.FRAUD_REPORT_EMAIL_SUBJECT % (self.data["input_url"], self.data["input_category"]),
"rendered",
legal_views.FRAUD_REPORT_EMAIL_FROM,
legal_views.FRAUD_REPORT_EMAIL_TO)
legal_views.FRAUD_REPORT_EMAIL_TO,
)
@patch('bedrock.legal.views.render_to_string', return_value='rendered')
@patch('bedrock.legal.views.EmailMessage')
@patch("bedrock.legal.views.render_to_string", return_value="rendered")
@patch("bedrock.legal.views.EmailMessage")
def test_email_with_attachement(self, mock_email_message, mock_render_to_string):
"""
Make sure email is sent with attachment.
"""
mock_attachment = self._create_image_file()
form = FraudReportForm(self.data, {'input_attachment': mock_attachment})
form = FraudReportForm(self.data, {"input_attachment": mock_attachment})
# submit form
request = self.factory.get('/')
request = self.factory.get("/")
ret = submit_form(request, form)
self.assertFalse(ret['form_error'])
self.assertFalse(ret["form_error"])
# make sure attachment was attached
mock_attachment.seek(0)
mock_email_message.return_value.attach.assert_called_once_with(
mock_attachment.name,
mock_attachment.read(),
mock_attachment.content_type)
mock_email_message.return_value.attach.assert_called_once_with(mock_attachment.name, mock_attachment.read(), mock_attachment.content_type)
# make sure email was sent
mock_email_message.return_value.send.assert_called_once()
# make sure email values are correct
mock_email_message.assert_called_once_with(
legal_views.FRAUD_REPORT_EMAIL_SUBJECT % (self.data['input_url'],
self.data['input_category']),
'rendered',
legal_views.FRAUD_REPORT_EMAIL_SUBJECT % (self.data["input_url"], self.data["input_category"]),
"rendered",
legal_views.FRAUD_REPORT_EMAIL_FROM,
legal_views.FRAUD_REPORT_EMAIL_TO)
legal_views.FRAUD_REPORT_EMAIL_TO,
)
def test_emails_not_escaped(self):
"""
@ -245,17 +242,16 @@ class TestFraudReport(TestCase):
Tags are still stripped, though.
"""
STRING1 = u"<em>J'adore Citröns</em> & <Piñatas> so there"
EXPECTED1 = u"J'adore Citröns & so there"
STRING1 = "<em>J'adore Citröns</em> & <Piñatas> so there"
EXPECTED1 = "J'adore Citröns & so there"
STRING2 = u"<em>J'adore Piñatas</em> & <fromage> so here"
EXPECTED2 = u"J'adore Piñatas & so here"
STRING2 = "<em>J'adore Piñatas</em> & <fromage> so here"
EXPECTED2 = "J'adore Piñatas & so here"
STRING3 = u"J'adore <coffee>el café</coffee> también"
EXPECTED3 = u"J'adore el café también"
STRING3 = "J'adore <coffee>el café</coffee> también"
EXPECTED3 = "J'adore el café también"
self.data.update(input_specific_product=STRING1, input_details=STRING2,
input_attachment_desc=STRING3)
self.data.update(input_specific_product=STRING1, input_details=STRING2, input_attachment_desc=STRING3)
request = self.factory.post(self.url, self.data)
# make sure CSRF doesn't hold us up

Просмотреть файл

@ -10,58 +10,79 @@ from bedrock.legal import views
from bedrock.legal_docs.views import LegalDocView
urlpatterns = (
page('', 'legal/index.html', ftl_files=['mozorg/about/legal']),
page('eula', 'legal/eula.html'),
page('eula/firefox-2', 'legal/eula/firefox-2-eula.html'),
page('eula/firefox-3', 'legal/eula/firefox-3-eula.html'),
page('eula/thunderbird-1.5', 'legal/eula/thunderbird-1.5-eula.html'),
page('eula/thunderbird-2', 'legal/eula/thunderbird-2-eula.html'),
page('firefox', 'legal/firefox.html'),
page("", "legal/index.html", ftl_files=["mozorg/about/legal"]),
page("eula", "legal/eula.html"),
page("eula/firefox-2", "legal/eula/firefox-2-eula.html"),
page("eula/firefox-3", "legal/eula/firefox-3-eula.html"),
page("eula/thunderbird-1.5", "legal/eula/thunderbird-1.5-eula.html"),
page("eula/thunderbird-2", "legal/eula/thunderbird-2-eula.html"),
page("firefox", "legal/firefox.html"),
# The "impressum" page is intended for Germany. Redirect to German (de) if
# requested in any other locale. (Bug 1248393)
page('impressum', 'legal/impressum.html', active_locales=['de']),
url(r'^terms/betterweb/$', LegalDocView.as_view(template_name='legal/terms/betterweb.html', legal_doc_name='better_web_terms'),
name='legal.terms.betterweb'),
url(r'^terms/mozilla/$', LegalDocView.as_view(template_name='legal/terms/mozilla.html', legal_doc_name='Websites_ToU'),
name='legal.terms.mozilla'),
url(r'^terms/mozilla-vpn/$', LegalDocView.as_view(template_name='legal/terms/mozilla-vpn.html',
legal_doc_name='Mozilla_VPN_ToS'), name='legal.terms.mozilla-vpn'),
url(r'^terms/firefox/$', LegalDocView.as_view(template_name='legal/terms/firefox.html', legal_doc_name='firefox_about_rights'),
name='legal.terms.firefox'),
url(r'^terms/firefox-lite/$', LegalDocView.as_view(template_name='legal/terms/firefox-lite.html',
legal_doc_name='firefox_lite_contentservices_ToS'), name='legal.terms.firefox-lite'),
url(r'^terms/firefox-lite/reward/$', LegalDocView.as_view(template_name='legal/terms/firefox-lite-reward.html',
legal_doc_name='firefox_lite_contentservices_reward'), name='legal.terms.firefox-lite-reward'),
url(r'^terms/firefox-reality/$', LegalDocView.as_view(template_name='legal/terms/firefox-reality.html',
legal_doc_name='firefox_reality_about_rights'), name='legal.terms.firefox-reality'),
url(r'^terms/firefox-private-network/$', LegalDocView.as_view(template_name='legal/terms/firefox-private-network.html',
legal_doc_name='Firefox_Private_Network_ToS'), name='legal.terms.firefox-private-network'),
url(r'^terms/firefox-relay/$', LegalDocView.as_view(template_name='legal/terms/firefox-relay.html',
legal_doc_name='firefox_relay_ToS'), name='legal.terms.firefox-relay'),
url(r'^terms/thunderbird/$', LegalDocView.as_view(template_name='legal/terms/thunderbird.html', legal_doc_name='thunderbird_about_rights'),
name='legal.terms.thunderbird'),
url(r'^terms/services/$', LegalDocView.as_view(template_name='legal/terms/services.html', legal_doc_name='firefox_cloud_services_ToS'),
name='legal.terms.services'),
page('terms/vpn', 'legal/terms/vpn.html'),
url(r'^acceptable-use/$', LegalDocView.as_view(template_name='legal/terms/acceptable-use.html', legal_doc_name='acceptable_use_policy'),
name='legal.terms.acceptable-use'),
url(r'^report-infringement/$', LegalDocView.as_view(template_name='legal/report-infringement.html', legal_doc_name='report_infringement'),
name='legal.report-infringement'),
url('^defend-mozilla-trademarks/$', views.fraud_report, name='legal.fraud-report'),
page("impressum", "legal/impressum.html", active_locales=["de"]),
url(
r"^terms/betterweb/$",
LegalDocView.as_view(template_name="legal/terms/betterweb.html", legal_doc_name="better_web_terms"),
name="legal.terms.betterweb",
),
url(
r"^terms/mozilla/$", LegalDocView.as_view(template_name="legal/terms/mozilla.html", legal_doc_name="Websites_ToU"), name="legal.terms.mozilla"
),
url(
r"^terms/mozilla-vpn/$",
LegalDocView.as_view(template_name="legal/terms/mozilla-vpn.html", legal_doc_name="Mozilla_VPN_ToS"),
name="legal.terms.mozilla-vpn",
),
url(
r"^terms/firefox/$",
LegalDocView.as_view(template_name="legal/terms/firefox.html", legal_doc_name="firefox_about_rights"),
name="legal.terms.firefox",
),
url(
r"^terms/firefox-lite/$",
LegalDocView.as_view(template_name="legal/terms/firefox-lite.html", legal_doc_name="firefox_lite_contentservices_ToS"),
name="legal.terms.firefox-lite",
),
url(
r"^terms/firefox-lite/reward/$",
LegalDocView.as_view(template_name="legal/terms/firefox-lite-reward.html", legal_doc_name="firefox_lite_contentservices_reward"),
name="legal.terms.firefox-lite-reward",
),
url(
r"^terms/firefox-reality/$",
LegalDocView.as_view(template_name="legal/terms/firefox-reality.html", legal_doc_name="firefox_reality_about_rights"),
name="legal.terms.firefox-reality",
),
url(
r"^terms/firefox-private-network/$",
LegalDocView.as_view(template_name="legal/terms/firefox-private-network.html", legal_doc_name="Firefox_Private_Network_ToS"),
name="legal.terms.firefox-private-network",
),
url(
r"^terms/firefox-relay/$",
LegalDocView.as_view(template_name="legal/terms/firefox-relay.html", legal_doc_name="firefox_relay_ToS"),
name="legal.terms.firefox-relay",
),
url(
r"^terms/thunderbird/$",
LegalDocView.as_view(template_name="legal/terms/thunderbird.html", legal_doc_name="thunderbird_about_rights"),
name="legal.terms.thunderbird",
),
url(
r"^terms/services/$",
LegalDocView.as_view(template_name="legal/terms/services.html", legal_doc_name="firefox_cloud_services_ToS"),
name="legal.terms.services",
),
page("terms/vpn", "legal/terms/vpn.html"),
url(
r"^acceptable-use/$",
LegalDocView.as_view(template_name="legal/terms/acceptable-use.html", legal_doc_name="acceptable_use_policy"),
name="legal.terms.acceptable-use",
),
url(
r"^report-infringement/$",
LegalDocView.as_view(template_name="legal/report-infringement.html", legal_doc_name="report_infringement"),
name="legal.report-infringement",
),
url("^defend-mozilla-trademarks/$", views.fraud_report, name="legal.fraud-report"),
)

Просмотреть файл

@ -11,9 +11,9 @@ from bedrock.base.urlresolvers import reverse
from bedrock.legal.forms import FraudReportForm
from lib import l10n_utils
FRAUD_REPORT_EMAIL_FROM = 'Mozilla.com <noreply@mozilla.com>'
FRAUD_REPORT_EMAIL_SUBJECT = 'New trademark infringement report: %s; %s'
FRAUD_REPORT_EMAIL_TO = ['trademarks@mozilla.com']
FRAUD_REPORT_EMAIL_FROM = "Mozilla.com <noreply@mozilla.com>"
FRAUD_REPORT_EMAIL_SUBJECT = "New trademark infringement report: %s; %s"
FRAUD_REPORT_EMAIL_TO = ["trademarks@mozilla.com"]
def submit_form(request, form):
@ -23,15 +23,14 @@ def submit_form(request, form):
form_error = False
data = form.cleaned_data
subject = FRAUD_REPORT_EMAIL_SUBJECT % (data['input_url'],
data['input_category'])
subject = FRAUD_REPORT_EMAIL_SUBJECT % (data["input_url"], data["input_category"])
sender = FRAUD_REPORT_EMAIL_FROM
to = FRAUD_REPORT_EMAIL_TO
msg = render_to_string('legal/emails/fraud-report.txt', data, request=request)
msg = render_to_string("legal/emails/fraud-report.txt", data, request=request)
email = EmailMessage(subject, msg, sender, to)
attachment = data['input_attachment']
attachment = data["input_attachment"]
if attachment:
email.attach(attachment.name, attachment.read(), attachment.content_type)
@ -40,40 +39,40 @@ def submit_form(request, form):
else:
form_error = True
return {'form_submitted': form_submitted, 'form_error': form_error}
return {"form_submitted": form_submitted, "form_error": form_error}
@csrf_protect
def fraud_report(request):
form = FraudReportForm(auto_id='%s')
form = FraudReportForm(auto_id="%s")
form_submitted = False
form_error = False
if request.method == 'POST':
if request.method == "POST":
form = FraudReportForm(request.POST, request.FILES)
form_results = submit_form(request, form)
form_submitted = form_results['form_submitted']
form_error = form_results['form_error']
form_submitted = form_results["form_submitted"]
form_error = form_results["form_error"]
template_vars = {
'form': form,
'form_submitted': form_submitted,
'form_error': form_error,
"form": form,
"form_submitted": form_submitted,
"form_error": form_error,
}
if request.POST and not form_error:
# Seeing the form was submitted without error, redirect, do not simply
# send a response to avoid problem described below.
# @see https://bugzilla.mozilla.org/show_bug.cgi?id=873476 (3.2)
response = redirect(reverse('legal.fraud-report'), template_vars)
response['Location'] += '?submitted=%s' % form_submitted
response = redirect(reverse("legal.fraud-report"), template_vars)
response["Location"] += "?submitted=%s" % form_submitted
return response
else:
# If the below is called after a redirect the template_vars will be lost, therefore
# we need to update the form_submitted state from the submitted url parameter.
submitted = request.GET.get('submitted') == 'True'
template_vars['form_submitted'] = submitted
return l10n_utils.render(request, 'legal/fraud-report.html', template_vars)
submitted = request.GET.get("submitted") == "True"
template_vars["form_submitted"] = submitted
return l10n_utils.render(request, "legal/fraud-report.html", template_vars)

Просмотреть файл

@ -10,10 +10,8 @@ from bedrock.legal_docs.models import LegalDoc
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False,
help='If no error occurs, swallow all output.'),
parser.add_argument('-f', '--force', action='store_true', dest='force', default=False,
help='Load the data even if nothing new from git.'),
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="If no error occurs, swallow all output."),
parser.add_argument("-f", "--force", action="store_true", dest="force", default=False, help="Load the data even if nothing new from git."),
def output(self, msg):
if not self.quiet:
@ -24,28 +22,25 @@ class Command(BaseCommand):
requests.get(settings.LEGAL_DOCS_DMS_URL)
def handle(self, *args, **options):
self.quiet = options['quiet']
repo = GitRepo(settings.LEGAL_DOCS_PATH,
settings.LEGAL_DOCS_REPO,
branch_name=settings.LEGAL_DOCS_BRANCH,
name='Legal Docs')
self.output('Updating git repo')
self.quiet = options["quiet"]
repo = GitRepo(settings.LEGAL_DOCS_PATH, settings.LEGAL_DOCS_REPO, branch_name=settings.LEGAL_DOCS_BRANCH, name="Legal Docs")
self.output("Updating git repo")
repo.update()
if not (options['force'] or repo.has_changes()):
self.output('No legal docs updates')
if not (options["force"] or repo.has_changes()):
self.output("No legal docs updates")
self.snitch()
return
self.output('Loading legal docs into database')
self.output("Loading legal docs into database")
count, errors = LegalDoc.objects.refresh()
self.output(f'{count} legal docs successfully loaded')
self.output(f"{count} legal docs successfully loaded")
if errors:
self.output(f'Encountered {errors} errors while loading docs')
self.output(f"Encountered {errors} errors while loading docs")
else:
# only set latest if there are no errors so that it will try the errors again next time
# also so that it will fail again and thus not ping the snitch so that we'll be notified
repo.set_db_latest()
self.output('Saved latest git repo state to database')
self.output("Saved latest git repo state to database")
self.snitch()
self.output('Done!')
self.output("Done!")

Просмотреть файл

@ -9,9 +9,9 @@ from mdx_outline import OutlineExtension
LEGAL_DOCS_LOCALES_TO_BEDROCK = {
'hi': 'hi-IN',
"hi": "hi-IN",
}
LOCALE_RE = re.compile(r'[a-z]{2,3}(-[A-Z]{2}(_[a-z])?)?$')
LOCALE_RE = re.compile(r"[a-z]{2,3}(-[A-Z]{2}(_[a-z])?)?$")
def process_md_file(file_path):
@ -19,12 +19,11 @@ def process_md_file(file_path):
try:
# Parse the Markdown file
md.markdownFromFile(
input=str(file_path), output=output, extensions=[
'markdown.extensions.attr_list',
'markdown.extensions.toc',
OutlineExtension((('wrapper_cls', ''),))
])
content = output.getvalue().decode('utf-8')
input=str(file_path),
output=output,
extensions=["markdown.extensions.attr_list", "markdown.extensions.toc", OutlineExtension((("wrapper_cls", ""),))],
)
content = output.getvalue().decode("utf-8")
except IOError:
content = None
finally:
@ -43,13 +42,13 @@ def get_data_from_file_path(file_path):
if locale in LEGAL_DOCS_LOCALES_TO_BEDROCK:
locale = LEGAL_DOCS_LOCALES_TO_BEDROCK[locale]
return {
'locale': locale,
'doc_name': doc_name,
"locale": locale,
"doc_name": doc_name,
}
def snake_case(name):
return name.lower().replace('-', '_')
return name.lower().replace("-", "_")
class LegalDocsManager(models.Manager):
@ -72,18 +71,18 @@ class LegalDocsManager(models.Manager):
doc_name = snake_case(doc_name)
doc = self.get(name=doc_name, locale=locale)
all_locales = list(self.filter(name=doc_name).values_list('locale', flat=True))
if 'en' in all_locales:
all_locales = list(self.filter(name=doc_name).values_list("locale", flat=True))
if "en" in all_locales:
# legal-docs now uses en but the site needs en-US
all_locales[all_locales.index('en')] = 'en-US'
all_locales[all_locales.index("en")] = "en-US"
# filter locales not active on the site
all_locales = [l for l in all_locales if l in settings.PROD_LANGUAGES]
return {
'content': doc.content,
"content": doc.content,
# sort and make unique
'active_locales': sorted(set(all_locales)),
"active_locales": sorted(set(all_locales)),
}
def refresh(self):
@ -92,7 +91,7 @@ class LegalDocsManager(models.Manager):
docs_path = settings.LEGAL_DOCS_PATH
with transaction.atomic(using=self.db):
self.all().delete()
doc_files = docs_path.glob('*/*.md')
doc_files = docs_path.glob("*/*.md")
for docf in doc_files:
path_data = get_data_from_file_path(docf)
content = process_md_file(docf)
@ -100,11 +99,13 @@ class LegalDocsManager(models.Manager):
errors += 1
continue
doc_objs.append(LegalDoc(
name=path_data['doc_name'],
locale=path_data['locale'],
content=content,
))
doc_objs.append(
LegalDoc(
name=path_data["doc_name"],
locale=path_data["locale"],
content=content,
)
)
self.bulk_create(doc_objs)
return len(doc_objs), errors
@ -118,4 +119,4 @@ class LegalDoc(models.Model):
objects = LegalDocsManager()
def __str__(self):
return f'{self.name} - {self.locale}'
return f"{self.name} - {self.locale}"

Просмотреть файл

@ -13,182 +13,178 @@ from bedrock.legal_docs.models import LegalDoc, get_data_from_file_path
from bedrock.mozorg.tests import TestCase
@override_settings(PROD_LANGUAGES=['en-US', 'de', 'hi-IN'])
@override_settings(PROD_LANGUAGES=["en-US", "de", "hi-IN"])
class TestLoadLegalDoc(TestCase):
def test_legal_doc_not_found(self):
"""Missing doc should be None"""
doc = views.load_legal_doc('the_dude_is_legal', 'de')
doc = views.load_legal_doc("the_dude_is_legal", "de")
self.assertIsNone(doc)
def test_legal_doc_exists(self):
"""Should return the content of the en-US file if it exists."""
LegalDoc.objects.create(
name='the_dude_exists',
locale='en-US',
name="the_dude_exists",
locale="en-US",
content="You're not wrong Walter...",
)
doc = views.load_legal_doc('the_dude_exists', 'de')
self.assertEqual(doc['content'], "You're not wrong Walter...")
self.assertEqual(doc['active_locales'], ['en-US'])
doc = views.load_legal_doc("the_dude_exists", "de")
self.assertEqual(doc["content"], "You're not wrong Walter...")
self.assertEqual(doc["active_locales"], ["en-US"])
def test_legal_doc_exists_en_locale(self):
"""Should return the content of the en file and say it's en-US."""
LegalDoc.objects.create(
name='the_dude_exists',
locale='en',
name="the_dude_exists",
locale="en",
content="You're not wrong Walter...",
)
doc = views.load_legal_doc('the_dude_exists', 'en-US')
self.assertEqual(doc['content'], "You're not wrong Walter...")
self.assertEqual(doc['active_locales'], ['en-US'])
doc = views.load_legal_doc("the_dude_exists", "en-US")
self.assertEqual(doc["content"], "You're not wrong Walter...")
self.assertEqual(doc["active_locales"], ["en-US"])
def test_legal_doc_exists_snake_case_convert(self):
"""Should return the content of the file if it exists in snake case."""
LegalDoc.objects.create(
name='the_dude_exists',
locale='en-US',
name="the_dude_exists",
locale="en-US",
content="You're not wrong Walter...",
)
doc = views.load_legal_doc('The-Dude-Exists', 'de')
self.assertEqual(doc['content'], "You're not wrong Walter...")
self.assertEqual(doc['active_locales'], ['en-US'])
doc = views.load_legal_doc("The-Dude-Exists", "de")
self.assertEqual(doc["content"], "You're not wrong Walter...")
self.assertEqual(doc["active_locales"], ["en-US"])
def test_localized_legal_doc_exists(self):
"""Localization works, and list of translations doesn't include non .md files and non-prod locales."""
LegalDoc.objects.create(
name='the_dude_exists',
locale='en',
name="the_dude_exists",
locale="en",
content="You're not wrong Walter...",
)
LegalDoc.objects.create(
name='the_dude_exists',
locale='de',
name="the_dude_exists",
locale="de",
content="You're in German Walter...",
)
doc = views.load_legal_doc('the_dude_exists', 'de')
self.assertEqual(doc['content'], "You're in German Walter...")
self.assertEqual(set(doc['active_locales']), {'de', 'en-US'})
doc = views.load_legal_doc("the_dude_exists", "de")
self.assertEqual(doc["content"], "You're in German Walter...")
self.assertEqual(set(doc["active_locales"]), {"de", "en-US"})
class TestLegalDocView(TestCase):
@patch.object(views, 'load_legal_doc')
@patch.object(views, "load_legal_doc")
def test_missing_doc_is_404(self, lld_mock):
lld_mock.return_value = None
req = RequestFactory().get('/dude/is/gone/')
req.locale = 'de'
view = views.LegalDocView.as_view(template_name='base.html',
legal_doc_name='the_dude_is_gone')
req = RequestFactory().get("/dude/is/gone/")
req.locale = "de"
view = views.LegalDocView.as_view(template_name="base.html", legal_doc_name="the_dude_is_gone")
with self.assertRaises(Http404):
view(req)
lld_mock.assert_called_with('the_dude_is_gone', 'de')
lld_mock.assert_called_with("the_dude_is_gone", "de")
@patch.object(views, 'load_legal_doc')
@patch.object(views.l10n_utils, 'render')
@patch.object(views, "load_legal_doc")
@patch.object(views.l10n_utils, "render")
def test_good_doc_okay(self, render_mock, lld_mock):
"""Should render correct thing when all is well"""
doc_value = "Donny, you're out of your element!"
lld_mock.return_value = {
'content': doc_value,
'active_locales': ['de', 'en-US'],
"content": doc_value,
"active_locales": ["de", "en-US"],
}
good_resp = HttpResponse(doc_value)
render_mock.return_value = good_resp
req = RequestFactory().get('/dude/exists/')
req.locale = 'de'
view = views.LegalDocView.as_view(template_name='base.html',
legal_doc_name='the_dude_exists')
req = RequestFactory().get("/dude/exists/")
req.locale = "de"
view = views.LegalDocView.as_view(template_name="base.html", legal_doc_name="the_dude_exists")
resp = view(req)
assert resp['cache-control'] == 'max-age={0!s}'.format(views.CACHE_TIMEOUT)
assert resp.content.decode('utf-8') == doc_value
assert render_mock.call_args[0][2]['doc'] == doc_value
lld_mock.assert_called_with('the_dude_exists', 'de')
assert resp["cache-control"] == "max-age={0!s}".format(views.CACHE_TIMEOUT)
assert resp.content.decode("utf-8") == doc_value
assert render_mock.call_args[0][2]["doc"] == doc_value
lld_mock.assert_called_with("the_dude_exists", "de")
@patch.object(views, 'load_legal_doc')
@patch.object(views.l10n_utils, 'render')
@patch.object(views, "load_legal_doc")
@patch.object(views.l10n_utils, "render")
def test_cache_settings(self, render_mock, lld_mock):
"""Should use the cache_timeout value from view."""
doc_value = "Donny, you're out of your element!"
lld_mock.return_value = {
'content': doc_value,
'active_locales': ['es-ES', 'en-US'],
"content": doc_value,
"active_locales": ["es-ES", "en-US"],
}
good_resp = HttpResponse(doc_value)
render_mock.return_value = good_resp
req = RequestFactory().get('/dude/exists/cached/')
req.locale = 'es-ES'
view = views.LegalDocView.as_view(template_name='base.html',
legal_doc_name='the_dude_exists',
cache_timeout=10)
req = RequestFactory().get("/dude/exists/cached/")
req.locale = "es-ES"
view = views.LegalDocView.as_view(template_name="base.html", legal_doc_name="the_dude_exists", cache_timeout=10)
resp = view(req)
assert resp['cache-control'] == 'max-age=10'
assert resp["cache-control"] == "max-age=10"
@patch.object(views, 'load_legal_doc')
@patch.object(views.l10n_utils, 'render')
@patch.object(views, "load_legal_doc")
@patch.object(views.l10n_utils, "render")
def test_cache_class_attrs(self, render_mock, lld_mock):
"""Should use the cache_timeout value from view class."""
doc_value = "Donny, you're out of your element!"
lld_mock.return_value = {
'content': doc_value,
'active_locales': ['es-ES', 'en-US'],
"content": doc_value,
"active_locales": ["es-ES", "en-US"],
}
good_resp = HttpResponse(doc_value)
render_mock.return_value = good_resp
req = RequestFactory().get('/dude/exists/cached/2/')
req.locale = 'es-ES'
req = RequestFactory().get("/dude/exists/cached/2/")
req.locale = "es-ES"
class DocTestView(views.LegalDocView):
cache_timeout = 20
template_name = 'base.html'
legal_doc_name = 'the_dude_abides'
template_name = "base.html"
legal_doc_name = "the_dude_abides"
view = DocTestView.as_view()
resp = view(req)
assert resp['cache-control'] == 'max-age=20'
lld_mock.assert_called_with('the_dude_abides', 'es-ES')
assert resp["cache-control"] == "max-age=20"
lld_mock.assert_called_with("the_dude_abides", "es-ES")
class TestFilePathData(TestCase):
def test_legacy_repo_layout(self):
path = Path('/repo/data/legal_docs/websites_privacy_notice/en-US.md')
path = Path("/repo/data/legal_docs/websites_privacy_notice/en-US.md")
assert get_data_from_file_path(path) == {
'locale': 'en-US',
'doc_name': 'websites_privacy_notice',
"locale": "en-US",
"doc_name": "websites_privacy_notice",
}
path = Path('/repo/data/legal_docs/websites_privacy_notice/de.md')
path = Path("/repo/data/legal_docs/websites_privacy_notice/de.md")
assert get_data_from_file_path(path) == {
'locale': 'de',
'doc_name': 'websites_privacy_notice',
"locale": "de",
"doc_name": "websites_privacy_notice",
}
path = Path('/repo/data/legal_docs/firefox_privacy_notice/es-ES_b.md')
path = Path("/repo/data/legal_docs/firefox_privacy_notice/es-ES_b.md")
assert get_data_from_file_path(path) == {
'locale': 'es-ES_b',
'doc_name': 'firefox_privacy_notice',
"locale": "es-ES_b",
"doc_name": "firefox_privacy_notice",
}
path = Path('/repo/data/legal_docs/WebRTC_ToS/cnh.md')
path = Path("/repo/data/legal_docs/WebRTC_ToS/cnh.md")
assert get_data_from_file_path(path) == {
'locale': 'cnh',
'doc_name': 'WebRTC_ToS',
"locale": "cnh",
"doc_name": "WebRTC_ToS",
}
def test_new_repo_layout(self):
path = Path('/repo/data/legal_docs/en-US/websites_privacy_notice.md')
path = Path("/repo/data/legal_docs/en-US/websites_privacy_notice.md")
assert get_data_from_file_path(path) == {
'locale': 'en-US',
'doc_name': 'websites_privacy_notice',
"locale": "en-US",
"doc_name": "websites_privacy_notice",
}
path = Path('/repo/data/legal_docs/de/websites_privacy_notice.md')
path = Path("/repo/data/legal_docs/de/websites_privacy_notice.md")
assert get_data_from_file_path(path) == {
'locale': 'de',
'doc_name': 'websites_privacy_notice',
"locale": "de",
"doc_name": "websites_privacy_notice",
}
path = Path('/repo/data/legal_docs/es-ES_b/firefox_privacy_notice.md')
path = Path("/repo/data/legal_docs/es-ES_b/firefox_privacy_notice.md")
assert get_data_from_file_path(path) == {
'locale': 'es-ES_b',
'doc_name': 'firefox_privacy_notice',
"locale": "es-ES_b",
"doc_name": "firefox_privacy_notice",
}
path = Path('/repo/data/legal_docs/cnh/WebRTC_ToS.md')
path = Path("/repo/data/legal_docs/cnh/WebRTC_ToS.md")
assert get_data_from_file_path(path) == {
'locale': 'cnh',
'doc_name': 'WebRTC_ToS',
"locale": "cnh",
"doc_name": "WebRTC_ToS",
}

Просмотреть файл

@ -28,10 +28,10 @@ def load_legal_doc(doc_name, locale):
doc = LegalDoc.objects.get_doc(doc_name, locale)
except LegalDoc.DoesNotExist:
try:
doc = LegalDoc.objects.get_doc(doc_name, 'en')
doc = LegalDoc.objects.get_doc(doc_name, "en")
except LegalDoc.DoesNotExist:
try:
doc = LegalDoc.objects.get_doc(doc_name, 'en-US')
doc = LegalDoc.objects.get_doc(doc_name, "en-US")
except LegalDoc.DoesNotExist:
doc = None
@ -53,8 +53,9 @@ class LegalDocView(TemplateView):
See `bedrock/privacy/views.py` for usage examples.
"""
legal_doc_name = None
legal_doc_context_name = 'doc'
legal_doc_context_name = "doc"
cache_timeout = CACHE_TIMEOUT
def get_legal_doc(self):
@ -62,23 +63,22 @@ class LegalDocView(TemplateView):
return load_legal_doc(self.legal_doc_name, locale)
def render_to_response(self, context, **response_kwargs):
response_kwargs.setdefault('content_type', self.content_type)
return l10n_utils.render(self.request,
self.get_template_names()[0],
context, ftl_files=['mozorg/about/legal', 'privacy/index'],
**response_kwargs)
response_kwargs.setdefault("content_type", self.content_type)
return l10n_utils.render(
self.request, self.get_template_names()[0], context, ftl_files=["mozorg/about/legal", "privacy/index"], **response_kwargs
)
def get_context_data(self, **kwargs):
legal_doc = self.get_legal_doc()
if legal_doc is None:
raise Http404('Legal doc not found')
raise Http404("Legal doc not found")
context = super(LegalDocView, self).get_context_data(**kwargs)
context[self.legal_doc_context_name] = legal_doc['content']
context['active_locales'] = legal_doc['active_locales']
context[self.legal_doc_context_name] = legal_doc["content"]
context["active_locales"] = legal_doc["active_locales"]
return context
@classmethod
def as_view(cls, **initkwargs):
cache_timeout = initkwargs.pop('cache_timeout', cls.cache_timeout)
cache_timeout = initkwargs.pop("cache_timeout", cls.cache_timeout)
return cache_page(cache_timeout)(super(LegalDocView, cls).as_view(**initkwargs))

Просмотреть файл

@ -13,7 +13,7 @@ from lib.l10n_utils import get_locale
# match 1 - 4 digits only
FC_RE = re.compile(r'^\d{1,4}$')
FC_RE = re.compile(r"^\d{1,4}$")
def canonical_path(request):
@ -21,9 +21,9 @@ def canonical_path(request):
The canonical path can be overridden with a template variable like
l10n_utils.render(request, template_name, {'canonical_path': '/firefox/'})
"""
lang = getattr(request, 'locale', settings.LANGUAGE_CODE)
url = getattr(request, 'path', '/')
return {'canonical_path': re.sub(r'^/' + lang, '', url)}
lang = getattr(request, "locale", settings.LANGUAGE_CODE)
url = getattr(request, "path", "/")
return {"canonical_path": re.sub(r"^/" + lang, "", url)}
def current_year(request):
@ -32,22 +32,22 @@ def current_year(request):
def funnelcake_param(request):
"""If a query param for a funnelcake is sent, add it to the context."""
fc_id = request.GET.get('f', None)
fc_id = request.GET.get("f", None)
context = {}
if fc_id and FC_RE.match(fc_id):
# special case for installer-help page
# bug 933852
installer_help_url = reverse('firefox.installer-help')
installer_help_url = reverse("firefox.installer-help")
if installer_help_url in request.path_info:
fc_id = str(int(fc_id) + 1)
context['funnelcake_id'] = fc_id
context["funnelcake_id"] = fc_id
return context
def facebook_locale(request):
return {'facebook_locale': get_fb_like_locale(get_locale(request))}
return {"facebook_locale": get_fb_like_locale(get_locale(request))}
def contrib_numbers(request):

Просмотреть файл

@ -13,12 +13,12 @@ from bedrock.externalfiles import ExternalFile
class CreditsFile(ExternalFile):
def validate_content(self, content):
rows = list(csv.reader(content.strip().split('\n')))
rows = list(csv.reader(content.strip().split("\n")))
if len(rows) < 2200: # it's 2273 as of now
raise ValueError('Much smaller file than expected. {0} rows.'.format(len(rows)))
raise ValueError("Much smaller file than expected. {0} rows.".format(len(rows)))
if len(rows[0]) != 2 or len(rows[-1]) != 2:
raise ValueError('CSV Content corrupted.')
raise ValueError("CSV Content corrupted.")
return content
@ -57,7 +57,7 @@ class CreditsFile(ExternalFile):
else:
continue
sortkey = unicodedata.normalize('NFKD', sortkey).encode('ascii', 'ignore').decode()
sortkey = unicodedata.normalize("NFKD", sortkey).encode("ascii", "ignore").decode()
names.append([name, sortkey.upper()])
return sorted(names, key=itemgetter(1))

Просмотреть файл

@ -15,11 +15,12 @@ def cache_control_expires(num_hours):
num_seconds = int(num_hours * 60 * 60)
def decorator(func):
@wraps(func)
def inner(request, *args, **kwargs):
response = func(request, *args, **kwargs)
patch_response_headers(response, num_seconds)
return response
return inner
return decorator

Просмотреть файл

@ -15,16 +15,16 @@ from django.utils.safestring import mark_safe
from lib.l10n_utils.fluent import ftl, ftl_lazy
FORMATS = (('H', ftl_lazy('newsletter-form-html')), ('T', ftl_lazy('newsletter-form-text')))
LANGS_TO_STRIP = ['en-US', 'es']
PARENTHETIC_RE = re.compile(r' \([^)]+\)$')
FORMATS = (("H", ftl_lazy("newsletter-form-html")), ("T", ftl_lazy("newsletter-form-text")))
LANGS_TO_STRIP = ["en-US", "es"]
PARENTHETIC_RE = re.compile(r" \([^)]+\)$")
def strip_parenthetical(lang_name):
"""
Remove the parenthetical from the end of the language name string.
"""
return PARENTHETIC_RE.sub('', lang_name, 1)
return PARENTHETIC_RE.sub("", lang_name, 1)
class PrivacyWidget(widgets.CheckboxInput):
@ -32,17 +32,13 @@ class PrivacyWidget(widgets.CheckboxInput):
it should be standardized"""
def render(self, name, value, attrs=None, renderer=None):
attrs['required'] = 'required'
attrs["required"] = "required"
input_txt = super(PrivacyWidget, self).render(name, value, attrs)
policy_txt = ftl('newsletter-form-im-okay-with-mozilla',
url=reverse('privacy.notices.websites'))
policy_txt = ftl("newsletter-form-im-okay-with-mozilla", url=reverse("privacy.notices.websites"))
return mark_safe(
'<label for="%s" class="privacy-check-label">'
'%s '
'<span class="title">%s</span></label>'
% (attrs['id'], input_txt, policy_txt)
'<label for="%s" class="privacy-check-label">' "%s " '<span class="title">%s</span></label>' % (attrs["id"], input_txt, policy_txt)
)
@ -50,36 +46,37 @@ class HoneyPotWidget(widgets.TextInput):
"""Render a text field to (hopefully) trick bots. Will be used on many pages."""
def render(self, name, value, attrs=None, renderer=None):
honeypot_txt = ftl('newsletter-form-leave-this-field-empty')
honeypot_txt = ftl("newsletter-form-leave-this-field-empty")
# semi-randomized in case we have more than one per page.
# this is maybe/probably overthought
honeypot_id = 'office-fax-' + str(randrange(1001)) + '-' + str(datetime.now().strftime("%Y%m%d%H%M%S%f"))
honeypot_id = "office-fax-" + str(randrange(1001)) + "-" + str(datetime.now().strftime("%Y%m%d%H%M%S%f"))
return mark_safe(
'<div class="super-priority-field">'
'<label for="%s">%s</label>'
'<input type="text" name="office_fax" id="%s">'
'</div>' % (honeypot_id, honeypot_txt, honeypot_id))
"</div>" % (honeypot_id, honeypot_txt, honeypot_id)
)
class URLInput(widgets.TextInput):
input_type = 'url'
input_type = "url"
class EmailInput(widgets.TextInput):
input_type = 'email'
input_type = "email"
class DateInput(widgets.DateInput):
input_type = 'date'
input_type = "date"
class TimeInput(widgets.TimeInput):
input_type = 'time'
input_type = "time"
class TelInput(widgets.TextInput):
input_type = 'tel'
input_type = "tel"
class NumberInput(widgets.TextInput):
input_type = 'number'
input_type = "number"

Просмотреть файл

@ -27,6 +27,7 @@ class PageNode:
In the example above, the template `child1.html` will be available at the
url `/root/child1/`.
"""
def __init__(self, display_name, path=None, template=None, children=None):
"""
Create a new PageNode.
@ -59,15 +60,13 @@ class PageNode:
The full url path for this node, including the paths of its parent
nodes.
"""
return '/'.join([node.path for node in self.breadcrumbs
if node.path is not None])
return "/".join([node.path for node in self.breadcrumbs if node.path is not None])
@property
def page(self):
"""The page for this node, which is a RegexURLPattern."""
if self.template:
return page(self.full_path, self.template, node_root=self.root,
node=self)
return page(self.full_path, self.template, node_root=self.root, node=self)
else:
return None
@ -96,7 +95,7 @@ class PageNode:
"""The root of the tree that this node is in."""
root = list(self.path_to_root)[-1]
if not isinstance(root, PageRoot):
raise ValueError('Root node is not a PageRoot object.')
raise ValueError("Root node is not a PageRoot object.")
return root
@property
@ -127,9 +126,7 @@ class PageNode:
return None
def __repr__(self):
return u'{0}(display_name="{1}", path="{2}", template="{3})"'.format(
self.__class__.__name__, self.display_name, self.full_path,
self.template)
return '{0}(display_name="{1}", path="{2}", template="{3})"'.format(self.__class__.__name__, self.display_name, self.full_path, self.template)
class PageRoot(PageNode):
@ -139,6 +136,7 @@ class PageRoot(PageNode):
The root node of a PageNode tree MUST be a PageRoot. If it is not, any
reference to the root of the tree with throw a ValueError.
"""
def __init__(self, *args, **kwargs):
super(PageRoot, self).__init__(*args, **kwargs)
@ -166,5 +164,4 @@ class PageRoot(PageNode):
def as_urlpatterns(self):
"""Return a urlconf for this PageRoot and its children."""
return [
node.page for node in self.preordered_nodes if node.template]
return [node.page for node in self.preordered_nodes if node.template]

Просмотреть файл

@ -9,13 +9,13 @@ from product_details.storage import PDDatabaseStorage, PDFileStorage
from bedrock.utils.git import GitRepo
FIREFOX_VERSION_KEYS = (
'FIREFOX_NIGHTLY',
'FIREFOX_DEVEDITION',
'FIREFOX_ESR',
'FIREFOX_ESR_NEXT',
'LATEST_FIREFOX_DEVEL_VERSION',
'LATEST_FIREFOX_RELEASED_DEVEL_VERSION',
'LATEST_FIREFOX_VERSION',
"FIREFOX_NIGHTLY",
"FIREFOX_DEVEDITION",
"FIREFOX_ESR",
"FIREFOX_ESR_NEXT",
"LATEST_FIREFOX_DEVEL_VERSION",
"LATEST_FIREFOX_RELEASED_DEVEL_VERSION",
"LATEST_FIREFOX_VERSION",
)
@ -23,79 +23,70 @@ class Command(BaseCommand):
def __init__(self, stdout=None, stderr=None, no_color=False):
self.file_storage = PDFileStorage(json_dir=settings.PROD_DETAILS_TEST_DIR)
self.db_storage = PDDatabaseStorage()
self.repo = GitRepo(settings.PROD_DETAILS_JSON_REPO_PATH,
settings.PROD_DETAILS_JSON_REPO_URI,
settings.PROD_DETAILS_JSON_REPO_BRANCH,
name='Product Details')
self.repo = GitRepo(
settings.PROD_DETAILS_JSON_REPO_PATH, settings.PROD_DETAILS_JSON_REPO_URI, settings.PROD_DETAILS_JSON_REPO_BRANCH, name="Product Details"
)
# fake last-modified string since the releng repo doesn't store those files
# and we rely on git commits for updates
self.last_modified = datetime.now().isoformat()
super(Command, self).__init__(stdout, stderr, no_color)
def add_arguments(self, parser):
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False,
help='If no error occurs, swallow all output.'),
parser.add_argument('--database', default='default',
help=('Specifies the database to use, if using a db. '
'Defaults to "default".')),
parser.add_argument('-f', '--force', action='store_true', dest='force', default=False,
help='Load the data even if nothing new from git.'),
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="If no error occurs, swallow all output."),
parser.add_argument("--database", default="default", help=("Specifies the database to use, if using a db. " 'Defaults to "default".')),
parser.add_argument("-f", "--force", action="store_true", dest="force", default=False, help="Load the data even if nothing new from git."),
def handle(self, *args, **options):
# don't really care about deleted files. almost never happens in p-d.
if not (self.update_file_data() or options['force']):
if not options['quiet']:
print('Product Details data was already up to date')
if not (self.update_file_data() or options["force"]):
if not options["quiet"]:
print("Product Details data was already up to date")
return
try:
self.validate_data()
except Exception:
raise CommandError('Product Details data is invalid')
raise CommandError("Product Details data is invalid")
if not options['quiet']:
print('Product Details data is valid')
if not options["quiet"]:
print("Product Details data is valid")
if not settings.PROD_DETAILS_STORAGE.endswith('PDDatabaseStorage'):
if not settings.PROD_DETAILS_STORAGE.endswith("PDDatabaseStorage"):
# no need to continue if not using DB backend
return
self.load_changes(options, self.file_storage.all_json_files())
self.repo.set_db_latest()
if not options['quiet']:
print('Product Details data update is complete')
if not options["quiet"]:
print("Product Details data update is complete")
def load_changes(self, options, modified_files):
with transaction.atomic(using=options['database']):
with transaction.atomic(using=options["database"]):
for filename in modified_files:
# skip the l10n directory for now
if filename.startswith('l10n/'):
if filename.startswith("l10n/"):
continue
self.db_storage.update(filename,
self.file_storage.content(filename),
self.last_modified)
if not options['quiet']:
print('Updated ' + filename)
self.db_storage.update(filename, self.file_storage.content(filename), self.last_modified)
if not options["quiet"]:
print("Updated " + filename)
self.db_storage.update('/', '', self.last_modified)
self.db_storage.update('regions/', '', self.last_modified)
self.db_storage.update("/", "", self.last_modified)
self.db_storage.update("regions/", "", self.last_modified)
def update_file_data(self):
self.repo.update()
return self.repo.has_changes()
def count_builds(self, version_key, min_builds=20):
version = self.file_storage.data('firefox_versions.json')[version_key]
version = self.file_storage.data("firefox_versions.json")[version_key]
if not version:
if version_key == 'FIREFOX_ESR_NEXT':
if version_key == "FIREFOX_ESR_NEXT":
return
builds = len([locale for locale, build in
self.file_storage.data('firefox_primary_builds.json').items()
if version in build])
builds = len([locale for locale, build in self.file_storage.data("firefox_primary_builds.json").items() if version in build])
if builds < min_builds:
raise ValueError('Too few builds for {}'.format(version_key))
raise ValueError("Too few builds for {}".format(version_key))
def validate_data(self):
self.file_storage.clear_cache()

Просмотреть файл

@ -12,7 +12,6 @@ from django.utils.cache import add_never_cache_headers
class CacheMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
@ -21,16 +20,13 @@ class CacheMiddleware:
return self.process_response(request, response)
def process_response(self, request, response):
cache = (request.method != 'POST'
and response.status_code != 404
and 'Cache-Control' not in response)
cache = request.method != "POST" and response.status_code != 404 and "Cache-Control" not in response
if cache:
d = datetime.datetime.now() + datetime.timedelta(minutes=10)
stamp = time.mktime(d.timetuple())
response['Cache-Control'] = 'max-age=600'
response['Expires'] = formatdate(timeval=stamp, localtime=False,
usegmt=True)
response["Cache-Control"] = "max-age=600"
response["Expires"] = formatdate(timeval=stamp, localtime=False, usegmt=True)
return response
@ -47,7 +43,7 @@ class ClacksOverheadMiddleware:
@staticmethod
def process_response(request, response):
if response.status_code == 200:
response['X-Clacks-Overhead'] = 'GNU Terry Pratchett'
response["X-Clacks-Overhead"] = "GNU Terry Pratchett"
return response
@ -56,8 +52,8 @@ class HostnameMiddleware:
if not settings.ENABLE_HOSTNAME_MIDDLEWARE:
raise MiddlewareNotUsed
values = [getattr(settings, x) for x in ['HOSTNAME', 'CLUSTER_NAME']]
self.backend_server = '.'.join(x for x in values if x)
values = [getattr(settings, x) for x in ["HOSTNAME", "CLUSTER_NAME"]]
self.backend_server = ".".join(x for x in values if x)
self.get_response = get_response
@ -66,7 +62,7 @@ class HostnameMiddleware:
return self.process_response(request, response)
def process_response(self, request, response):
response['X-Backend-Server'] = self.backend_server
response["X-Backend-Server"] = self.backend_server
return response
@ -82,12 +78,11 @@ class VaryNoCacheMiddleware:
@staticmethod
def process_response(request, response):
if 'vary' in response:
if "vary" in response:
path = request.path
if path != '/' and not any(path.startswith(x) for x in
settings.VARY_NOCACHE_EXEMPT_URL_PREFIXES):
del response['vary']
del response['expires']
if path != "/" and not any(path.startswith(x) for x in settings.VARY_NOCACHE_EXEMPT_URL_PREFIXES):
del response["vary"]
del response["expires"]
add_never_cache_headers(response)
return response

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -14,6 +14,7 @@ from django.template.loader import render_to_string
from django.utils import six
from django.utils.http import urlquote
from django.utils.translation import ugettext as _
try:
from django.utils.encoding import smart_unicode as smart_text
except ImportError:
@ -27,45 +28,45 @@ from bedrock.base.templatetags.helpers import static
from bedrock.firefox.firefox_details import firefox_ios
ALL_FX_PLATFORMS = ('windows', 'linux', 'mac', 'android', 'ios')
ALL_FX_PLATFORMS = ("windows", "linux", "mac", "android", "ios")
def _strip_img_prefix(url):
return re.sub(r'^/?img/', '', url)
return re.sub(r"^/?img/", "", url)
def _l10n_media_exists(type, locale, url):
""" checks if a localized media file exists for the locale """
return find_static(path.join(type, 'l10n', locale, url)) is not None
"""checks if a localized media file exists for the locale"""
return find_static(path.join(type, "l10n", locale, url)) is not None
def add_string_to_image_url(url, addition):
"""Add the platform string to an image url."""
filename, ext = splitext(url)
return ''.join([filename, '-', addition, ext])
return "".join([filename, "-", addition, ext])
def convert_to_high_res(url):
"""Convert a file name to the high-resolution version."""
return add_string_to_image_url(url, 'high-res')
return add_string_to_image_url(url, "high-res")
def l10n_img_file_name(ctx, url):
"""Return the filename of the l10n image for use by static()"""
url = url.lstrip('/')
locale = getattr(ctx['request'], 'locale', None)
url = url.lstrip("/")
locale = getattr(ctx["request"], "locale", None)
if not locale:
locale = settings.LANGUAGE_CODE
# We use the same localized screenshots for all Spanishes
if locale.startswith('es') and not _l10n_media_exists('img', locale, url):
locale = 'es-ES'
if locale.startswith("es") and not _l10n_media_exists("img", locale, url):
locale = "es-ES"
if locale != settings.LANGUAGE_CODE:
if not _l10n_media_exists('img', locale, url):
if not _l10n_media_exists("img", locale, url):
locale = settings.LANGUAGE_CODE
return path.join('img', 'l10n', locale, url)
return path.join("img", "l10n", locale, url)
@library.global_function
@ -139,13 +140,12 @@ def l10n_css(ctx):
$ROOT/media/css/l10n/fr/intl.css
"""
locale = getattr(ctx['request'], 'locale', 'en-US')
locale = getattr(ctx["request"], "locale", "en-US")
if _l10n_media_exists('css', locale, 'intl.css'):
markup = ('<link rel="stylesheet" media="screen,projection,tv" href='
'"%s">' % static(path.join('css', 'l10n', locale, 'intl.css')))
if _l10n_media_exists("css", locale, "intl.css"):
markup = '<link rel="stylesheet" media="screen,projection,tv" href=' '"%s">' % static(path.join("css", "l10n", locale, "intl.css"))
else:
markup = ''
markup = ""
return jinja2.Markup(markup)
@ -163,14 +163,14 @@ def field_with_attrs(bfield, **kwargs):
def platform_img(ctx, url, optional_attributes=None):
optional_attributes = optional_attributes or {}
img_urls = {}
platforms = optional_attributes.pop('platforms', ALL_FX_PLATFORMS)
add_high_res = optional_attributes.pop('high-res', False)
is_l10n = optional_attributes.pop('l10n', False)
platforms = optional_attributes.pop("platforms", ALL_FX_PLATFORMS)
add_high_res = optional_attributes.pop("high-res", False)
is_l10n = optional_attributes.pop("l10n", False)
for platform in platforms:
img_urls[platform] = add_string_to_image_url(url, platform)
if add_high_res:
img_urls[platform + '-high-res'] = convert_to_high_res(img_urls[platform])
img_urls[platform + "-high-res"] = convert_to_high_res(img_urls[platform])
img_attrs = {}
for platform, image in img_urls.items():
@ -178,22 +178,23 @@ def platform_img(ctx, url, optional_attributes=None):
image = l10n_img_file_name(ctx, _strip_img_prefix(image))
if find_static(image):
key = 'data-src-' + platform
key = "data-src-" + platform
img_attrs[key] = static(image)
if add_high_res:
img_attrs['data-high-res'] = 'true'
img_attrs["data-high-res"] = "true"
img_attrs.update(optional_attributes)
attrs = ' '.join('%s="%s"' % (attr, val)
for attr, val in img_attrs.items())
attrs = " ".join('%s="%s"' % (attr, val) for attr, val in img_attrs.items())
# Don't download any image until the javascript sets it based on
# data-src so we can do platform detection. If no js, show the
# windows version.
markup = ('<img class="platform-img js" src="" data-processed="false" {attrs}>'
'<noscript><img class="platform-img win" src="{win_src}" {attrs}>'
'</noscript>').format(attrs=attrs, win_src=img_attrs['data-src-windows'])
markup = (
'<img class="platform-img js" src="" data-processed="false" {attrs}>'
'<noscript><img class="platform-img win" src="{win_src}" {attrs}>'
"</noscript>"
).format(attrs=attrs, win_src=img_attrs["data-src-windows"])
return jinja2.Markup(markup)
@ -201,7 +202,7 @@ def platform_img(ctx, url, optional_attributes=None):
@library.global_function
@jinja2.contextfunction
def high_res_img(ctx, url, optional_attributes=None):
if optional_attributes and optional_attributes.pop('l10n', False) is True:
if optional_attributes and optional_attributes.pop("l10n", False) is True:
url = _strip_img_prefix(url)
url_high_res = convert_to_high_res(url)
url = l10n_img(ctx, url)
@ -212,35 +213,32 @@ def high_res_img(ctx, url, optional_attributes=None):
url_high_res = static(url_high_res)
if optional_attributes:
class_name = optional_attributes.pop('class', '')
attrs = ' ' + ' '.join('%s="%s"' % (attr, val)
for attr, val in optional_attributes.items())
class_name = optional_attributes.pop("class", "")
attrs = " " + " ".join('%s="%s"' % (attr, val) for attr, val in optional_attributes.items())
else:
class_name = ''
attrs = ''
class_name = ""
attrs = ""
# Use native srcset attribute for high res images
markup = ('<img class="{class_name}" src="{url}" '
'srcset="{url_high_res} 1.5x"'
'{attrs}>').format(url=url, url_high_res=url_high_res,
attrs=attrs, class_name=class_name)
markup = ('<img class="{class_name}" src="{url}" ' 'srcset="{url_high_res} 1.5x"' "{attrs}>").format(
url=url, url_high_res=url_high_res, attrs=attrs, class_name=class_name
)
return jinja2.Markup(markup)
@library.global_function
@jinja2.contextfunction
def lazy_img(ctx, image_url, placeholder_url, include_highres_image=False,
optional_attributes=None, highres_image_url=None):
def lazy_img(ctx, image_url, placeholder_url, include_highres_image=False, optional_attributes=None, highres_image_url=None):
placeholder = static(placeholder_url)
external_img = re.match(r'^https?://', image_url, flags=re.I)
external_img = re.match(r"^https?://", image_url, flags=re.I)
if include_highres_image and not external_img:
image_high_res = static(convert_to_high_res(image_url))
srcset = f'data-srcset="{image_high_res} 2x"'
else:
srcset = ''
srcset = ""
# image could be external
if not external_img:
@ -250,21 +248,22 @@ def lazy_img(ctx, image_url, placeholder_url, include_highres_image=False,
srcset = f'data-srcset="{highres_image_url} 2x"'
if optional_attributes:
class_name = optional_attributes.pop('class', 'lazy-image')
alt_text = optional_attributes.pop('alt', '')
attrs = ' '.join('%s="%s"' % (attr, val)
for attr, val in optional_attributes.items())
class_name = optional_attributes.pop("class", "lazy-image")
alt_text = optional_attributes.pop("alt", "")
attrs = " ".join('%s="%s"' % (attr, val) for attr, val in optional_attributes.items())
else:
class_name = 'lazy-image'
alt_text = ''
attrs = ''
class_name = "lazy-image"
alt_text = ""
attrs = ""
markup = (f'<div class="lazy-image-container">'
f'<img class="{class_name}" src="{placeholder}" data-src="{image_url}" {srcset} alt="{alt_text}" {attrs}>'
f'<noscript>'
f'<img class="{class_name}" src="{image_url}" {srcset} alt="{alt_text}" {attrs}>'
f'</noscript>'
f'</div>')
markup = (
f'<div class="lazy-image-container">'
f'<img class="{class_name}" src="{placeholder}" data-src="{image_url}" {srcset} alt="{alt_text}" {attrs}>'
f"<noscript>"
f'<img class="{class_name}" src="{image_url}" {srcset} alt="{alt_text}" {attrs}>'
f"</noscript>"
f"</div>"
)
return jinja2.Markup(markup)
@ -292,40 +291,37 @@ def video(ctx, *args, **kwargs):
mp4, ogv. If you want anything else, patches welcome.
"""
filetypes = ('webm', 'ogv', 'mp4')
mime = {'webm': 'video/webm',
'ogv': 'video/ogg; codecs="theora, vorbis"',
'mp4': 'video/mp4'}
filetypes = ("webm", "ogv", "mp4")
mime = {"webm": "video/webm", "ogv": 'video/ogg; codecs="theora, vorbis"', "mp4": "video/mp4"}
videos = {}
for v in args:
try:
ext = v.rsplit('.', 1)[1].lower()
ext = v.rsplit(".", 1)[1].lower()
except IndexError:
# TODO: Perhaps we don't want to swallow this quietly in the future
continue
if ext not in filetypes:
continue
videos[ext] = (v if 'prefix' not in kwargs else
urllib.parse.urljoin(kwargs['prefix'], v))
videos[ext] = v if "prefix" not in kwargs else urllib.parse.urljoin(kwargs["prefix"], v)
if not videos:
return ''
return ""
# defaults
data = {
'w': 640,
'h': 360,
'autoplay': False,
'preload': False,
'id': 'htmlPlayer',
'fluent_l10n': ctx['fluent_l10n'],
"w": 640,
"h": 360,
"autoplay": False,
"preload": False,
"id": "htmlPlayer",
"fluent_l10n": ctx["fluent_l10n"],
}
data.update(**kwargs)
data.update(filetypes=filetypes, mime=mime, videos=videos)
return jinja2.Markup(render_to_string('mozorg/videotag.html', data, request=ctx['request']))
return jinja2.Markup(render_to_string("mozorg/videotag.html", data, request=ctx["request"]))
@library.global_function
@ -358,16 +354,16 @@ def press_blog_url(ctx):
https://blog.mozilla.org/press-de/
"""
locale = getattr(ctx['request'], 'locale', 'en-US')
locale = getattr(ctx["request"], "locale", "en-US")
if locale not in settings.PRESS_BLOGS:
locale = 'en-US'
locale = "en-US"
return settings.PRESS_BLOG_ROOT + settings.PRESS_BLOGS[locale]
@library.global_function
@jinja2.contextfunction
def donate_url(ctx, source=''):
def donate_url(ctx, source=""):
"""Output a donation link to the donation page formatted using settings.DONATE_PARAMS
Examples
@ -391,18 +387,20 @@ def donate_url(ctx, source=''):
https://donate.mozilla.org/?utm_source=mozilla.org&utm_medium=referral&utm_content=footer
"""
locale = getattr(ctx['request'], 'locale', 'en-US')
locale = getattr(ctx["request"], "locale", "en-US")
donate_url_params = settings.DONATE_PARAMS.get(
locale, settings.DONATE_PARAMS['en-US'])
donate_url_params = settings.DONATE_PARAMS.get(locale, settings.DONATE_PARAMS["en-US"])
donate_url = settings.DONATE_LINK_UNKNOWN.format(source=source)
if locale in settings.DONATE_PARAMS:
donate_url = settings.DONATE_LINK.format(
locale=locale, presets=donate_url_params['presets'],
default=donate_url_params['default'], source=source,
currency=donate_url_params['currency'])
locale=locale,
presets=donate_url_params["presets"],
default=donate_url_params["default"],
source=source,
currency=donate_url_params["currency"],
)
return donate_url
@ -437,9 +435,9 @@ def firefox_twitter_url(ctx):
https://twitter.com/firefoxbrasil
"""
locale = getattr(ctx['request'], 'locale', 'en-US')
locale = getattr(ctx["request"], "locale", "en-US")
if locale not in settings.FIREFOX_TWITTER_ACCOUNTS:
locale = 'en-US'
locale = "en-US"
return settings.FIREFOX_TWITTER_ACCOUNTS[locale]
@ -470,9 +468,9 @@ def firefox_instagram_url(ctx):
https://www.instagram.com/unfcktheinternet/
"""
locale = getattr(ctx['request'], 'locale', 'en-US')
locale = getattr(ctx["request"], "locale", "en-US")
if locale not in settings.FIREFOX_INSTAGRAM_ACCOUNTS:
locale = 'en-US'
locale = "en-US"
return settings.FIREFOX_INSTAGRAM_ACCOUNTS[locale]
@ -499,12 +497,12 @@ def absolute_url(url):
{% endfilter %}
"""
if url.startswith('//'):
prefix = 'https:'
elif url.startswith('/'):
if url.startswith("//"):
prefix = "https:"
elif url.startswith("/"):
prefix = settings.CANONICAL_URL
else:
prefix = ''
prefix = ""
return prefix + url
@ -543,11 +541,11 @@ def firefox_ios_url(ctx, ct_param=None):
https://itunes.apple.com/jp/app/firefox-private-safe-browser/id989804926
"""
locale = getattr(ctx['request'], 'locale', 'en-US')
link = firefox_ios.get_download_url('release', locale)
locale = getattr(ctx["request"], "locale", "en-US")
link = firefox_ios.get_download_url("release", locale)
if ct_param:
return link + '?ct=' + ct_param
return link + "?ct=" + ct_param
return link
@ -593,7 +591,7 @@ def slugify(text):
@library.filter
def bleach_tags(text):
return bleach.clean(text, tags=[], strip=True).replace('&amp;', '&')
return bleach.clean(text, tags=[], strip=True).replace("&amp;", "&")
# from jingo
@ -624,39 +622,39 @@ def f(s, *args, **kwargs):
def datetime(t, fmt=None):
"""Call ``datetime.strftime`` with the given format string."""
if fmt is None:
fmt = _(u'%B %e, %Y')
fmt = _("%B %e, %Y")
if not six.PY3:
# The datetime.strftime function strictly does not
# support Unicode in Python 2 but is Unicode only in 3.x.
fmt = fmt.encode('utf-8')
return smart_text(t.strftime(fmt)) if t else ''
fmt = fmt.encode("utf-8")
return smart_text(t.strftime(fmt)) if t else ""
@library.filter
def ifeq(a, b, text):
"""Return ``text`` if ``a == b``."""
return jinja2.Markup(text if a == b else '')
return jinja2.Markup(text if a == b else "")
@library.global_function
@jinja2.contextfunction
def app_store_url(ctx, product):
"""Returns a localised app store URL for a given product"""
base_url = getattr(settings, f'APPLE_APPSTORE_{product.upper()}_LINK')
locale = getattr(ctx['request'], 'locale', 'en-US')
base_url = getattr(settings, f"APPLE_APPSTORE_{product.upper()}_LINK")
locale = getattr(ctx["request"], "locale", "en-US")
countries = settings.APPLE_APPSTORE_COUNTRY_MAP
if locale in countries:
return base_url.format(country=countries[locale])
else:
return base_url.replace('/{country}/', '/')
return base_url.replace("/{country}/", "/")
@library.global_function
@jinja2.contextfunction
def play_store_url(ctx, product):
"""Returns a localised play store URL for a given product"""
base_url = getattr(settings, f'GOOGLE_PLAY_{product.upper()}_LINK')
return f'{base_url}&hl={lang_short(ctx)}'
base_url = getattr(settings, f"GOOGLE_PLAY_{product.upper()}_LINK")
return f"{base_url}&hl={lang_short(ctx)}"
@library.global_function
@ -667,46 +665,46 @@ def structured_data_id(ctx, id, domain=None):
a supplied id e.g. https://www.mozilla.org/#firefoxbrowser
"""
canonical = settings.CANONICAL_URL if not domain else domain
locale = getattr(ctx['request'], 'locale', 'en-US')
suffix = ''
locale = getattr(ctx["request"], "locale", "en-US")
suffix = ""
if locale != 'en-US':
suffix = '-' + locale.lower()
if locale != "en-US":
suffix = "-" + locale.lower()
return canonical + '/#' + id + suffix
return canonical + "/#" + id + suffix
@library.global_function
@jinja2.contextfunction
def lang_short(ctx):
"""Returns a shortened locale code e.g. en."""
locale = getattr(ctx['request'], 'locale', 'en-US')
return locale.split('-')[0]
locale = getattr(ctx["request"], "locale", "en-US")
return locale.split("-")[0]
def _get_adjust_link(adjust_url, app_store_url, google_play_url, redirect, locale, adgroup, creative=None):
link = adjust_url
params = 'campaign=www.mozilla.org&adgroup=' + adgroup
params = "campaign=www.mozilla.org&adgroup=" + adgroup
redirect_url = None
# Get the appropriate app store URL to use as a fallback redirect.
if redirect == 'ios':
if redirect == "ios":
countries = settings.APPLE_APPSTORE_COUNTRY_MAP
if locale in countries:
redirect_url = app_store_url.format(country=countries[locale])
else:
redirect_url = app_store_url.replace('/{country}/', '/')
elif redirect == 'android':
redirect_url = app_store_url.replace("/{country}/", "/")
elif redirect == "android":
redirect_url = google_play_url
# Optional creative parameter.
if creative:
params += '&creative=' + creative
params += "&creative=" + creative
if redirect_url:
link += '?redirect=' + urlquote(redirect_url, safe='') + '&' + params
link += "?redirect=" + urlquote(redirect_url, safe="") + "&" + params
else:
link += '?' + params
link += "?" + params
return link
@ -728,7 +726,7 @@ def firefox_adjust_url(ctx, redirect, adgroup, creative=None):
adjust_url = settings.ADJUST_FIREFOX_URL
app_store_url = settings.APPLE_APPSTORE_FIREFOX_LINK
play_store_url = settings.GOOGLE_PLAY_FIREFOX_LINK
locale = getattr(ctx['request'], 'locale', 'en-US')
locale = getattr(ctx["request"], "locale", "en-US")
return _get_adjust_link(adjust_url, app_store_url, play_store_url, redirect, locale, adgroup, creative)
@ -747,11 +745,11 @@ def focus_adjust_url(ctx, redirect, adgroup, creative=None):
{{ focus_adjust_url('ios', 'fights-for-you-page') }}
"""
klar_locales = ['de']
klar_locales = ["de"]
adjust_url = settings.ADJUST_FOCUS_URL
app_store_url = settings.APPLE_APPSTORE_FOCUS_LINK
play_store_url = settings.GOOGLE_PLAY_FOCUS_LINK
locale = getattr(ctx['request'], 'locale', 'en-US')
locale = getattr(ctx["request"], "locale", "en-US")
if locale in klar_locales:
adjust_url = settings.ADJUST_KLAR_URL
@ -778,7 +776,7 @@ def pocket_adjust_url(ctx, redirect, adgroup, creative=None):
adjust_url = settings.ADJUST_POCKET_URL
app_store_url = settings.APPLE_APPSTORE_POCKET_LINK
play_store_url = settings.GOOGLE_PLAY_POCKET_LINK
locale = getattr(ctx['request'], 'locale', 'en-US')
locale = getattr(ctx["request"], "locale", "en-US")
return _get_adjust_link(adjust_url, app_store_url, play_store_url, redirect, locale, adgroup, creative)
@ -800,51 +798,58 @@ def lockwise_adjust_url(ctx, redirect, adgroup, creative=None):
adjust_url = settings.ADJUST_LOCKWISE_URL
app_store_url = settings.APPLE_APPSTORE_LOCKWISE_LINK
play_store_url = settings.GOOGLE_PLAY_LOCKWISE_LINK
locale = getattr(ctx['request'], 'locale', 'en-US')
locale = getattr(ctx["request"], "locale", "en-US")
return _get_adjust_link(adjust_url, app_store_url, play_store_url, redirect, locale, adgroup, creative)
def _fxa_product_url(product_url, entrypoint, optional_parameters=None):
separator = '&' if '?' in product_url else '?'
url = f'{product_url}{separator}entrypoint={entrypoint}&form_type=button&utm_source={entrypoint}&utm_medium=referral'
separator = "&" if "?" in product_url else "?"
url = f"{product_url}{separator}entrypoint={entrypoint}&form_type=button&utm_source={entrypoint}&utm_medium=referral"
if optional_parameters:
params = '&'.join('%s=%s' % (param, val) for param, val in optional_parameters.items())
url += f'&{params}'
params = "&".join("%s=%s" % (param, val) for param, val in optional_parameters.items())
url += f"&{params}"
return url
def _fxa_product_button(product_url, entrypoint, button_text, class_name=None, is_button_class=True,
include_metrics=True, optional_parameters=None, optional_attributes=None):
def _fxa_product_button(
product_url,
entrypoint,
button_text,
class_name=None,
is_button_class=True,
include_metrics=True,
optional_parameters=None,
optional_attributes=None,
):
href = _fxa_product_url(product_url, entrypoint, optional_parameters)
css_class = 'js-fxa-cta-link'
attrs = ''
css_class = "js-fxa-cta-link"
attrs = ""
if optional_attributes:
attrs += ' '.join('%s="%s"' % (attr, val) for attr, val in optional_attributes.items())
attrs += " ".join('%s="%s"' % (attr, val) for attr, val in optional_attributes.items())
if include_metrics:
css_class += ' js-fxa-product-button'
css_class += " js-fxa-product-button"
if is_button_class:
css_class += ' mzp-c-button mzp-t-product'
css_class += " mzp-c-button mzp-t-product"
if class_name:
css_class += f' {class_name}'
css_class += f" {class_name}"
markup = (f'<a href="{href}" data-action="{settings.FXA_ENDPOINT}" class="{css_class}" {attrs}>'
f'{button_text}'
f'</a>')
markup = f'<a href="{href}" data-action="{settings.FXA_ENDPOINT}" class="{css_class}" {attrs}>' f"{button_text}" f"</a>"
return jinja2.Markup(markup)
@library.global_function
@jinja2.contextfunction
def pocket_fxa_button(ctx, entrypoint, button_text, class_name=None, is_button_class=True, include_metrics=True,
optional_parameters=None, optional_attributes=None):
def pocket_fxa_button(
ctx, entrypoint, button_text, class_name=None, is_button_class=True, include_metrics=True, optional_parameters=None, optional_attributes=None
):
"""
Render a getpocket.com link with required params for FxA authentication.
@ -856,15 +861,17 @@ def pocket_fxa_button(ctx, entrypoint, button_text, class_name=None, is_button_c
{{ pocket_fxa_button(entrypoint='mozilla.org-firefox-pocket', button_text='Try Pocket Now') }}
"""
product_url = 'https://getpocket.com/ff_signup'
return _fxa_product_button(product_url, entrypoint, button_text, class_name, is_button_class, include_metrics,
optional_parameters, optional_attributes)
product_url = "https://getpocket.com/ff_signup"
return _fxa_product_button(
product_url, entrypoint, button_text, class_name, is_button_class, include_metrics, optional_parameters, optional_attributes
)
@library.global_function
@jinja2.contextfunction
def monitor_fxa_button(ctx, entrypoint, button_text, class_name=None, is_button_class=True, include_metrics=True,
optional_parameters=None, optional_attributes=None):
def monitor_fxa_button(
ctx, entrypoint, button_text, class_name=None, is_button_class=True, include_metrics=True, optional_parameters=None, optional_attributes=None
):
"""
Render a monitor.firefox.com link with required params for FxA authentication.
@ -876,14 +883,15 @@ def monitor_fxa_button(ctx, entrypoint, button_text, class_name=None, is_button_
{{ monitor_fxa_button(entrypoint='mozilla.org-firefox-accounts', button_text='Sign In to Monitor') }}
"""
product_url = 'https://monitor.firefox.com/oauth/init'
return _fxa_product_button(product_url, entrypoint, button_text, class_name, is_button_class, include_metrics,
optional_parameters, optional_attributes)
product_url = "https://monitor.firefox.com/oauth/init"
return _fxa_product_button(
product_url, entrypoint, button_text, class_name, is_button_class, include_metrics, optional_parameters, optional_attributes
)
@library.global_function
@jinja2.contextfunction
def fxa_link_fragment(ctx, entrypoint, action='signup', optional_parameters=None):
def fxa_link_fragment(ctx, entrypoint, action="signup", optional_parameters=None):
"""
Returns `href` and `data-mozillaonline-link` attributes as a string fragment.
This is useful for inline links that appear inside a string of localized copy,
@ -900,22 +908,30 @@ def fxa_link_fragment(ctx, entrypoint, action='signup', optional_parameters=None
<p>Already have an account? <a {{ sign_in }} class="{{ class_name }}">Sign In</a> to start syncing.</p>
"""
if action == 'email':
action = '?action=email'
if action == "email":
action = "?action=email"
fxa_url = _fxa_product_url(f'{settings.FXA_ENDPOINT}{action}', entrypoint, optional_parameters)
mozillaonline_url = _fxa_product_url(f'{settings.FXA_ENDPOINT_MOZILLAONLINE}{action}', entrypoint, optional_parameters)
fxa_url = _fxa_product_url(f"{settings.FXA_ENDPOINT}{action}", entrypoint, optional_parameters)
mozillaonline_url = _fxa_product_url(f"{settings.FXA_ENDPOINT_MOZILLAONLINE}{action}", entrypoint, optional_parameters)
markup = (f'href="{fxa_url}" data-mozillaonline-link="{mozillaonline_url}" '
f'data-mozillaonline-action="{settings.FXA_ENDPOINT_MOZILLAONLINE}"')
markup = f'href="{fxa_url}" data-mozillaonline-link="{mozillaonline_url}" ' f'data-mozillaonline-action="{settings.FXA_ENDPOINT_MOZILLAONLINE}"'
return jinja2.Markup(markup)
@library.global_function
@jinja2.contextfunction
def fxa_button(ctx, entrypoint, button_text, action='signup', class_name=None, is_button_class=True,
include_metrics=True, optional_parameters=None, optional_attributes=None):
def fxa_button(
ctx,
entrypoint,
button_text,
action="signup",
class_name=None,
is_button_class=True,
include_metrics=True,
optional_parameters=None,
optional_attributes=None,
):
"""
Render a accounts.firefox.com link with required params for FxA authentication.
@ -928,19 +944,20 @@ def fxa_button(ctx, entrypoint, button_text, action='signup', class_name=None, i
{{ fxa_button(entrypoint='mozilla.org-firefox-accounts', button_text='Sign In') }}
"""
if action == 'email':
action = '?action=email'
if action == "email":
action = "?action=email"
product_url = f'{settings.FXA_ENDPOINT}{action}'
mozillaonline_product_url = f'{settings.FXA_ENDPOINT_MOZILLAONLINE}{action}'
product_url = f"{settings.FXA_ENDPOINT}{action}"
mozillaonline_product_url = f"{settings.FXA_ENDPOINT_MOZILLAONLINE}{action}"
mozillaonline_attribute = {
'data-mozillaonline-link': _fxa_product_url(mozillaonline_product_url, entrypoint, optional_parameters),
'data-mozillaonline-action': settings.FXA_ENDPOINT_MOZILLAONLINE
"data-mozillaonline-link": _fxa_product_url(mozillaonline_product_url, entrypoint, optional_parameters),
"data-mozillaonline-action": settings.FXA_ENDPOINT_MOZILLAONLINE,
}
optional_attributes = optional_attributes or {}
optional_attributes.update(mozillaonline_attribute)
return _fxa_product_button(product_url, entrypoint, button_text, class_name, is_button_class, include_metrics,
optional_parameters, optional_attributes)
return _fxa_product_button(
product_url, entrypoint, button_text, class_name, is_button_class, include_metrics, optional_parameters, optional_attributes
)

Просмотреть файл

@ -9,20 +9,18 @@ from jinja2 import Markup
from qrcode.image.svg import SvgPathImage
cache = caches['qrcode']
cache = caches["qrcode"]
@library.global_function
def qrcode(data, box_size=20):
key = sha1(f'{data}-{box_size}'.encode('utf-8')).hexdigest()
key = sha1(f"{data}-{box_size}".encode("utf-8")).hexdigest()
svg = cache.get(key)
if not svg:
img = qr.make(data,
image_factory=SvgPathImage,
box_size=box_size)
img = qr.make(data, image_factory=SvgPathImage, box_size=box_size)
svg = BytesIO()
img.save(svg)
svg = svg.getvalue().decode('utf-8')
svg = svg.getvalue().decode("utf-8")
cache.set(key, svg)
return Markup(svg)

Просмотреть файл

@ -6,7 +6,7 @@ from contextlib import contextmanager
from django.test import RequestFactory, TestCase as DjTestCase
from bedrock.base.urlresolvers import (get_url_prefix, Prefixer, set_url_prefix)
from bedrock.base.urlresolvers import get_url_prefix, Prefixer, set_url_prefix
from lib.l10n_utils import translation
@ -19,7 +19,7 @@ class TestCase(DjTestCase):
old_prefix = get_url_prefix()
old_locale = translation.get_language()
rf = RequestFactory()
set_url_prefix(Prefixer(rf.get('/%s/' % (locale,))))
set_url_prefix(Prefixer(rf.get("/%s/" % (locale,))))
translation.activate(locale)
yield
set_url_prefix(old_prefix)

Просмотреть файл

@ -11,11 +11,10 @@ from bedrock.mozorg.tests import TestCase
from bedrock.mozorg.management.commands import update_product_details_files
PD_REPO_TEST_PATH = Path(__file__).parent.joinpath('test_pd_repo')
PD_REPO_TEST_PATH = Path(__file__).parent.joinpath("test_pd_repo")
@override_settings(PROD_DETAILS_STORAGE='PDDatabaseStorage',
PROD_DETAILS_TEST_DIR=str(PD_REPO_TEST_PATH.joinpath('product-details')))
@override_settings(PROD_DETAILS_STORAGE="PDDatabaseStorage", PROD_DETAILS_TEST_DIR=str(PD_REPO_TEST_PATH.joinpath("product-details")))
class TestUpdateProductDetailsFiles(TestCase):
def setUp(self):
self.command = update_product_details_files.Command()
@ -23,33 +22,28 @@ class TestUpdateProductDetailsFiles(TestCase):
self.command.repo.path_str = str(PD_REPO_TEST_PATH)
def test_handle_diff_loads_all(self):
with patch.multiple(self.command, update_file_data=DEFAULT, validate_data=DEFAULT,
file_storage=DEFAULT, load_changes=DEFAULT, repo=DEFAULT):
options = dict(quiet=False, database='default', force=False)
with patch.multiple(self.command, update_file_data=DEFAULT, validate_data=DEFAULT, file_storage=DEFAULT, load_changes=DEFAULT, repo=DEFAULT):
options = dict(quiet=False, database="default", force=False)
self.command.update_file_data.return_value = True
self.command.handle(**options)
assert self.command.file_storage.all_json_files.called
self.command.load_changes. \
assert_called_with(options, self.command.file_storage.all_json_files())
self.command.load_changes.assert_called_with(options, self.command.file_storage.all_json_files())
assert self.command.repo.set_db_latest.called
def test_handle_error_no_set_latest(self):
with patch.multiple(self.command, update_file_data=DEFAULT, validate_data=DEFAULT,
file_storage=DEFAULT, load_changes=DEFAULT, repo=DEFAULT):
options = dict(quiet=False, database='default', force=False)
with patch.multiple(self.command, update_file_data=DEFAULT, validate_data=DEFAULT, file_storage=DEFAULT, load_changes=DEFAULT, repo=DEFAULT):
options = dict(quiet=False, database="default", force=False)
self.command.update_file_data.return_value = True
self.command.load_changes.side_effect = Exception('broke yo')
self.command.load_changes.side_effect = Exception("broke yo")
with self.assertRaises(Exception):
self.command.handle(**options)
assert self.command.file_storage.all_json_files.called
self.command.load_changes.\
assert_called_with(options, self.command.file_storage.all_json_files())
self.command.load_changes.assert_called_with(options, self.command.file_storage.all_json_files())
assert not self.command.repo.set_db_latest.called
def test_handle_no_diff_does_nothing(self):
with patch.multiple(self.command, update_file_data=DEFAULT, validate_data=DEFAULT,
file_storage=DEFAULT, load_changes=DEFAULT, repo=DEFAULT):
options = dict(quiet=False, database='default', force=False)
with patch.multiple(self.command, update_file_data=DEFAULT, validate_data=DEFAULT, file_storage=DEFAULT, load_changes=DEFAULT, repo=DEFAULT):
options = dict(quiet=False, database="default", force=False)
self.command.update_file_data.return_value = False
self.command.handle(**options)
assert not self.command.file_storage.all_json_files.called

Просмотреть файл

@ -14,7 +14,7 @@ class TestFunnelcakeParam(TestCase):
def setUp(self):
self.rf = RequestFactory()
def _funnelcake(self, url='/', **kwargs):
def _funnelcake(self, url="/", **kwargs):
return funnelcake_param(self.rf.get(url, kwargs))
def test_funnelcake_param_noop(self):
@ -23,22 +23,22 @@ class TestFunnelcakeParam(TestCase):
def test_funnelcake_param_f(self):
"""Should inject funnelcake into context."""
assert self._funnelcake(f='5') == {'funnelcake_id': '5'}
assert self._funnelcake(f='234') == {'funnelcake_id': '234'}
assert self._funnelcake(f="5") == {"funnelcake_id": "5"}
assert self._funnelcake(f="234") == {"funnelcake_id": "234"}
def test_funnelcake_param_bad(self):
"""Should not inject bad funnelcake into context."""
assert self._funnelcake(f='5dude') == {}
assert self._funnelcake(f='123456') == {}
assert self._funnelcake(f="5dude") == {}
assert self._funnelcake(f="123456") == {}
def test_funnelcake_param_increment_installer_help(self):
"""FC param should be +1 on the firefox/installer-help/ page.
Bug 933852.
"""
url = reverse('firefox.installer-help')
ctx = self._funnelcake(url, f='20')
assert ctx['funnelcake_id'] == '21'
url = reverse("firefox.installer-help")
ctx = self._funnelcake(url, f="20")
assert ctx["funnelcake_id"] == "21"
ctx = self._funnelcake(url, f='10')
assert ctx['funnelcake_id'] == '11'
ctx = self._funnelcake(url, f="10")
assert ctx["funnelcake_id"] == "11"

Просмотреть файл

@ -13,46 +13,57 @@ from bedrock.mozorg.tests import TestCase
class TestCredits(TestCase):
def setUp(self):
self.credits_file = credits.CreditsFile('credits')
self.credits_file = credits.CreditsFile("credits")
self.credits_file.clear_cache()
def test_credits_list(self):
self.credits_file.read = Mock(return_value=dedent("""\
self.credits_file.read = Mock(
return_value=dedent(
"""\
The Dude,Dude
Walter Sobchak,Sobchak
Theodore Donald Kerabatsos,Kerabatsos
Tantek Çelik,Çelik
"""))
self.assertListEqual(self.credits_file.rows, [
['Tantek Çelik', 'CELIK'],
['The Dude', 'DUDE'],
['Theodore Donald Kerabatsos', 'KERABATSOS'],
['Walter Sobchak', 'SOBCHAK'],
])
"""
)
)
self.assertListEqual(
self.credits_file.rows,
[
["Tantek Çelik", "CELIK"],
["The Dude", "DUDE"],
["Theodore Donald Kerabatsos", "KERABATSOS"],
["Walter Sobchak", "SOBCHAK"],
],
)
def test_credits_ordered_no_sortkey(self):
"""Should give an ordered dict or ordered lists keyed on first letter of name."""
self.credits_file.readlines = Mock(return_value=[
'Bunny Lebowski',
'Maude Lebowski',
'Jeffrey Lebowski',
'Uli Kunkel',
'The Dude',
'Walter Sobchak',
'Theodore Donald Kerabatsos',
])
self.credits_file.readlines = Mock(
return_value=[
"Bunny Lebowski",
"Maude Lebowski",
"Jeffrey Lebowski",
"Uli Kunkel",
"The Dude",
"Walter Sobchak",
"Theodore Donald Kerabatsos",
]
)
good_names = OrderedDict()
good_names['B'] = ['Bunny Lebowski']
good_names['J'] = ['Jeffrey Lebowski']
good_names['M'] = ['Maude Lebowski']
good_names['T'] = ['The Dude', 'Theodore Donald Kerabatsos']
good_names['U'] = ['Uli Kunkel']
good_names['W'] = ['Walter Sobchak']
good_names["B"] = ["Bunny Lebowski"]
good_names["J"] = ["Jeffrey Lebowski"]
good_names["M"] = ["Maude Lebowski"]
good_names["T"] = ["The Dude", "Theodore Donald Kerabatsos"]
good_names["U"] = ["Uli Kunkel"]
good_names["W"] = ["Walter Sobchak"]
self.assertEqual(self.credits_file.ordered, good_names)
def test_credits_ordered(self):
"""Should give an ordered dict or ordered lists keyed on first letter of sortkey."""
self.credits_file.read = Mock(return_value=dedent("""\
self.credits_file.read = Mock(
return_value=dedent(
"""\
Bunny Lebowski,Lebowski Bunny
Maude Lebowski,Lebowski Maude
Jeffrey Lebowski,Lebowski Jeffrey
@ -61,30 +72,34 @@ class TestCredits(TestCase):
Walter Sobchak,Sobchak
Theodore Donald Kerabatsos,Kerabatsos
Tantek Çelik,Çelik
"""))
"""
)
)
good_names = OrderedDict()
good_names['C'] = ['Tantek Çelik']
good_names['D'] = ['The Dude']
good_names['K'] = ['Theodore Donald Kerabatsos', 'Uli Kunkel']
good_names['L'] = ['Bunny Lebowski', 'Jeffrey Lebowski', 'Maude Lebowski']
good_names['S'] = ['Walter Sobchak']
good_names["C"] = ["Tantek Çelik"]
good_names["D"] = ["The Dude"]
good_names["K"] = ["Theodore Donald Kerabatsos", "Uli Kunkel"]
good_names["L"] = ["Bunny Lebowski", "Jeffrey Lebowski", "Maude Lebowski"]
good_names["S"] = ["Walter Sobchak"]
self.assertEqual(self.credits_file.ordered, good_names)
def test_credits_ordered_skips(self):
"""Should skip lines with more than 2 items."""
self.credits_file.readlines = Mock(return_value=[
'Bunny Lebowski,Lebowski Bunny',
'Maude Lebowski,Lebowski Maude',
'Jeffrey Lebowski,Lebowski Jeffrey',
'Karl Hungus,Karl,Inappropriate',
'Uli Kunkel,Kunkel',
'The Dude,Dude',
'Walter Sobchak,Sobchak',
'Theodore Donald Kerabatsos,Kerabatsos',
])
self.credits_file.readlines = Mock(
return_value=[
"Bunny Lebowski,Lebowski Bunny",
"Maude Lebowski,Lebowski Maude",
"Jeffrey Lebowski,Lebowski Jeffrey",
"Karl Hungus,Karl,Inappropriate",
"Uli Kunkel,Kunkel",
"The Dude,Dude",
"Walter Sobchak,Sobchak",
"Theodore Donald Kerabatsos,Kerabatsos",
]
)
good_names = OrderedDict()
good_names['D'] = ['The Dude']
good_names['K'] = ['Theodore Donald Kerabatsos', 'Uli Kunkel']
good_names['L'] = ['Bunny Lebowski', 'Jeffrey Lebowski', 'Maude Lebowski']
good_names['S'] = ['Walter Sobchak']
good_names["D"] = ["The Dude"]
good_names["K"] = ["Theodore Donald Kerabatsos", "Uli Kunkel"]
good_names["L"] = ["Bunny Lebowski", "Jeffrey Lebowski", "Maude Lebowski"]
good_names["S"] = ["Walter Sobchak"]
self.assertEqual(self.credits_file.ordered, good_names)

Просмотреть файл

@ -20,13 +20,13 @@ class ViewDecoratorTests(TestCase):
"""
Should have appropriate Cache-Control and Expires headers.
"""
test_request = self.rf.get('/hi-there-dude/')
test_request = self.rf.get("/hi-there-dude/")
resp = view(test_request)
num_seconds = hours * 60 * 60
self.assertEqual(resp['cache-control'], 'max-age=%d' % num_seconds)
self.assertEqual(resp["cache-control"], "max-age=%d" % num_seconds)
now_date = floor(time.time())
exp_date = parse_http_date(resp['expires'])
exp_date = parse_http_date(resp["expires"])
self.assertAlmostEqual(now_date + num_seconds, exp_date, delta=2)
def test_cache_headers_48_hours(self):

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -6,17 +6,17 @@ from bedrock.mozorg.tests import TestCase
from bedrock.mozorg.templatetags.qrcode import qrcode
@patch('bedrock.mozorg.templatetags.qrcode.cache')
@patch('bedrock.mozorg.templatetags.qrcode.qr')
@patch("bedrock.mozorg.templatetags.qrcode.cache")
@patch("bedrock.mozorg.templatetags.qrcode.qr")
class TestQRCode(TestCase):
def test_qrcode_cache_cold(self, qr_mock, cache_mock):
cache_mock.get.return_value = None
data = 'https://dude.abide'
data = "https://dude.abide"
qrcode(data, 20)
qr_mock.make.assert_called_with(data, image_factory=SvgPathImage, box_size=20)
def test_qrcode_cache_warm(self, qr_mock, cache_mock):
cache_mock.get.return_value = '<svg>stuff</svg>'
data = 'https://dude.abide'
cache_mock.get.return_value = "<svg>stuff</svg>"
data = "https://dude.abide"
qrcode(data, 20)
qr_mock.make.assert_not_called()

Просмотреть файл

@ -14,8 +14,8 @@ class TestPageNode(TestCase):
If a node is given children in the constructor, the children must mark
the node as their parent.
"""
children = [PageNode('test'), PageNode('test2')]
parent = PageRoot('parent', children=children)
children = [PageNode("test"), PageNode("test2")]
parent = PageRoot("parent", children=children)
for child in children:
assert child.parent == parent
@ -24,44 +24,41 @@ class TestPageNode(TestCase):
full_path should return the path of this node and all of its parents
joined by slashes.
"""
child = PageNode('test', path='asdf')
PageRoot('test', path='blah', children=[
PageNode('test', path='whoo', children=[child])
])
assert child.full_path == 'blah/whoo/asdf'
child = PageNode("test", path="asdf")
PageRoot("test", path="blah", children=[PageNode("test", path="whoo", children=[child])])
assert child.full_path == "blah/whoo/asdf"
def test_full_path_empty(self):
"""
If one of a node's parents have an empty path, they should not be
included in the full path.
"""
child = PageNode('test', path='asdf')
PageRoot('', path='blah', children=[PageNode('', children=[child])])
assert child.full_path == 'blah/asdf'
child = PageNode("test", path="asdf")
PageRoot("", path="blah", children=[PageNode("", children=[child])])
assert child.full_path == "blah/asdf"
@patch('bedrock.mozorg.hierarchy.page')
@patch("bedrock.mozorg.hierarchy.page")
def test_page(self, page):
"""
If a pagenode is given a template, it should provide a page for
inclusion in a urlconf.
"""
page.return_value = 'testreturn'
assert PageNode('test').page is None
page.return_value = "testreturn"
assert PageNode("test").page is None
node = PageNode('test', path='blah', template='test.html')
parent = PageRoot('testparent', path='yo', children=[node])
assert node.page == 'testreturn'
page.assert_called_with('yo/blah', 'test.html', node_root=parent,
node=node)
node = PageNode("test", path="blah", template="test.html")
parent = PageRoot("testparent", path="yo", children=[node])
assert node.page == "testreturn"
page.assert_called_with("yo/blah", "test.html", node_root=parent, node=node)
def test_path_to_root(self):
"""
path_to_root should return an iterable of nodes following the route from
the child node to the root of the tree.
"""
child1 = PageNode('test')
child2 = PageNode('test', children=[child1])
root = PageRoot('test', children=[child2, PageNode('test')])
child1 = PageNode("test")
child2 = PageNode("test", children=[child1])
root = PageRoot("test", children=[child2, PageNode("test")])
assert list(child1.path_to_root) == [child1, child2, root]
def test_breadcrumbs(self):
@ -69,23 +66,23 @@ class TestPageNode(TestCase):
breadcrumbs should return a list of nodes following the path from the
root to the child node.
"""
child1 = PageNode('test')
child2 = PageNode('test', children=[child1])
root = PageRoot('test', children=[child2, PageNode('test')])
child1 = PageNode("test")
child2 = PageNode("test", children=[child1])
root = PageRoot("test", children=[child2, PageNode("test")])
assert list(child1.breadcrumbs) == [root, child2, child1]
def test_root(self):
"""root should return the root of the page tree."""
child1 = PageNode('test')
child2 = PageNode('test', children=[child1])
root = PageRoot('test', children=[child2, PageNode('test')])
child1 = PageNode("test")
child2 = PageNode("test", children=[child1])
root = PageRoot("test", children=[child2, PageNode("test")])
assert child1.root == root
def test_no_root(self):
"""If the root of a tree is not a PageRoot, raise a ValueError."""
child1 = PageNode('test')
child2 = PageNode('test', children=[child1])
PageNode('test', children=[child2, PageNode('test')])
child1 = PageNode("test")
child2 = PageNode("test", children=[child1])
PageNode("test", children=[child2, PageNode("test")])
self.assertRaises(ValueError, lambda: child1.root)
def test_previous(self):
@ -93,9 +90,9 @@ class TestPageNode(TestCase):
Previous should return the previous sibling node, or None if one doesn't
exist.
"""
child1 = PageNode('', template='test1.html')
child2 = PageNode('', template='test2.html')
PageRoot('', children=[child1, child2])
child1 = PageNode("", template="test1.html")
child2 = PageNode("", template="test2.html")
PageRoot("", children=[child1, child2])
assert child2.previous == child1
assert child1.previous is None
@ -112,19 +109,18 @@ class TestPageNode(TestCase):
# O O O
# / / \ / \
# c1 c2 c3 c4 O
child1 = PageNode('', template='test1.html')
child2 = PageNode('', template='test2.html')
child3 = PageNode('', template='test3.html')
child4 = PageNode('', template='test4.html')
root = PageRoot('', template='root.html', children=[
PageNode('', children=[
PageNode('', children=[child1])
]),
PageNode('', children=[
PageNode('', children=[child2, child3]),
PageNode('', children=[child4, PageNode('')])
])
])
child1 = PageNode("", template="test1.html")
child2 = PageNode("", template="test2.html")
child3 = PageNode("", template="test3.html")
child4 = PageNode("", template="test4.html")
root = PageRoot(
"",
template="root.html",
children=[
PageNode("", children=[PageNode("", children=[child1])]),
PageNode("", children=[PageNode("", children=[child2, child3]), PageNode("", children=[child4, PageNode("")])]),
],
)
assert root.previous is None
assert child1.previous == root
assert child2.previous == child1
@ -135,9 +131,9 @@ class TestPageNode(TestCase):
"""
Next should return the next sibling node, or None if one doesn't exist.
"""
child1 = PageNode('', template='test1.html')
child2 = PageNode('', template='test1.html')
PageRoot('', children=[child1, child2])
child1 = PageNode("", template="test1.html")
child2 = PageNode("", template="test1.html")
PageRoot("", children=[child1, child2])
assert child1.next == child2
assert child2.next is None
@ -154,67 +150,65 @@ class TestPageNode(TestCase):
# O O O
# / / \ / \
# c1 c2 c3 c4 O
child1 = PageNode('', template='test1.html')
child2 = PageNode('', template='test2.html')
child3 = PageNode('', template='test3.html')
child4 = PageNode('', template='test4.html')
root = PageRoot('', template='root.html', children=[
PageNode('', children=[
PageNode('', children=[child1])
]),
PageNode('', children=[
PageNode('', children=[child2, child3]),
PageNode('', children=[child4, PageNode('')])
])
])
child1 = PageNode("", template="test1.html")
child2 = PageNode("", template="test2.html")
child3 = PageNode("", template="test3.html")
child4 = PageNode("", template="test4.html")
root = PageRoot(
"",
template="root.html",
children=[
PageNode("", children=[PageNode("", children=[child1])]),
PageNode("", children=[PageNode("", children=[child2, child3]), PageNode("", children=[child4, PageNode("")])]),
],
)
assert root.next == child1
assert child1.next == child2
assert child2.next == child3
assert child3.next == child4
assert child4.next is None
@patch('bedrock.mozorg.hierarchy.reverse')
@patch("bedrock.mozorg.hierarchy.reverse")
def test_url(self, reverse):
"""If a node has a page, url should return the url for that page."""
node = PageRoot('test', path='asdf/qwer', template='fake.html')
reverse.return_value = 'asdf'
assert node.url == 'asdf'
reverse.assert_called_with('fake')
node = PageRoot("test", path="asdf/qwer", template="fake.html")
reverse.return_value = "asdf"
assert node.url == "asdf"
reverse.assert_called_with("fake")
@patch('bedrock.mozorg.hierarchy.reverse')
@patch("bedrock.mozorg.hierarchy.reverse")
def test_url_child(self, reverse):
"""
If a node doesn't have a page, but has children, it should return the
url of its first child.
"""
child1 = PageNode('test', path='asdf/qwer', template='fake.html')
child2 = PageNode('test', path='bb/qr', template='fake2.html')
parent = PageRoot('', children=[child1, child2])
child1 = PageNode("test", path="asdf/qwer", template="fake.html")
child2 = PageNode("test", path="bb/qr", template="fake2.html")
parent = PageRoot("", children=[child1, child2])
reverse.return_value = 'asdf'
assert parent.url == 'asdf'
reverse.assert_called_with('fake')
reverse.return_value = "asdf"
assert parent.url == "asdf"
reverse.assert_called_with("fake")
def test_url_none(self):
"""If a node doesn't have a page or children, url should return None."""
node = PageNode('')
node = PageNode("")
assert node.url is None
class TestPageRoot(TestCase):
@patch.object(PageNode, 'page')
@patch.object(PageNode, "page")
def test_as_urlpatterns(self, page):
"""
as_urlpatterns should return a urlconf with the pages for all the nodes
included in the tree.
"""
child1 = PageNode('child1', path='asdf/qwer', template='fake.html')
child2 = PageNode('child2', path='bb/qr', template='fake2.html')
parent = PageNode('parent', children=[child1, child2])
root = PageRoot('root', path='badsbi', template='fake3.html',
children=[parent])
child1 = PageNode("child1", path="asdf/qwer", template="fake.html")
child2 = PageNode("child2", path="bb/qr", template="fake2.html")
parent = PageNode("parent", children=[child1, child2])
root = PageRoot("root", path="badsbi", template="fake3.html", children=[parent])
# Mocking properties
page.__get__ = lambda mock, self, cls: self.display_name
assert root.as_urlpatterns() == ['root', 'child1', 'child2']
assert root.as_urlpatterns() == ["root", "child1", "child2"]

Просмотреть файл

@ -18,34 +18,34 @@ class TestClacksOverheadMiddleware(TestCase):
def test_good_response_has_header(self):
self.response.status_code = 200
self.middleware.process_response(self.request, self.response)
self.assertEqual(self.response['X-Clacks-Overhead'], 'GNU Terry Pratchett')
self.assertEqual(self.response["X-Clacks-Overhead"], "GNU Terry Pratchett")
def test_other_response_has_no_header(self):
self.response.status_code = 301
self.middleware.process_response(self.request, self.response)
self.assertNotIn('X-Clacks-Overhead', self.response)
self.assertNotIn("X-Clacks-Overhead", self.response)
self.response.status_code = 404
self.middleware.process_response(self.request, self.response)
self.assertNotIn('X-Clacks-Overhead', self.response)
self.assertNotIn("X-Clacks-Overhead", self.response)
@override_settings(ENABLE_HOSTNAME_MIDDLEWARE=True)
class TestHostnameMiddleware(TestCase):
@override_settings(HOSTNAME='foobar', CLUSTER_NAME='oregon-b')
@override_settings(HOSTNAME="foobar", CLUSTER_NAME="oregon-b")
def test_base(self):
self.middleware = HostnameMiddleware()
self.request = HttpRequest()
self.response = HttpResponse()
self.middleware.process_response(self.request, self.response)
self.assertEqual(self.response['X-Backend-Server'], 'foobar.oregon-b')
self.assertEqual(self.response["X-Backend-Server"], "foobar.oregon-b")
@override_settings(
MIDDLEWARE=(list(settings.MIDDLEWARE) + ['bedrock.mozorg.middleware.HostnameMiddleware']),
HOSTNAME='foobar',
CLUSTER_NAME='el-dudarino',
MIDDLEWARE=(list(settings.MIDDLEWARE) + ["bedrock.mozorg.middleware.HostnameMiddleware"]),
HOSTNAME="foobar",
CLUSTER_NAME="el-dudarino",
)
def test_request(self):
response = self.client.get('/en-US/')
self.assertEqual(response['X-Backend-Server'], 'foobar.el-dudarino')
response = self.client.get("/en-US/")
self.assertEqual(response["X-Backend-Server"], "foobar.el-dudarino")

Просмотреть файл

@ -15,16 +15,15 @@ from bedrock.mozorg.util import (
page,
)
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_files')
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files")
class TestGetFacebookLikeLocale(TestCase):
def test_supported_locale(self):
"""
Return the given locale if supported.
"""
assert get_fb_like_locale('en-PI') == 'en_PI'
assert get_fb_like_locale("en-PI") == "en_PI"
def test_first_supported_locale_for_language(self):
"""
@ -32,40 +31,38 @@ class TestGetFacebookLikeLocale(TestCase):
the supported locales and return the first one that
matches the language.
"""
assert get_fb_like_locale('es-AR') == 'es_ES'
assert get_fb_like_locale("es-AR") == "es_ES"
def test_unsupported_locale(self):
"""
Return the default en_US when locale isn't supported.
"""
assert get_fb_like_locale('zz-ZZ') == 'en_US'
assert get_fb_like_locale("zz-ZZ") == "en_US"
@patch('bedrock.mozorg.util.l10n_utils')
@patch("bedrock.mozorg.util.l10n_utils")
class TestPageUtil(TestCase):
def setUp(self):
self.rf = RequestFactory()
def test_locale_redirect(self, l10n_mock):
"""Should use l10n render."""
url = page('walter/abides', 'walter/abides.html', donny='ashes')
url.callback(self.rf.get('/walter/abides/'))
l10n_mock.render.assert_called_with(ANY, 'walter/abides.html', {'urlname': 'walter.abides',
'donny': 'ashes'}, ftl_files=None)
url = page("walter/abides", "walter/abides.html", donny="ashes")
url.callback(self.rf.get("/walter/abides/"))
l10n_mock.render.assert_called_with(ANY, "walter/abides.html", {"urlname": "walter.abides", "donny": "ashes"}, ftl_files=None)
def test_locale_redirect_works_home_page(self, l10n_mock):
"""Make sure the home page still works. "/" is a special case."""
url = page('', 'index.html')
url.callback(self.rf.get('/'))
l10n_mock.render.assert_called_with(ANY, 'index.html', {'urlname': 'index'}, ftl_files=None)
url = page("", "index.html")
url.callback(self.rf.get("/"))
l10n_mock.render.assert_called_with(ANY, "index.html", {"urlname": "index"}, ftl_files=None)
def test_url_name_set_from_template(self, l10n_mock):
"""If not provided the URL pattern name should be set from the template path."""
url = page('lebowski/urban_achievers', 'lebowski/achievers.html')
assert url.name == 'lebowski.achievers'
url = page("lebowski/urban_achievers", "lebowski/achievers.html")
assert url.name == "lebowski.achievers"
def test_url_name_set_from_param(self, l10n_mock):
"""If provided the URL pattern name should be set from the parameter."""
url = page('lebowski/urban_achievers', 'lebowski/achievers.html',
url_name='proud.we.are.of.all.of.them')
assert url.name == 'proud.we.are.of.all.of.them'
url = page("lebowski/urban_achievers", "lebowski/achievers.html", url_name="proud.we.are.of.all.of.them")
assert url.name == "proud.we.are.of.all.of.them"

Просмотреть файл

@ -14,23 +14,23 @@ from bedrock.mozorg import views
class TestViews(TestCase):
@patch.dict(os.environ, FUNNELCAKE_5_LOCALES='en-US', FUNNELCAKE_5_PLATFORMS='win')
@patch.dict(os.environ, FUNNELCAKE_5_LOCALES="en-US", FUNNELCAKE_5_PLATFORMS="win")
def test_download_button_funnelcake(self):
"""The download button should have the funnelcake ID."""
with self.activate('en-US'):
resp = self.client.get(reverse('mozorg.home'), {'f': '5'})
assert b'product=firefox-stub-f5&' in resp.content
with self.activate("en-US"):
resp = self.client.get(reverse("mozorg.home"), {"f": "5"})
assert b"product=firefox-stub-f5&" in resp.content
def test_download_button_bad_funnelcake(self):
"""The download button should not have a bad funnelcake ID."""
with self.activate('en-US'):
resp = self.client.get(reverse('mozorg.home'), {'f': '5dude'})
assert b'product=firefox-stub&' in resp.content
assert b'product=firefox-stub-f5dude&' not in resp.content
with self.activate("en-US"):
resp = self.client.get(reverse("mozorg.home"), {"f": "5dude"})
assert b"product=firefox-stub&" in resp.content
assert b"product=firefox-stub-f5dude&" not in resp.content
resp = self.client.get(reverse('mozorg.home'), {'f': '999999999'})
assert b'product=firefox-stub&' in resp.content
assert b'product=firefox-stub-f999999999&' not in resp.content
resp = self.client.get(reverse("mozorg.home"), {"f": "999999999"})
assert b"product=firefox-stub&" in resp.content
assert b"product=firefox-stub-f999999999&" not in resp.content
class TestRobots(TestCase):
@ -39,45 +39,45 @@ class TestRobots(TestCase):
self.view = views.Robots()
def test_production_disallow_all_is_false(self):
self.view.request = self.rf.get('/', HTTP_HOST='www.mozilla.org')
self.assertFalse(self.view.get_context_data()['disallow_all'])
self.view.request = self.rf.get("/", HTTP_HOST="www.mozilla.org")
self.assertFalse(self.view.get_context_data()["disallow_all"])
def test_non_production_disallow_all_is_true(self):
self.view.request = self.rf.get('/', HTTP_HOST='www.allizom.org')
self.assertTrue(self.view.get_context_data()['disallow_all'])
self.view.request = self.rf.get("/", HTTP_HOST="www.allizom.org")
self.assertTrue(self.view.get_context_data()["disallow_all"])
def test_robots_no_redirect(self):
response = self.client.get('/robots.txt', HTTP_HOST='www.mozilla.org')
response = self.client.get("/robots.txt", HTTP_HOST="www.mozilla.org")
self.assertEqual(response.status_code, 200)
self.assertFalse(response.context_data['disallow_all'])
self.assertEqual(response.get('Content-Type'), 'text/plain')
self.assertFalse(response.context_data["disallow_all"])
self.assertEqual(response.get("Content-Type"), "text/plain")
@patch('bedrock.mozorg.views.l10n_utils.render')
@patch("bedrock.mozorg.views.l10n_utils.render")
class TestHomePage(TestCase):
def setUp(self):
self.rf = RequestFactory()
def test_home_en_template(self, render_mock):
req = RequestFactory().get('/')
req.locale = 'en-US'
req = RequestFactory().get("/")
req.locale = "en-US"
views.home_view(req)
render_mock.assert_called_once_with(req, 'mozorg/home/home-en.html', ANY)
render_mock.assert_called_once_with(req, "mozorg/home/home-en.html", ANY)
def test_home_de_template(self, render_mock):
req = RequestFactory().get('/')
req.locale = 'de'
req = RequestFactory().get("/")
req.locale = "de"
views.home_view(req)
render_mock.assert_called_once_with(req, 'mozorg/home/home-de.html', ANY)
render_mock.assert_called_once_with(req, "mozorg/home/home-de.html", ANY)
def test_home_fr_template(self, render_mock):
req = RequestFactory().get('/')
req.locale = 'fr'
req = RequestFactory().get("/")
req.locale = "fr"
views.home_view(req)
render_mock.assert_called_once_with(req, 'mozorg/home/home-fr.html', ANY)
render_mock.assert_called_once_with(req, "mozorg/home/home-fr.html", ANY)
def test_home_locale_template(self, render_mock):
req = RequestFactory().get('/')
req.locale = 'es'
req = RequestFactory().get("/")
req.locale = "es"
views.home_view(req)
render_mock.assert_called_once_with(req, 'mozorg/home/home.html', ANY)
render_mock.assert_called_once_with(req, "mozorg/home/home.html", ANY)

Просмотреть файл

@ -10,12 +10,11 @@ from bedrock.mozorg.util import page
def mock_view(request):
return HttpResponse('test')
return HttpResponse("test")
urlpatterns = [
url(r'', include('%s.urls' % settings.PROJECT_MODULE)),
url(r"", include("%s.urls" % settings.PROJECT_MODULE)),
# Used by test_helper
page('base', 'base-protocol.html'),
page("base", "base-protocol.html"),
]

Просмотреть файл

@ -7,7 +7,7 @@ from django.http import HttpResponse
from bedrock.mozorg.decorators import cache_control_expires
RESPONSE_CONTENT = 'The Dude abides, man.'
RESPONSE_CONTENT = "The Dude abides, man."
@cache_control_expires(48)

Просмотреть файл

@ -12,131 +12,108 @@ from bedrock.redirects.util import redirect
urlpatterns = (
url(r'^$', views.home_view, name='mozorg.home'),
page('about', 'mozorg/about/index.html', ftl_files=['mozorg/about']),
page('about/manifesto', 'mozorg/about/manifesto.html', ftl_files=['mozorg/about/manifesto']),
page('about/manifesto/details', 'mozorg/about/manifesto-details.html', ftl_files=['mozorg/about/manifesto']),
page('about/leadership', 'mozorg/about/leadership.html'),
page('about/policy/lean-data', 'mozorg/about/policy/lean-data/index.html'),
page('about/policy/lean-data/build-security', 'mozorg/about/policy/lean-data/build-security.html'),
page('about/policy/lean-data/stay-lean', 'mozorg/about/policy/lean-data/stay-lean.html'),
page('about/policy/lean-data/engage-users', 'mozorg/about/policy/lean-data/engage-users.html'),
page('about/policy/patents', 'mozorg/about/policy/patents/index.html'),
page('about/policy/patents/license', 'mozorg/about/policy/patents/license.html'),
page('about/policy/patents/license/1.0', 'mozorg/about/policy/patents/license-1.0.html'),
page('about/policy/patents/guide', 'mozorg/about/policy/patents/guide.html'),
page('book', 'mozorg/book.html'),
url('^credits/$', views.credits_view, name='mozorg.credits'),
page('credits/faq', 'mozorg/credits-faq.html'),
page('about/history', 'mozorg/about/history.html', ftl_files=['mozorg/about/history']),
url(r"^$", views.home_view, name="mozorg.home"),
page("about", "mozorg/about/index.html", ftl_files=["mozorg/about"]),
page("about/manifesto", "mozorg/about/manifesto.html", ftl_files=["mozorg/about/manifesto"]),
page("about/manifesto/details", "mozorg/about/manifesto-details.html", ftl_files=["mozorg/about/manifesto"]),
page("about/leadership", "mozorg/about/leadership.html"),
page("about/policy/lean-data", "mozorg/about/policy/lean-data/index.html"),
page("about/policy/lean-data/build-security", "mozorg/about/policy/lean-data/build-security.html"),
page("about/policy/lean-data/stay-lean", "mozorg/about/policy/lean-data/stay-lean.html"),
page("about/policy/lean-data/engage-users", "mozorg/about/policy/lean-data/engage-users.html"),
page("about/policy/patents", "mozorg/about/policy/patents/index.html"),
page("about/policy/patents/license", "mozorg/about/policy/patents/license.html"),
page("about/policy/patents/license/1.0", "mozorg/about/policy/patents/license-1.0.html"),
page("about/policy/patents/guide", "mozorg/about/policy/patents/guide.html"),
page("book", "mozorg/book.html"),
url("^credits/$", views.credits_view, name="mozorg.credits"),
page("credits/faq", "mozorg/credits-faq.html"),
page("about/history", "mozorg/about/history.html", ftl_files=["mozorg/about/history"]),
# Bug 981063, catch all for old calendar urls.
# must be here to avoid overriding the above
redirect(r'^projects/calendar/', 'https://www.thunderbird.net/calendar/', locale_prefix=False),
page('mission', 'mozorg/mission.html', ftl_files=['mozorg/mission']),
url('^about/forums/$', views.forums_view, name='mozorg.about.forums.forums'),
page('about/forums/etiquette', 'mozorg/about/forums/etiquette.html'),
page('about/forums/cancellation', 'mozorg/about/forums/cancellation.html'),
page('about/governance', 'mozorg/about/governance/governance.html'),
page('about/governance/roles', 'mozorg/about/governance/roles.html'),
page('about/governance/policies', 'mozorg/about/governance/policies/policies.html'),
page('about/governance/policies/commit', 'mozorg/about/governance/policies/commit.html'),
page('about/governance/policies/commit/access-policy',
'mozorg/about/governance/policies/commit/access-policy.html'),
page('about/governance/policies/commit/requirements',
'mozorg/about/governance/policies/commit/requirements.html'),
page('about/governance/policies/security-group/bugs',
'mozorg/about/governance/policies/security/bugs.html'),
page('about/governance/policies/security-group/membership',
'mozorg/about/governance/policies/security/membership.html'),
page('about/governance/policies/security-group/certs/policy',
'mozorg/about/governance/policies/security/certs/policy.html'),
page('about/governance/organizations', 'mozorg/about/governance/organizations.html'),
page('about/governance/policies/participation/reporting',
'mozorg/about/governance/policies/reporting.html',
ftl_files=['mozorg/about/governance/policies/reporting']),
page('about/governance/policies/participation',
'mozorg/about/governance/policies/participation.html',
ftl_files=['mozorg/about/governance/policies/participation']),
page('about/governance/policies/participation/reporting/community-hotline',
'mozorg/about/governance/policies/community-hotline.html',
ftl_files=['mozorg/about/governance/policies/community-hotline']),
page('about/governance/policies/module-ownership',
'mozorg/about/governance/policies/module-ownership.html'),
page('about/governance/policies/regressions',
'mozorg/about/governance/policies/regressions.html'),
page('about/policy/transparency', 'mozorg/about/policy/transparency/index.html'),
page('about/policy/transparency/jan-dec-2015',
'mozorg/about/policy/transparency/jan-dec-2015.html'),
page('about/policy/transparency/jan-jun-2016',
'mozorg/about/policy/transparency/jan-jun-2016.html'),
page('about/policy/transparency/jul-dec-2016',
'mozorg/about/policy/transparency/jul-dec-2016.html'),
page('about/policy/transparency/jan-jun-2017',
'mozorg/about/policy/transparency/jan-jun-2017.html'),
page('about/policy/transparency/jul-dec-2017',
'mozorg/about/policy/transparency/jul-dec-2017.html'),
page('about/policy/transparency/jan-jun-2018',
'mozorg/about/policy/transparency/jan-jun-2018.html'),
page('about/policy/transparency/jul-dec-2018',
'mozorg/about/policy/transparency/jul-dec-2018.html'),
page('about/policy/transparency/jan-jun-2019',
'mozorg/about/policy/transparency/jan-jun-2019.html'),
page('about/policy/transparency/jul-dec-2019',
'mozorg/about/policy/transparency/jul-dec-2019.html'),
page('about/policy/transparency/jan-jun-2020',
'mozorg/about/policy/transparency/jan-jun-2020.html'),
page('about/policy/transparency/jul-dec-2020',
'mozorg/about/policy/transparency/jul-dec-2020.html'),
page('about/policy/transparency/jan-jun-2021',
'mozorg/about/policy/transparency/jan-jun-2021.html'),
page('contact', 'mozorg/contact/contact-landing.html'),
page('contact/spaces', 'mozorg/contact/spaces/spaces-landing.html'),
page('contact/spaces/beijing', 'mozorg/contact/spaces/beijing.html'),
page('contact/spaces/berlin', 'mozorg/contact/spaces/berlin.html'),
page('contact/spaces/paris', 'mozorg/contact/spaces/paris.html'),
page('contact/spaces/portland', 'mozorg/contact/spaces/portland.html'),
page('contact/spaces/san-francisco', 'mozorg/contact/spaces/san-francisco.html'),
page('contact/spaces/toronto', 'mozorg/contact/spaces/toronto.html'),
page('MPL', 'mozorg/mpl/index.html'),
page('MPL/historical', 'mozorg/mpl/historical.html'),
page('MPL/license-policy', 'mozorg/mpl/license-policy.html'),
page('MPL/headers', 'mozorg/mpl/headers/index.html'),
page('MPL/1.1', 'mozorg/mpl/1.1/index.html'),
page('MPL/1.1/FAQ', 'mozorg/mpl/1.1/faq.html'),
page('MPL/1.1/annotated', 'mozorg/mpl/1.1/annotated/index.html'),
page('MPL/2.0', 'mozorg/mpl/2.0/index.html'),
page('MPL/2.0/FAQ', 'mozorg/mpl/2.0/faq.html'),
page('MPL/2.0/Revision-FAQ', 'mozorg/mpl/2.0/revision-faq.html'),
page('MPL/2.0/combining-mpl-and-gpl', 'mozorg/mpl/2.0/combining-mpl-and-gpl.html'),
page('MPL/2.0/differences', 'mozorg/mpl/2.0/differences.html'),
page('MPL/2.0/permissive-code-into-mpl', 'mozorg/mpl/2.0/permissive-code-into-mpl.html'),
page('contribute', 'mozorg/contribute.html', ftl_files=['mozorg/contribute']),
page('moss', 'mozorg/moss/index.html'),
page('moss/foundational-technology', 'mozorg/moss/foundational-technology.html'),
page('moss/mission-partners', 'mozorg/moss/mission-partners.html'),
page('moss/secure-open-source', 'mozorg/moss/secure-open-source.html'),
url(r'^robots\.txt$', views.Robots.as_view(), name='robots.txt'),
redirect(r"^projects/calendar/", "https://www.thunderbird.net/calendar/", locale_prefix=False),
page("mission", "mozorg/mission.html", ftl_files=["mozorg/mission"]),
url("^about/forums/$", views.forums_view, name="mozorg.about.forums.forums"),
page("about/forums/etiquette", "mozorg/about/forums/etiquette.html"),
page("about/forums/cancellation", "mozorg/about/forums/cancellation.html"),
page("about/governance", "mozorg/about/governance/governance.html"),
page("about/governance/roles", "mozorg/about/governance/roles.html"),
page("about/governance/policies", "mozorg/about/governance/policies/policies.html"),
page("about/governance/policies/commit", "mozorg/about/governance/policies/commit.html"),
page("about/governance/policies/commit/access-policy", "mozorg/about/governance/policies/commit/access-policy.html"),
page("about/governance/policies/commit/requirements", "mozorg/about/governance/policies/commit/requirements.html"),
page("about/governance/policies/security-group/bugs", "mozorg/about/governance/policies/security/bugs.html"),
page("about/governance/policies/security-group/membership", "mozorg/about/governance/policies/security/membership.html"),
page("about/governance/policies/security-group/certs/policy", "mozorg/about/governance/policies/security/certs/policy.html"),
page("about/governance/organizations", "mozorg/about/governance/organizations.html"),
page(
"about/governance/policies/participation/reporting",
"mozorg/about/governance/policies/reporting.html",
ftl_files=["mozorg/about/governance/policies/reporting"],
),
page(
"about/governance/policies/participation",
"mozorg/about/governance/policies/participation.html",
ftl_files=["mozorg/about/governance/policies/participation"],
),
page(
"about/governance/policies/participation/reporting/community-hotline",
"mozorg/about/governance/policies/community-hotline.html",
ftl_files=["mozorg/about/governance/policies/community-hotline"],
),
page("about/governance/policies/module-ownership", "mozorg/about/governance/policies/module-ownership.html"),
page("about/governance/policies/regressions", "mozorg/about/governance/policies/regressions.html"),
page("about/policy/transparency", "mozorg/about/policy/transparency/index.html"),
page("about/policy/transparency/jan-dec-2015", "mozorg/about/policy/transparency/jan-dec-2015.html"),
page("about/policy/transparency/jan-jun-2016", "mozorg/about/policy/transparency/jan-jun-2016.html"),
page("about/policy/transparency/jul-dec-2016", "mozorg/about/policy/transparency/jul-dec-2016.html"),
page("about/policy/transparency/jan-jun-2017", "mozorg/about/policy/transparency/jan-jun-2017.html"),
page("about/policy/transparency/jul-dec-2017", "mozorg/about/policy/transparency/jul-dec-2017.html"),
page("about/policy/transparency/jan-jun-2018", "mozorg/about/policy/transparency/jan-jun-2018.html"),
page("about/policy/transparency/jul-dec-2018", "mozorg/about/policy/transparency/jul-dec-2018.html"),
page("about/policy/transparency/jan-jun-2019", "mozorg/about/policy/transparency/jan-jun-2019.html"),
page("about/policy/transparency/jul-dec-2019", "mozorg/about/policy/transparency/jul-dec-2019.html"),
page("about/policy/transparency/jan-jun-2020", "mozorg/about/policy/transparency/jan-jun-2020.html"),
page("about/policy/transparency/jul-dec-2020", "mozorg/about/policy/transparency/jul-dec-2020.html"),
page("about/policy/transparency/jan-jun-2021", "mozorg/about/policy/transparency/jan-jun-2021.html"),
page("contact", "mozorg/contact/contact-landing.html"),
page("contact/spaces", "mozorg/contact/spaces/spaces-landing.html"),
page("contact/spaces/beijing", "mozorg/contact/spaces/beijing.html"),
page("contact/spaces/berlin", "mozorg/contact/spaces/berlin.html"),
page("contact/spaces/paris", "mozorg/contact/spaces/paris.html"),
page("contact/spaces/portland", "mozorg/contact/spaces/portland.html"),
page("contact/spaces/san-francisco", "mozorg/contact/spaces/san-francisco.html"),
page("contact/spaces/toronto", "mozorg/contact/spaces/toronto.html"),
page("MPL", "mozorg/mpl/index.html"),
page("MPL/historical", "mozorg/mpl/historical.html"),
page("MPL/license-policy", "mozorg/mpl/license-policy.html"),
page("MPL/headers", "mozorg/mpl/headers/index.html"),
page("MPL/1.1", "mozorg/mpl/1.1/index.html"),
page("MPL/1.1/FAQ", "mozorg/mpl/1.1/faq.html"),
page("MPL/1.1/annotated", "mozorg/mpl/1.1/annotated/index.html"),
page("MPL/2.0", "mozorg/mpl/2.0/index.html"),
page("MPL/2.0/FAQ", "mozorg/mpl/2.0/faq.html"),
page("MPL/2.0/Revision-FAQ", "mozorg/mpl/2.0/revision-faq.html"),
page("MPL/2.0/combining-mpl-and-gpl", "mozorg/mpl/2.0/combining-mpl-and-gpl.html"),
page("MPL/2.0/differences", "mozorg/mpl/2.0/differences.html"),
page("MPL/2.0/permissive-code-into-mpl", "mozorg/mpl/2.0/permissive-code-into-mpl.html"),
page("contribute", "mozorg/contribute.html", ftl_files=["mozorg/contribute"]),
page("moss", "mozorg/moss/index.html"),
page("moss/foundational-technology", "mozorg/moss/foundational-technology.html"),
page("moss/mission-partners", "mozorg/moss/mission-partners.html"),
page("moss/secure-open-source", "mozorg/moss/secure-open-source.html"),
url(r"^robots\.txt$", views.Robots.as_view(), name="robots.txt"),
# namespaces
url(r'^2004/em-rdf$', views.namespaces, {'namespace': 'em-rdf'}),
url(r'^2005/app-update$', views.namespaces, {'namespace': 'update'}),
url(r'^2006/addons-blocklist$', views.namespaces, {'namespace': 'addons-bl'}),
url(r'^2006/browser/search/$', views.namespaces, {'namespace': 'mozsearch'}),
url(r'^keymaster/gatekeeper/there\.is\.only\.xul$', views.namespaces, {'namespace': 'xul'}),
url(r'^microsummaries/0\.1$', views.namespaces, {'namespace': 'microsummaries'}),
url(r'^projects/xforms/2005/type$', views.namespaces, {'namespace': 'xforms-type'}),
url(r'^xbl$', views.namespaces, {'namespace': 'xbl'}),
page('locales', 'mozorg/locales.html'),
url(r"^2004/em-rdf$", views.namespaces, {"namespace": "em-rdf"}),
url(r"^2005/app-update$", views.namespaces, {"namespace": "update"}),
url(r"^2006/addons-blocklist$", views.namespaces, {"namespace": "addons-bl"}),
url(r"^2006/browser/search/$", views.namespaces, {"namespace": "mozsearch"}),
url(r"^keymaster/gatekeeper/there\.is\.only\.xul$", views.namespaces, {"namespace": "xul"}),
url(r"^microsummaries/0\.1$", views.namespaces, {"namespace": "microsummaries"}),
url(r"^projects/xforms/2005/type$", views.namespaces, {"namespace": "xforms-type"}),
url(r"^xbl$", views.namespaces, {"namespace": "xbl"}),
page("locales", "mozorg/locales.html"),
)
if settings.DEV:
urlpatterns += (
path('contentful-preview/<content_id>/', views.ContentfulPreviewView.as_view()),
)
urlpatterns += (path("contentful-preview/<content_id>/", views.ContentfulPreviewView.as_view()),)

Просмотреть файл

@ -17,7 +17,7 @@ except ImportError:
newrelic = False
log = commonware.log.getLogger('mozorg.util')
log = commonware.log.getLogger("mozorg.util")
def page(name, tmpl, decorators=None, url_name=None, ftl_files=None, **kwargs):
@ -47,12 +47,12 @@ def page(name, tmpl, decorators=None, url_name=None, ftl_files=None, **kwargs):
@param kwargs: Any additional arguments are passed to l10n_utils.render
as the context.
"""
pattern = r'^%s/$' % name if name else r'^$'
pattern = r"^%s/$" % name if name else r"^$"
if url_name is None:
# Set the name of the view to the template path replaced with dots
(base, ext) = os.path.splitext(tmpl)
url_name = base.replace('/', '.')
url_name = base.replace("/", ".")
# we don't have a caching backend yet, so no csrf (it's just a
# newsletter form anyway)
@ -60,10 +60,9 @@ def page(name, tmpl, decorators=None, url_name=None, ftl_files=None, **kwargs):
def _view(request):
if newrelic:
# Name this in New Relic to differentiate pages
newrelic.agent.set_transaction_name(
'mozorg.util.page:' + url_name.replace('.', '_'))
newrelic.agent.set_transaction_name("mozorg.util.page:" + url_name.replace(".", "_"))
kwargs.setdefault('urlname', url_name)
kwargs.setdefault("urlname", url_name)
return l10n_utils.render(request, tmpl, kwargs, ftl_files=ftl_files)
# This is for graphite so that we can differentiate pages
@ -81,8 +80,7 @@ def page(name, tmpl, decorators=None, url_name=None, ftl_files=None, **kwargs):
for decorator in reversed(decorators):
_view = decorator(_view)
except TypeError:
log.exception('decorators not iterable or does not contain '
'callable items')
log.exception("decorators not iterable or does not contain callable items")
return url(pattern, _view, name=url_name)
@ -98,15 +96,14 @@ def get_fb_like_locale(request_locale):
Adapted from the facebookapp get_best_locale() util
"""
lang = request_locale.replace('-', '_')
lang = request_locale.replace("-", "_")
if lang not in settings.FACEBOOK_LIKE_LOCALES:
lang_prefix = lang.split('_')[0]
lang_prefix = lang.split("_")[0]
try:
lang = next(locale for locale in settings.FACEBOOK_LIKE_LOCALES
if locale.startswith(lang_prefix))
lang = next(locale for locale in settings.FACEBOOK_LIKE_LOCALES if locale.startswith(lang_prefix))
except StopIteration:
lang = 'en_US'
lang = "en_US"
return lang

Просмотреть файл

@ -20,165 +20,159 @@ from bedrock.contentful.models import ContentfulEntry
from bedrock.mozorg.credits import CreditsFile
from bedrock.pocketfeed.models import PocketArticle
credits_file = CreditsFile('credits')
TECH_BLOG_SLUGS = ['hacks', 'cd', 'futurereleases']
credits_file = CreditsFile("credits")
TECH_BLOG_SLUGS = ["hacks", "cd", "futurereleases"]
def csrf_failure(request, reason=''):
template_vars = {'reason': reason}
return l10n_utils.render(request, 'mozorg/csrf-failure.html', template_vars,
status=403)
def csrf_failure(request, reason=""):
template_vars = {"reason": reason}
return l10n_utils.render(request, "mozorg/csrf-failure.html", template_vars, status=403)
@xframe_allow
def hacks_newsletter(request):
return l10n_utils.render(request,
'mozorg/newsletter/hacks.mozilla.org.html')
return l10n_utils.render(request, "mozorg/newsletter/hacks.mozilla.org.html")
@require_safe
def credits_view(request):
"""Display the names of our contributors."""
ctx = {'credits': credits_file}
ctx = {"credits": credits_file}
# not translated
return django_render(request, 'mozorg/credits.html', ctx)
return django_render(request, "mozorg/credits.html", ctx)
@require_safe
def forums_view(request):
"""Display our mailing lists and newsgroups."""
return l10n_utils.render(request, 'mozorg/about/forums/forums.html')
return l10n_utils.render(request, "mozorg/about/forums/forums.html")
class Robots(TemplateView):
template_name = 'mozorg/robots.txt'
content_type = 'text/plain'
template_name = "mozorg/robots.txt"
content_type = "text/plain"
def get_context_data(self, **kwargs):
hostname = self.request.get_host()
return {'disallow_all': not hostname == 'www.mozilla.org'}
return {"disallow_all": not hostname == "www.mozilla.org"}
NAMESPACES = {
'addons-bl': {
'namespace': 'http://www.mozilla.org/2006/addons-blocklist',
'standard': 'Add-ons Blocklist',
'docs': 'https://wiki.mozilla.org/Extension_Blocklisting:Code_Design',
"addons-bl": {
"namespace": "http://www.mozilla.org/2006/addons-blocklist",
"standard": "Add-ons Blocklist",
"docs": "https://wiki.mozilla.org/Extension_Blocklisting:Code_Design",
},
'em-rdf': {
'namespace': 'http://www.mozilla.org/2004/em-rdf',
'standard': 'Extension Manifest',
'docs': 'https://developer.mozilla.org/en/Install_Manifests',
"em-rdf": {
"namespace": "http://www.mozilla.org/2004/em-rdf",
"standard": "Extension Manifest",
"docs": "https://developer.mozilla.org/en/Install_Manifests",
},
'microsummaries': {
'namespace': 'http://www.mozilla.org/microsummaries/0.1',
'standard': 'Microsummaries',
'docs': 'https://developer.mozilla.org/en/Microsummary_XML_grammar_reference',
"microsummaries": {
"namespace": "http://www.mozilla.org/microsummaries/0.1",
"standard": "Microsummaries",
"docs": "https://developer.mozilla.org/en/Microsummary_XML_grammar_reference",
},
'mozsearch': {
'namespace': 'http://www.mozilla.org/2006/browser/search/',
'standard': 'MozSearch plugin format',
'docs': 'https://developer.mozilla.org/en/Creating_MozSearch_plugins',
"mozsearch": {
"namespace": "http://www.mozilla.org/2006/browser/search/",
"standard": "MozSearch plugin format",
"docs": "https://developer.mozilla.org/en/Creating_MozSearch_plugins",
},
'update': {
'namespace': 'http://www.mozilla.org/2005/app-update',
'standard': 'Software Update Service',
'docs': 'https://wiki.mozilla.org/Software_Update:Testing',
"update": {
"namespace": "http://www.mozilla.org/2005/app-update",
"standard": "Software Update Service",
"docs": "https://wiki.mozilla.org/Software_Update:Testing",
},
'xbl': {
'namespace': 'http://www.mozilla.org/xbl',
'standard': 'XML Binding Language (XBL)',
'docs': 'https://developer.mozilla.org/en/XBL',
"xbl": {
"namespace": "http://www.mozilla.org/xbl",
"standard": "XML Binding Language (XBL)",
"docs": "https://developer.mozilla.org/en/XBL",
},
'xforms-type': {
'namespace': 'http://www.mozilla.org/projects/xforms/2005/type',
'standard': 'XForms mozType extension',
'docs': 'https://developer.mozilla.org/en/XForms/Custom_Controls',
"xforms-type": {
"namespace": "http://www.mozilla.org/projects/xforms/2005/type",
"standard": "XForms mozType extension",
"docs": "https://developer.mozilla.org/en/XForms/Custom_Controls",
},
'xul': {
'namespace': 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul',
'standard': 'XML User Interface Language (XUL)',
'docs': 'https://developer.mozilla.org/en/XUL',
"xul": {
"namespace": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"standard": "XML User Interface Language (XUL)",
"docs": "https://developer.mozilla.org/en/XUL",
},
}
def namespaces(request, namespace):
context = NAMESPACES[namespace]
context['slug'] = namespace
template = 'mozorg/namespaces.html'
context["slug"] = namespace
template = "mozorg/namespaces.html"
return django_render(request, template, context)
def home_view(request):
locale = l10n_utils.get_locale(request)
donate_params = settings.DONATE_PARAMS.get(
locale, settings.DONATE_PARAMS['en-US'])
donate_params = settings.DONATE_PARAMS.get(locale, settings.DONATE_PARAMS["en-US"])
# presets are stored as a string but, for the home banner
# we need it as a list.
donate_params['preset_list'] = donate_params['presets'].split(',')
donate_params["preset_list"] = donate_params["presets"].split(",")
ctx = {
'donate_params': donate_params,
'pocket_articles': PocketArticle.objects.all()[:4],
'ftl_files': ['mozorg/home', 'mozorg/home-mr1-promo'],
'add_active_locales': ['de', 'fr']
"donate_params": donate_params,
"pocket_articles": PocketArticle.objects.all()[:4],
"ftl_files": ["mozorg/home", "mozorg/home-mr1-promo"],
"add_active_locales": ["de", "fr"],
}
if locale.startswith('en-'):
if switch('contentful-homepage-en'):
if locale.startswith("en-"):
if switch("contentful-homepage-en"):
try:
template_name = 'mozorg/contentful-homepage.html'
template_name = "mozorg/contentful-homepage.html"
# TODO: use a better system to get the pages than the ID
ctx.update(ContentfulEntry.objects.get_page_by_id('58YIvwDmzSDjtvpSqstDcL'))
ctx.update(ContentfulEntry.objects.get_page_by_id("58YIvwDmzSDjtvpSqstDcL"))
except Exception:
# if anything goes wrong, use the old page
template_name = 'mozorg/home/home-en.html'
ctx['page_content_cards'] = get_page_content_cards('home-en', 'en-US')
template_name = "mozorg/home/home-en.html"
ctx["page_content_cards"] = get_page_content_cards("home-en", "en-US")
else:
template_name = 'mozorg/home/home-en.html'
ctx['page_content_cards'] = get_page_content_cards('home-en', 'en-US')
elif locale == 'de':
if switch('contentful-homepage-de'):
template_name = "mozorg/home/home-en.html"
ctx["page_content_cards"] = get_page_content_cards("home-en", "en-US")
elif locale == "de":
if switch("contentful-homepage-de"):
try:
template_name = 'mozorg/contentful-homepage.html'
ctx.update(ContentfulEntry.objects.get_page_by_id('4k3CxqZGjxXOjR1I0dhyto'))
template_name = "mozorg/contentful-homepage.html"
ctx.update(ContentfulEntry.objects.get_page_by_id("4k3CxqZGjxXOjR1I0dhyto"))
except Exception:
# if anything goes wrong, use the old page
template_name = 'mozorg/home/home-de.html'
ctx['page_content_cards'] = get_page_content_cards('home-de', 'de')
template_name = "mozorg/home/home-de.html"
ctx["page_content_cards"] = get_page_content_cards("home-de", "de")
else:
template_name = 'mozorg/home/home-de.html'
ctx['page_content_cards'] = get_page_content_cards('home-de', 'de')
elif locale == 'fr':
template_name = 'mozorg/home/home-fr.html'
ctx['page_content_cards'] = get_page_content_cards('home-fr', 'fr')
template_name = "mozorg/home/home-de.html"
ctx["page_content_cards"] = get_page_content_cards("home-de", "de")
elif locale == "fr":
template_name = "mozorg/home/home-fr.html"
ctx["page_content_cards"] = get_page_content_cards("home-fr", "fr")
else:
template_name = 'mozorg/home/home.html'
template_name = "mozorg/home/home.html"
return l10n_utils.render(request, template_name, ctx)
@method_decorator(never_cache, name='dispatch')
@method_decorator(never_cache, name="dispatch")
class ContentfulPreviewView(L10nTemplateView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
content_id = ctx['content_id']
content_id = ctx["content_id"]
page = ContentfulPage(self.request, content_id)
ctx.update(page.get_content())
return ctx
def render_to_response(self, context, **response_kwargs):
page_type = context['page_type']
theme = context['info']['theme']
page_type = context["page_type"]
theme = context["info"]["theme"]
if page_type == "pageHome":
template = 'mozorg/contentful-homepage.html'
template = "mozorg/contentful-homepage.html"
elif theme == "firefox":
template = 'firefox/contentful-all.html'
template = "firefox/contentful-all.html"
else:
template = 'mozorg/contentful-all.html'
template = "mozorg/contentful-all.html"
return l10n_utils.render(self.request,
template,
context,
**response_kwargs)
return l10n_utils.render(self.request, template, context, **response_kwargs)

Просмотреть файл

@ -12,45 +12,43 @@ from django.utils.safestring import mark_safe
from lib.l10n_utils.fluent import ftl, ftl_lazy
from product_details import product_details
from bedrock.mozorg.forms import (FORMATS, EmailInput, PrivacyWidget,
strip_parenthetical)
from bedrock.mozorg.forms import FORMATS, EmailInput, PrivacyWidget, strip_parenthetical
from bedrock.newsletter import utils
_newsletters_re = re.compile(r'^[\w,-]+$')
_newsletters_re = re.compile(r"^[\w,-]+$")
def validate_newsletters(newsletters):
if not newsletters:
raise ValidationError('No Newsletter Provided')
raise ValidationError("No Newsletter Provided")
newsletters = newsletters.replace(' ', '')
newsletters = newsletters.replace(" ", "")
if not _newsletters_re.match(newsletters):
raise ValidationError('Invalid Newsletter')
raise ValidationError("Invalid Newsletter")
return newsletters
def get_lang_choices(newsletters=None):
"""
Return a localized list of choices for language.
Return a localized list of choices for language.
List looks like: [[lang_code, lang_name], [lang_code, lang_name], ...]
List looks like: [[lang_code, lang_name], [lang_code, lang_name], ...]
:param newsletters: Either a comma separated string or a list of newsletter ids.
:param newsletters: Either a comma separated string or a list of newsletter ids.
"""
lang_choices = []
languages = utils.get_languages_for_newsletters(newsletters)
for lang in languages:
if lang in product_details.languages:
lang_name = product_details.languages[lang]['native']
lang_name = product_details.languages[lang]["native"]
else:
try:
locale = [loc for loc in product_details.languages
if loc.startswith(lang)][0]
locale = [loc for loc in product_details.languages if loc.startswith(lang)][0]
except IndexError:
continue
lang_name = product_details.languages[locale]['native']
lang_name = product_details.languages[locale]["native"]
lang_choices.append([lang, strip_parenthetical(lang_name)])
return sorted(lang_choices, key=itemgetter(1))
@ -59,50 +57,54 @@ class SimpleRadioSelect(widgets.RadioSelect):
"""
Render radio buttons as just labels with no <ul> chrome.
"""
template_name = 'newsletter/forms/simple_radio_select.html'
template_name = "newsletter/forms/simple_radio_select.html"
class BooleanTabularRadioSelect(widgets.RadioSelect):
"""
A Select Widget intended to be used with NullBooleanField.
"""
template_name = 'newsletter/forms/tabular_radio_select.html'
template_name = "newsletter/forms/tabular_radio_select.html"
wrap_label = False
def __init__(self, attrs=None):
choices = (
('true', ftl('newsletter-form-yes')),
('false', ftl('newsletter-form-no')),
("true", ftl("newsletter-form-yes")),
("false", ftl("newsletter-form-no")),
)
super(BooleanTabularRadioSelect, self).__init__(attrs, choices)
def format_value(self, value):
try:
return {
True: 'true', False: 'false',
'true': 'true', 'false': 'false',
True: "true",
False: "false",
"true": "true",
"false": "false",
}[value]
except KeyError:
return 'unknown'
return "unknown"
def value_from_datadict(self, data, files, name):
value = data.get(name)
return {
True: True,
False: False,
'true': True,
'false': False,
"true": True,
"false": False,
}.get(value)
def get_context(self, name, value, attrs):
context = super(BooleanTabularRadioSelect, self).get_context(
name, value, attrs)
context['wrap_label'] = False
context = super(BooleanTabularRadioSelect, self).get_context(name, value, attrs)
context["wrap_label"] = False
return context
class TableCheckboxInput(widgets.CheckboxInput):
"""Add table cell markup around the rendered checkbox"""
def render(self, *args, **kwargs):
out = super(TableCheckboxInput, self).render(*args, **kwargs)
return mark_safe("<td>" + out + "</td>")
@ -114,13 +116,14 @@ class CountrySelectForm(forms.Form):
us with their country so that we can include them in mailings relevant to
their area of the world.
"""
country = forms.ChoiceField(choices=[]) # will set choices based on locale
def __init__(self, locale, *args, **kwargs):
regions = product_details.get_regions(locale)
regions = sorted(iter(regions.items()), key=itemgetter(1))
super(CountrySelectForm, self).__init__(*args, **kwargs)
self.fields['country'].choices = regions
self.fields["country"].choices = regions
class ManageSubscriptionsForm(forms.Form):
@ -135,15 +138,11 @@ class ManageSubscriptionsForm(forms.Form):
@param kwargs: Other standard form kwargs
"""
format = forms.ChoiceField(widget=SimpleRadioSelect,
choices=FORMATS,
initial='H')
format = forms.ChoiceField(widget=SimpleRadioSelect, choices=FORMATS, initial="H")
remove_all = forms.BooleanField(required=False)
country = forms.ChoiceField(choices=[], # will set choices based on locale
required=False)
lang = forms.ChoiceField(choices=[], # will set choices based on newsletter languages
required=False)
country = forms.ChoiceField(choices=[], required=False) # will set choices based on locale
lang = forms.ChoiceField(choices=[], required=False) # will set choices based on newsletter languages
def __init__(self, locale, *args, **kwargs):
regions_dict = product_details.get_regions(locale)
@ -152,24 +151,24 @@ class ManageSubscriptionsForm(forms.Form):
languages = [x[0] for x in lang_choices]
lang = country = locale.lower()
if '-' in lang:
lang, country = lang.split('-', 1)
lang = lang if lang in languages else 'en'
if "-" in lang:
lang, country = lang.split("-", 1)
lang = lang if lang in languages else "en"
self.newsletters = kwargs.pop('newsletters', [])
self.newsletters = kwargs.pop("newsletters", [])
# Get initial - work with a copy so we're not modifying the
# data that was passed to us
initial = kwargs.get('initial', {}).copy()
if 'country' in initial and initial['country'] not in regions_dict:
initial = kwargs.get("initial", {}).copy()
if "country" in initial and initial["country"] not in regions_dict:
# clear the initial country if it's not in the list
del initial['country']
if not initial.get('country', None):
initial['country'] = country
if not initial.get('lang', None):
initial['lang'] = lang
del initial["country"]
if not initial.get("country", None):
initial["country"] = country
if not initial.get("lang", None):
initial["lang"] = lang
else:
lang = initial['lang']
lang = initial["lang"]
# Sometimes people are in ET with a language that is spelled a
# little differently from our list. E.g. we have 'es' on our
@ -188,24 +187,22 @@ class ManageSubscriptionsForm(forms.Form):
else:
# No luck - guess from the locale
lang = locale.lower()
if '-' in lang:
lang, _unused = lang.split('-', 1)
initial['lang'] = lang
if "-" in lang:
lang, _unused = lang.split("-", 1)
initial["lang"] = lang
kwargs['initial'] = initial
kwargs["initial"] = initial
super(ManageSubscriptionsForm, self).__init__(*args, **kwargs)
self.fields['country'].choices = regions
self.fields['lang'].choices = lang_choices
self.fields["country"].choices = regions
self.fields["lang"].choices = lang_choices
self.already_subscribed = initial.get('newsletters', [])
self.already_subscribed = initial.get("newsletters", [])
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'])
msg = ftl("newsletters-is-not-a-valid-newsletter", newsletter=newsletter, ftl_files=["mozorg/newsletters"])
raise ValidationError(msg)
return super(ManageSubscriptionsForm, self).clean()
@ -215,6 +212,7 @@ class NewsletterForm(forms.Form):
Form to let a user subscribe to or unsubscribe from a newsletter
on the manage existing newsletters page. Used in a FormSet.
"""
title = forms.CharField(required=False)
description = forms.CharField(required=False)
subscribed_radio = forms.BooleanField(
@ -234,19 +232,16 @@ class NewsletterFooterForm(forms.Form):
footer of a page (see newsletters/middleware.py) but sometimes
on a dedicated page.
"""
email = forms.EmailField(widget=EmailInput(attrs={'required': 'required'}))
email = forms.EmailField(widget=EmailInput(attrs={"required": "required"}))
# first/last_name not yet included in email_newsletter_form helper
# currently used on /contribute/friends/ (custom markup)
first_name = forms.CharField(widget=forms.TextInput, required=False)
last_name = forms.CharField(widget=forms.TextInput, required=False)
fmt = forms.ChoiceField(widget=SimpleRadioSelect,
choices=FORMATS,
initial='H')
fmt = forms.ChoiceField(widget=SimpleRadioSelect, choices=FORMATS, initial="H")
privacy = forms.BooleanField(widget=PrivacyWidget)
source_url = forms.CharField(required=False)
newsletters = forms.CharField(widget=forms.HiddenInput,
required=True,
max_length=100)
newsletters = forms.CharField(widget=forms.HiddenInput, required=True, max_length=100)
# has to take a newsletters argument so it can figure
# out which languages to list in the form.
@ -259,15 +254,14 @@ class NewsletterFooterForm(forms.Form):
except ValidationError:
# replace with most common good newsletter
# form validation will work with submitted data
newsletters = 'mozilla-and-you'
newsletters = "mozilla-and-you"
lang = locale.lower()
if '-' in lang:
lang, country = lang.split('-', 1)
if "-" in lang:
lang, country = lang.split("-", 1)
else:
country = ''
regions.insert(0, ('', ftl_lazy('newsletter-form-select-country-or-region',
fallback='newsletter-form-select-country')))
country = ""
regions.insert(0, ("", ftl_lazy("newsletter-form-select-country-or-region", fallback="newsletter-form-select-country")))
lang_choices = get_lang_choices(newsletters)
languages = [x[0] for x in lang_choices]
if lang not in languages:
@ -275,32 +269,26 @@ class NewsletterFooterForm(forms.Form):
# are translated into. Initialize the language field to no
# choice, to force the user to pick one of the languages that
# we do support.
lang = ''
lang_choices.insert(0, ('', ftl_lazy('newsletter-form-available-languages')))
lang = ""
lang_choices.insert(0, ("", ftl_lazy("newsletter-form-available-languages")))
super(NewsletterFooterForm, self).__init__(data, *args, **kwargs)
required_args = {
'required': 'required',
'aria-required': 'true',
"required": "required",
"aria-required": "true",
}
country_widget = widgets.Select(attrs=required_args)
self.fields['country'] = forms.ChoiceField(widget=country_widget,
choices=regions,
initial=country,
required=False)
self.fields["country"] = forms.ChoiceField(widget=country_widget, choices=regions, initial=country, required=False)
lang_widget = widgets.Select(attrs=required_args)
self.fields['lang'] = forms.TypedChoiceField(widget=lang_widget,
choices=lang_choices,
initial=lang,
required=False)
self.fields['newsletters'].initial = newsletters
self.fields["lang"] = forms.TypedChoiceField(widget=lang_widget, choices=lang_choices, initial=lang, required=False)
self.fields["newsletters"].initial = newsletters
def clean_newsletters(self):
return validate_newsletters(self.cleaned_data['newsletters'])
return validate_newsletters(self.cleaned_data["newsletters"])
def clean_source_url(self):
su = self.cleaned_data['source_url'].strip()
su = self.cleaned_data["source_url"].strip()
if su:
# limit to 255 characters by truncation
return su[:255]
@ -312,4 +300,5 @@ class EmailForm(forms.Form):
"""
Form to enter email, e.g. to be sent a recovery message
"""
email = forms.EmailField(widget=EmailInput(attrs={'required': 'required'}))
email = forms.EmailField(widget=EmailInput(attrs={"required": "required"}))

Просмотреть файл

@ -7,17 +7,16 @@ from bedrock.newsletter.models import Newsletter
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet', default=False,
help='If no error occurs, swallow all output.'),
parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="If no error occurs, swallow all output."),
def handle(self, *args, **options):
newsletters = basket.get_newsletters()
if not newsletters:
raise CommandError('No data from basket')
raise CommandError("No data from basket")
count = Newsletter.objects.refresh(newsletters)
if not options['quiet']:
if not options["quiet"]:
if count:
print('Updated %d newsletters' % count)
print("Updated %d newsletters" % count)
else:
print('Nothing to update')
print("Nothing to update")

Просмотреть файл

@ -3,8 +3,8 @@ from bedrock.redirects.util import redirect
redirectpatterns = (
# bug 926629
redirect(r'^newsletter/about_mobile(?:/(?:index\.html)?)?$', 'newsletter.subscribe'),
redirect(r'^newsletter/about_mozilla(?:/(?:index\.html)?)?$', 'mozorg.contribute'),
redirect(r'^newsletter/new(?:/(?:index\.html)?)?$', 'newsletter.subscribe'),
redirect(r'^newsletter/ios(?:/(?:index\.html)?)?$', 'firefox.browsers.mobile.ios'),
redirect(r"^newsletter/about_mobile(?:/(?:index\.html)?)?$", "newsletter.subscribe"),
redirect(r"^newsletter/about_mozilla(?:/(?:index\.html)?)?$", "mozorg.contribute"),
redirect(r"^newsletter/new(?:/(?:index\.html)?)?$", "newsletter.subscribe"),
redirect(r"^newsletter/ios(?:/(?:index\.html)?)?$", "firefox.browsers.mobile.ios"),
)

Просмотреть файл

@ -17,47 +17,61 @@ log = logging.getLogger(__name__)
@library.global_function
@jinja2.contextfunction
def email_newsletter_form(ctx, newsletters='mozilla-and-you', title=None,
subtitle=None, desc=None, include_country=True,
include_language=True, details=None,
use_thankyou=True, thankyou_head=None,
thankyou_content=None, footer=True,
process_form=True, include_title=None,
submit_text=None, button_class=None,
spinner_color=None, email_label=None,
email_placeholder=None):
request = ctx['request']
def email_newsletter_form(
ctx,
newsletters="mozilla-and-you",
title=None,
subtitle=None,
desc=None,
include_country=True,
include_language=True,
details=None,
use_thankyou=True,
thankyou_head=None,
thankyou_content=None,
footer=True,
process_form=True,
include_title=None,
submit_text=None,
button_class=None,
spinner_color=None,
email_label=None,
email_placeholder=None,
):
request = ctx["request"]
context = ctx.get_all()
success = bool(ctx.get('success'))
success = bool(ctx.get("success"))
if success and not use_thankyou:
return
form = ctx.get('newsletter_form', None)
form = ctx.get("newsletter_form", None)
if not form:
form = NewsletterFooterForm(newsletters, get_locale(request))
context.update(dict(
id=newsletters,
title=title,
subtitle=subtitle, # nested in/depends on include_title
desc=desc, # nested in/depends on include_title
include_country=include_country,
include_language=include_language,
details=details,
use_thankyou=use_thankyou,
thankyou_head=thankyou_head,
thankyou_content=thankyou_content,
footer=footer,
include_title=include_title if include_title is not None else footer,
form=form,
submit_text=submit_text,
button_class=button_class,
spinner_color=spinner_color,
success=success,
email_label=email_label,
email_placeholder=email_placeholder,
))
context.update(
dict(
id=newsletters,
title=title,
subtitle=subtitle, # nested in/depends on include_title
desc=desc, # nested in/depends on include_title
include_country=include_country,
include_language=include_language,
details=details,
use_thankyou=use_thankyou,
thankyou_head=thankyou_head,
thankyou_content=thankyou_content,
footer=footer,
include_title=include_title if include_title is not None else footer,
form=form,
submit_text=submit_text,
button_class=button_class,
spinner_color=spinner_color,
success=success,
email_label=email_label,
email_placeholder=email_placeholder,
)
)
html = render_to_string('newsletter/includes/form.html', context, request=request)
html = render_to_string("newsletter/includes/form.html", context, request=request)
return jinja2.Markup(html)

Просмотреть файл

@ -12,36 +12,36 @@ utils.basket.get_newsletters = news_mock
# Test data for newsletters
# In the format returned by utils.get_newsletters()
newsletters = {
u'mozilla-and-you': {
'active': True,
'show': True,
'title': "Firefox & You",
'languages': ['en', 'fr', 'de', 'pt', 'ru'],
'description': 'Firefox and you',
'order': 4,
"mozilla-and-you": {
"active": True,
"show": True,
"title": "Firefox & You",
"languages": ["en", "fr", "de", "pt", "ru"],
"description": "Firefox and you",
"order": 4,
},
u'firefox-tips': {
'active': True,
'show': True,
'title': 'Firefox Tips',
'languages': ['en', 'fr', 'de', 'pt', 'ru'],
'description': 'Firefox tips',
'order': 2,
"firefox-tips": {
"active": True,
"show": True,
"title": "Firefox Tips",
"languages": ["en", "fr", "de", "pt", "ru"],
"description": "Firefox tips",
"order": 2,
},
u'beta': {
'active': False,
'show': False,
'title': 'Beta News',
'languages': ['en'],
'description': 'Beta News',
'order': 3,
"beta": {
"active": False,
"show": False,
"title": "Beta News",
"languages": ["en"],
"description": "Beta News",
"order": 3,
},
u'join-mozilla': {
'active': True,
'show': False,
'title': 'Join Mozilla',
'languages': ['en', 'es'],
'description': 'Join Mozilla',
'order': 1,
"join-mozilla": {
"active": True,
"show": False,
"title": "Join Mozilla",
"languages": ["en", "es"],
"description": "Join Mozilla",
"order": 1,
},
}

Просмотреть файл

@ -6,34 +6,32 @@ from pyquery import PyQuery as pq
from bedrock.mozorg.tests import TestCase
@patch('bedrock.newsletter.forms.get_lang_choices',
lambda *x: [['en', 'English'], ['fr', 'French'], ['pt', 'Portuguese']])
@patch('lib.l10n_utils.translations_for_template',
lambda *x: ['en-US', 'fr', 'pt-BR', 'af'])
@patch("bedrock.newsletter.forms.get_lang_choices", lambda *x: [["en", "English"], ["fr", "French"], ["pt", "Portuguese"]])
@patch("lib.l10n_utils.translations_for_template", lambda *x: ["en-US", "fr", "pt-BR", "af"])
class TestNewsletterFooter(TestCase):
def setUp(self):
self.view_name = 'newsletter.subscribe'
self.view_name = "newsletter.subscribe"
@override_settings(DEV=True)
def test_country_selected(self):
"""
The correct country for the locale should be initially selected.
"""
with self.activate('en-US'):
with self.activate("en-US"):
resp = self.client.get(reverse(self.view_name))
doc = pq(resp.content)
assert doc('#id_country option[selected="selected"]').val() == 'us'
assert doc('#id_country option[selected="selected"]').val() == "us"
# no country in locale, no country selected
with self.activate('fr'):
with self.activate("fr"):
resp = self.client.get(reverse(self.view_name))
doc = pq(resp.content)
assert doc('#id_country option[selected="selected"]').val() == ''
assert doc('#id_country option[selected="selected"]').val() == ""
with self.activate('pt-BR'):
with self.activate("pt-BR"):
resp = self.client.get(reverse(self.view_name))
doc = pq(resp.content)
assert doc('#id_country option[selected="selected"]').val() == 'br'
assert doc('#id_country option[selected="selected"]').val() == "br"
@override_settings(DEV=True)
def test_language_selected(self):
@ -41,19 +39,19 @@ class TestNewsletterFooter(TestCase):
The correct language for the locale should be initially selected or
'en' if it's not an option.
"""
with self.activate('fr'):
with self.activate("fr"):
resp = self.client.get(reverse(self.view_name))
doc = pq(resp.content)
assert doc('#id_lang option[selected="selected"]').val() == 'fr'
assert doc('#id_lang option[selected="selected"]').val() == "fr"
# with hyphenated regional locale, should have only lang
with self.activate('pt-BR'):
with self.activate("pt-BR"):
resp = self.client.get(reverse(self.view_name))
doc = pq(resp.content)
assert doc('#id_lang option[selected="selected"]').val() == 'pt'
assert doc('#id_lang option[selected="selected"]').val() == "pt"
# not supported. should default to ''
with self.activate('af'):
with self.activate("af"):
resp = self.client.get(reverse(self.view_name))
doc = pq(resp.content)
assert doc('#id_lang option[selected="selected"]').val() == ''
assert doc('#id_lang option[selected="selected"]').val() == ""

Просмотреть файл

@ -5,8 +5,10 @@ import mock
from bedrock.mozorg.tests import TestCase
from bedrock.newsletter.forms import (
BooleanTabularRadioSelect, ManageSubscriptionsForm,
NewsletterFooterForm, NewsletterForm,
BooleanTabularRadioSelect,
ManageSubscriptionsForm,
NewsletterFooterForm,
NewsletterForm,
)
from bedrock.newsletter.tests import newsletters
@ -16,7 +18,6 @@ newsletters_mock.return_value = newsletters
class TestRenderers(TestCase):
def test_str_true(self):
"""renderer starts with True selected if value given is True"""
widget = BooleanTabularRadioSelect()
@ -51,91 +52,101 @@ class TestRenderers(TestCase):
class TestManageSubscriptionsForm(TestCase):
@mock.patch('bedrock.newsletter.forms.get_lang_choices')
@mock.patch("bedrock.newsletter.forms.get_lang_choices")
def test_locale(self, langs_mock):
"""Get initial lang, country from the right places"""
# Get initial lang and country from 'initial' if provided there,
# else from the locale passed in
# First, not passed in
langs_mock.return_value = [['en', 'English'], ['pt', 'Portuguese']]
langs_mock.return_value = [["en", "English"], ["pt", "Portuguese"]]
locale = "en-US"
form = ManageSubscriptionsForm(locale=locale, initial={})
self.assertEqual('en', form.initial['lang'])
self.assertEqual('us', form.initial['country'])
self.assertEqual("en", form.initial["lang"])
self.assertEqual("us", form.initial["country"])
# now, test with them passed in.
form = ManageSubscriptionsForm(locale=locale,
initial={
'lang': 'pt',
'country': 'br',
})
self.assertEqual('pt', form.initial['lang'])
self.assertEqual('br', form.initial['country'])
form = ManageSubscriptionsForm(
locale=locale,
initial={
"lang": "pt",
"country": "br",
},
)
self.assertEqual("pt", form.initial["lang"])
self.assertEqual("br", form.initial["country"])
@mock.patch('bedrock.newsletter.forms.get_lang_choices')
@mock.patch("bedrock.newsletter.forms.get_lang_choices")
def test_long_language(self, langs_mock):
"""Fuzzy match their language preference"""
# Suppose their selected language in ET is a long form ("es-ES")
# while we only have the short forms ("es") in our list of
# valid languages. Or vice-versa. Find the match to the one
# in our list and use that, not the lang from ET.
locale = 'en-US'
langs_mock.return_value = [['en', 'English'], ['es', 'Spanish']]
form = ManageSubscriptionsForm(locale=locale,
initial={
'lang': 'es-ES',
'country': 'es',
})
locale = "en-US"
langs_mock.return_value = [["en", "English"], ["es", "Spanish"]]
form = ManageSubscriptionsForm(
locale=locale,
initial={
"lang": "es-ES",
"country": "es",
},
)
# Initial value is 'es'
self.assertEqual('es', form.initial['lang'])
self.assertEqual("es", form.initial["lang"])
def test_bad_language(self):
"""Handle their language preference if it's not valid"""
# Suppose their selected language in ET is one we don't recognize
# at all. Use the language from their locale instead.
locale = "pt-BR"
form = ManageSubscriptionsForm(locale=locale,
initial={
'lang': 'zz',
'country': 'es',
})
self.assertEqual('pt', form.initial['lang'])
form = ManageSubscriptionsForm(
locale=locale,
initial={
"lang": "zz",
"country": "es",
},
)
self.assertEqual("pt", form.initial["lang"])
def test_bad_country(self):
"""Handle their country preference if it's not valid"""
# Suppose their selected country in CTMS is one we don't recognize
# at all. Use the country from their locale instead.
locale = "pt-BR"
form = ManageSubscriptionsForm(locale=locale,
initial={
'lang': 'zz',
'country': 'Spain',
})
self.assertEqual('br', form.initial['country'])
form = ManageSubscriptionsForm(
locale=locale,
initial={
"lang": "zz",
"country": "Spain",
},
)
self.assertEqual("br", form.initial["country"])
def test_no_country(self):
"""Handle their country preference if it's not set"""
# Suppose they have no selected country in CTMS.
# Use the country from their locale instead.
locale = "en-US"
form = ManageSubscriptionsForm(locale=locale,
initial={
'lang': 'zz',
})
self.assertEqual('us', form.initial['country'])
form = ManageSubscriptionsForm(
locale=locale,
initial={
"lang": "zz",
},
)
self.assertEqual("us", form.initial["country"])
@mock.patch('bedrock.newsletter.utils.get_newsletters', newsletters_mock)
@mock.patch("bedrock.newsletter.utils.get_newsletters", newsletters_mock)
class TestNewsletterForm(TestCase):
def test_form(self):
"""test NewsletterForm"""
# not much to test, but at least construct one
title = "Newsletter title"
newsletter = 'newsletter-a'
newsletter = "newsletter-a"
initial = {
'title': title,
'newsletter': newsletter,
'subscribed_radio': True,
'subscribed_check': True,
"title": title,
"newsletter": newsletter,
"subscribed_radio": True,
"subscribed_check": True,
}
form = NewsletterForm(initial=initial)
rendered = str(form)
@ -144,148 +155,148 @@ class TestNewsletterForm(TestCase):
# And validate one
form = NewsletterForm(data=initial)
self.assertTrue(form.is_valid())
self.assertEqual(title, form.cleaned_data['title'])
self.assertEqual(title, form.cleaned_data["title"])
def test_multiple_newsletters(self):
"""Should allow to subscribe to multiple newsletters at a time."""
newsletters = 'mozilla-and-you,beta'
newsletters = "mozilla-and-you,beta"
data = {
'email': 'dude@example.com',
'lang': 'en',
'privacy': 'Y',
'fmt': 'H',
'newsletters': newsletters,
"email": "dude@example.com",
"lang": "en",
"privacy": "Y",
"fmt": "H",
"newsletters": newsletters,
}
form = NewsletterFooterForm(newsletters, 'en-US', data=data.copy())
form = NewsletterFooterForm(newsletters, "en-US", data=data.copy())
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['newsletters'], newsletters)
self.assertEqual(form.cleaned_data["newsletters"], newsletters)
# whitespace shouldn't matter
form = NewsletterFooterForm('mozilla-and-you , beta ', 'en-US', data=data.copy())
form = NewsletterFooterForm("mozilla-and-you , beta ", "en-US", data=data.copy())
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['newsletters'], newsletters)
self.assertEqual(form.cleaned_data["newsletters"], newsletters)
@mock.patch('bedrock.newsletter.utils.get_newsletters', newsletters_mock)
@mock.patch("bedrock.newsletter.utils.get_newsletters", newsletters_mock)
class TestNewsletterFooterForm(TestCase):
newsletter_name = 'mozilla-and-you'
newsletter_name = "mozilla-and-you"
def test_form(self):
"""Form works normally"""
data = {
'email': 'foo@example.com',
'lang': 'fr',
'first_name': 'Walter',
'last_name': 'Sobchak',
'privacy': True,
'fmt': 'H',
'source_url': 'https://accounts.firefox.com',
'newsletters': self.newsletter_name,
"email": "foo@example.com",
"lang": "fr",
"first_name": "Walter",
"last_name": "Sobchak",
"privacy": True,
"fmt": "H",
"source_url": "https://accounts.firefox.com",
"newsletters": self.newsletter_name,
}
form = NewsletterFooterForm(self.newsletter_name, locale='en-US', data=data.copy())
form = NewsletterFooterForm(self.newsletter_name, locale="en-US", data=data.copy())
self.assertTrue(form.is_valid(), form.errors)
cleaned_data = form.cleaned_data
self.assertEqual(data['fmt'], cleaned_data['fmt'])
self.assertEqual(data['lang'], cleaned_data['lang'])
self.assertEqual(data['source_url'], cleaned_data['source_url'])
self.assertEqual(data["fmt"], cleaned_data["fmt"])
self.assertEqual(data["lang"], cleaned_data["lang"])
self.assertEqual(data["source_url"], cleaned_data["source_url"])
def test_source_url_non_url(self):
"""Form works normally"""
data = {
'email': 'foo@example.com',
'lang': 'fr',
'first_name': 'Walter',
'last_name': 'Sobchak',
'privacy': True,
'fmt': 'H',
'source_url': 'about:devtools?dude=abiding',
'newsletters': self.newsletter_name,
"email": "foo@example.com",
"lang": "fr",
"first_name": "Walter",
"last_name": "Sobchak",
"privacy": True,
"fmt": "H",
"source_url": "about:devtools?dude=abiding",
"newsletters": self.newsletter_name,
}
form = NewsletterFooterForm(self.newsletter_name, locale='en-US', data=data.copy())
form = NewsletterFooterForm(self.newsletter_name, locale="en-US", data=data.copy())
self.assertTrue(form.is_valid(), form.errors)
cleaned_data = form.cleaned_data
self.assertEqual(data['source_url'], cleaned_data['source_url'])
self.assertEqual(data["source_url"], cleaned_data["source_url"])
def test_source_url_too_long(self):
"""Form works normally"""
data = {
'email': 'foo@example.com',
'lang': 'fr',
'first_name': 'Walter',
'last_name': 'Sobchak',
'privacy': True,
'fmt': 'H',
'source_url': 'about:devtools' * 20,
'newsletters': self.newsletter_name,
"email": "foo@example.com",
"lang": "fr",
"first_name": "Walter",
"last_name": "Sobchak",
"privacy": True,
"fmt": "H",
"source_url": "about:devtools" * 20,
"newsletters": self.newsletter_name,
}
form = NewsletterFooterForm(self.newsletter_name, locale='en-US', data=data.copy())
form = NewsletterFooterForm(self.newsletter_name, locale="en-US", data=data.copy())
self.assertTrue(form.is_valid(), form.errors)
cleaned_data = form.cleaned_data
self.assertEqual(data['source_url'][:255], cleaned_data['source_url'])
self.assertEqual(data["source_url"][:255], cleaned_data["source_url"])
def test_country_default(self):
"""country defaults based on the locale.
But only for country based locales (e.g. pt-BR)"""
form = NewsletterFooterForm(self.newsletter_name, locale='fr')
self.assertEqual('', form.fields['country'].initial)
form = NewsletterFooterForm(self.newsletter_name, locale='pt-BR')
self.assertEqual('br', form.fields['country'].initial)
form = NewsletterFooterForm(self.newsletter_name, locale='zh-TW')
self.assertEqual('tw', form.fields['country'].initial)
form = NewsletterFooterForm(self.newsletter_name, locale="fr")
self.assertEqual("", form.fields["country"].initial)
form = NewsletterFooterForm(self.newsletter_name, locale="pt-BR")
self.assertEqual("br", form.fields["country"].initial)
form = NewsletterFooterForm(self.newsletter_name, locale="zh-TW")
self.assertEqual("tw", form.fields["country"].initial)
def test_lang_choices_per_newsletter(self):
"""Lang choices should be based on the newsletter."""
form = NewsletterFooterForm('beta', 'en-US')
choices = [lang[0] for lang in form.fields['lang'].choices]
self.assertEqual(choices, ['en'])
form = NewsletterFooterForm("beta", "en-US")
choices = [lang[0] for lang in form.fields["lang"].choices]
self.assertEqual(choices, ["en"])
form = NewsletterFooterForm('join-mozilla', 'en-US')
choices = [lang[0] for lang in form.fields['lang'].choices]
self.assertEqual(choices, ['en', 'es'])
form = NewsletterFooterForm("join-mozilla", "en-US")
choices = [lang[0] for lang in form.fields["lang"].choices]
self.assertEqual(choices, ["en", "es"])
def test_lang_choices_multiple_newsletters(self):
"""Lang choices should be based on all newsletters."""
form = NewsletterFooterForm('join-mozilla,firefox-tips', 'en-US')
choices = [lang[0] for lang in form.fields['lang'].choices]
self.assertEqual(choices, ['de', 'en', 'es', 'fr', 'pt', 'ru'])
form = NewsletterFooterForm("join-mozilla,firefox-tips", "en-US")
choices = [lang[0] for lang in form.fields["lang"].choices]
self.assertEqual(choices, ["de", "en", "es", "fr", "pt", "ru"])
def test_lang_default(self):
"""lang defaults based on the locale"""
form = NewsletterFooterForm(self.newsletter_name, locale='pt-BR')
self.assertEqual('pt', form.fields['lang'].initial)
form = NewsletterFooterForm(self.newsletter_name, locale="pt-BR")
self.assertEqual("pt", form.fields["lang"].initial)
def test_lang_default_not_supported(self):
"""lang defaults to blank if not supported by newsletter."""
form = NewsletterFooterForm('beta', locale='pt-BR')
self.assertEqual('', form.fields['lang'].initial)
form = NewsletterFooterForm("beta", locale="pt-BR")
self.assertEqual("", form.fields["lang"].initial)
def test_lang_not_required(self):
"""lang not required since field not always displayed"""
data = {
'email': 'foo@example.com',
'privacy': True,
'fmt': 'H',
'newsletters': self.newsletter_name,
"email": "foo@example.com",
"privacy": True,
"fmt": "H",
"newsletters": self.newsletter_name,
}
form = NewsletterFooterForm(self.newsletter_name, locale='en-US', data=data.copy())
form = NewsletterFooterForm(self.newsletter_name, locale="en-US", data=data.copy())
self.assertTrue(form.is_valid(), form.errors)
# Form returns '' for lang, so we don't accidentally change the user's
# preferred language thinking they entered something here that they
# didn't.
self.assertEqual(u'', form.cleaned_data['lang'])
self.assertEqual("", form.cleaned_data["lang"])
def test_privacy_required(self):
"""they have to check the privacy box"""
data = {
'email': 'foo@example.com',
'privacy': False,
'fmt': 'H',
'newsletters': self.newsletter_name,
"email": "foo@example.com",
"privacy": False,
"fmt": "H",
"newsletters": self.newsletter_name,
}
form = NewsletterFooterForm(self.newsletter_name, locale='en-US', data=data)
form = NewsletterFooterForm(self.newsletter_name, locale="en-US", data=data)
self.assertFalse(form.is_valid())
self.assertIn('privacy', form.errors)
self.assertIn("privacy", form.errors)
def test_invalid_newsletter_is_error(self):
"""Invalid newsletter should not raise exception. Bug 1072302.
@ -294,26 +305,26 @@ class TestNewsletterFooterForm(TestCase):
form error.
"""
data = {
'email': 'fred@example.com',
'lang': 'fr',
'privacy': True,
'fmt': 'H',
'newsletters': '',
"email": "fred@example.com",
"lang": "fr",
"privacy": True,
"fmt": "H",
"newsletters": "",
}
form = NewsletterFooterForm('', locale='en-US', data=data.copy())
form = NewsletterFooterForm("", locale="en-US", data=data.copy())
self.assertFalse(form.is_valid())
self.assertIn('newsletters', form.errors)
self.assertEqual(form.errors['newsletters'], [u'This field is required.'])
self.assertIn("newsletters", form.errors)
self.assertEqual(form.errors["newsletters"], ["This field is required."])
invalid_newsletter = '!nv@l1d'
invalid_newsletter = "!nv@l1d"
data = {
'email': 'fred@example.com',
'lang': 'fr',
'privacy': True,
'fmt': 'H',
'newsletters': invalid_newsletter,
"email": "fred@example.com",
"lang": "fr",
"privacy": True,
"fmt": "H",
"newsletters": invalid_newsletter,
}
form = NewsletterFooterForm(invalid_newsletter, locale='en-US', data=data.copy())
form = NewsletterFooterForm(invalid_newsletter, locale="en-US", data=data.copy())
self.assertFalse(form.is_valid())
self.assertIn('newsletters', form.errors)
self.assertEqual(form.errors['newsletters'], [u'Invalid Newsletter'])
self.assertIn("newsletters", form.errors)
self.assertEqual(form.errors["newsletters"], ["Invalid Newsletter"])

Просмотреть файл

@ -13,32 +13,26 @@ newsletters_mock.return_value = newsletters
class TestNewsletterModel(TestCase):
def setUp(self):
Newsletter.objects.create(
slug='dude',
data={
'title': 'Abide',
'languages': ['en']
},
slug="dude",
data={"title": "Abide", "languages": ["en"]},
)
Newsletter.objects.create(
slug='donnie',
data={
'title': 'Walrus',
'languages': ['de']
},
slug="donnie",
data={"title": "Walrus", "languages": ["de"]},
)
self.data = {
'dude': {
'title': 'Abide',
'languages': ['en'],
"dude": {
"title": "Abide",
"languages": ["en"],
},
'donnie': {
'title': 'Walrus',
'languages': ['de'],
"donnie": {
"title": "Walrus",
"languages": ["de"],
},
}
def test_refresh_with_change(self):
self.data['donnie']['languages'].append('fr')
self.data["donnie"]["languages"].append("fr")
count = Newsletter.objects.refresh(self.data)
self.assertEqual(count, 2)
# run again to verify updated
@ -55,32 +49,32 @@ class TestNewsletterModel(TestCase):
self.assertEqual(result, self.data)
@mock.patch('bedrock.newsletter.utils.get_newsletters', newsletters_mock)
@mock.patch("bedrock.newsletter.utils.get_newsletters", newsletters_mock)
class TestGetNewsletterLanguages(TestCase):
def test_newsletter_langs(self):
"""Without args should return all langs."""
result = utils.get_languages_for_newsletters()
good_set = {'en', 'es', 'fr', 'de', 'pt', 'ru'}
good_set = {"en", "es", "fr", "de", "pt", "ru"}
self.assertSetEqual(good_set, result)
def test_single_newsletter_langs(self):
"""Should return languages for a single newsletter."""
result = utils.get_languages_for_newsletters('join-mozilla')
good_set = {'en', 'es'}
result = utils.get_languages_for_newsletters("join-mozilla")
good_set = {"en", "es"}
self.assertSetEqual(good_set, result)
def test_list_newsletter_langs(self):
"""Should return all languages for specified list of newsletters."""
result = utils.get_languages_for_newsletters(['join-mozilla', 'beta'])
good_set = {'en', 'es'}
result = utils.get_languages_for_newsletters(["join-mozilla", "beta"])
good_set = {"en", "es"}
self.assertSetEqual(good_set, result)
result = utils.get_languages_for_newsletters(['firefox-tips', 'beta'])
good_set = {'en', 'fr', 'de', 'pt', 'ru'}
result = utils.get_languages_for_newsletters(["firefox-tips", "beta"])
good_set = {"en", "fr", "de", "pt", "ru"}
self.assertSetEqual(good_set, result)
def test_works_with_bad_newsletter(self):
"""If given a bad newsletter name, should still return a set."""
result = utils.get_languages_for_newsletters(['join-mozilla', 'eldudarino'])
good_set = {'en', 'es'}
result = utils.get_languages_for_newsletters(["join-mozilla", "eldudarino"])
good_set = {"en", "es"}
self.assertSetEqual(good_set, result)

Просмотреть файл

@ -27,132 +27,119 @@ class TestViews(TestCase):
def setUp(self):
self.rf = RequestFactory()
@patch('bedrock.newsletter.views.l10n_utils.render')
@patch("bedrock.newsletter.views.l10n_utils.render")
def test_updated_allows_good_tokens(self, mock_render):
token = str(uuid.uuid4())
req = self.rf.get('/', {'token': token, 'unsub': 1})
req = self.rf.get("/", {"token": token, "unsub": 1})
updated(req)
self.assertEqual(mock_render.call_args[0][2]['token'], token)
self.assertEqual(mock_render.call_args[0][2]["token"], token)
@patch('bedrock.newsletter.views.l10n_utils.render')
@patch("bedrock.newsletter.views.l10n_utils.render")
def test_updated_disallows_bad_tokens(self, mock_render):
token = 'the-dude'
req = self.rf.get('/', {'token': token, 'unsub': 1})
token = "the-dude"
req = self.rf.get("/", {"token": token, "unsub": 1})
updated(req)
assert mock_render.call_args[0][2]['token'] is None
assert mock_render.call_args[0][2]["token"] is None
token = '\'>"><img src=x onerror=alert(1)>'
req = self.rf.get('/', {'token': token, 'unsub': 1})
token = "'>\"><img src=x onerror=alert(1)>"
req = self.rf.get("/", {"token": token, "unsub": 1})
updated(req)
assert mock_render.call_args[0][2]['token'] is None
assert mock_render.call_args[0][2]["token"] is None
# Always mock basket.request to be sure we never actually call basket
# during tests.
@patch('basket.base.request')
@patch("basket.base.request")
class TestExistingNewsletterView(TestCase):
def setUp(self):
self.token = str(uuid.uuid4())
self.user = {
'newsletters': [u'mozilla-and-you'],
'token': self.token,
'email': u'user@example.com',
'lang': u'pt',
'country': u'br',
'format': u'T',
"newsletters": ["mozilla-and-you"],
"token": self.token,
"email": "user@example.com",
"lang": "pt",
"country": "br",
"format": "T",
}
# By default, data matches user's existing data; change it
# in the test as desired. Also, user has accepted privacy
# checkbox.
self.data = {
u'form-MAX_NUM_FORMS': 4,
u'form-INITIAL_FORMS': 4,
u'form-TOTAL_FORMS': 4,
u'email': self.user['email'],
u'lang': self.user['lang'],
u'country': self.user['country'],
u'format': self.user['format'],
u'privacy': u'on',
u'form-0-newsletter': u'mozilla-and-you',
u'form-0-subscribed_radio': u'true',
u'form-1-newsletter': u'mobile',
u'form-1-subscribed_radio': u'false',
u'form-2-newsletter': u'firefox-tips',
u'form-2-subscribed_check': u'false',
u'form-3-newsletter': u'join-mozilla',
u'form-3-subscribed_check': u'false',
u'submit': u'Save Preferences',
"form-MAX_NUM_FORMS": 4,
"form-INITIAL_FORMS": 4,
"form-TOTAL_FORMS": 4,
"email": self.user["email"],
"lang": self.user["lang"],
"country": self.user["country"],
"format": self.user["format"],
"privacy": "on",
"form-0-newsletter": "mozilla-and-you",
"form-0-subscribed_radio": "true",
"form-1-newsletter": "mobile",
"form-1-subscribed_radio": "false",
"form-2-newsletter": "firefox-tips",
"form-2-subscribed_check": "false",
"form-3-newsletter": "join-mozilla",
"form-3-subscribed_check": "false",
"submit": "Save Preferences",
}
super(TestExistingNewsletterView, self).setUp()
@patch('bedrock.newsletter.utils.get_newsletters')
@patch("bedrock.newsletter.utils.get_newsletters")
def test_will_show_confirm_copy(self, get_newsletters, mock_basket_request):
# After successful confirm, ensure proper context var is set to display
# confirmation-specific copy.
get_newsletters.return_value = newsletters
url = "%s?confirm=1" % reverse('newsletter.existing.token', args=(self.token,))
url = "%s?confirm=1" % reverse("newsletter.existing.token", args=(self.token,))
# noinspection PyUnresolvedReferences
with patch.multiple('basket',
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render') as render:
basket_patches['request'].return_value = self.user
render.return_value = HttpResponse('')
with patch.multiple("basket", request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render") as render:
basket_patches["request"].return_value = self.user
render.return_value = HttpResponse("")
self.client.get(url)
request, template_name, context = render.call_args[0]
self.assertEqual(context['did_confirm'], True)
self.assertEqual(context["did_confirm"], True)
@patch('bedrock.newsletter.utils.get_newsletters')
@patch("bedrock.newsletter.utils.get_newsletters")
def test_get_token(self, get_newsletters, mock_basket_request):
# If user gets page with valid token in their URL, they
# see their data, and no privacy checkbox is presented
get_newsletters.return_value = newsletters
url = reverse('newsletter.existing.token', args=(self.token,))
url = reverse("newsletter.existing.token", args=(self.token,))
# noinspection PyUnresolvedReferences
with patch.multiple('basket',
update_user=DEFAULT,
subscribe=DEFAULT,
unsubscribe=DEFAULT,
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render') as render:
basket_patches['request'].return_value = self.user
render.return_value = HttpResponse('')
with patch.multiple("basket", update_user=DEFAULT, subscribe=DEFAULT, unsubscribe=DEFAULT, request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render") as render:
basket_patches["request"].return_value = self.user
render.return_value = HttpResponse("")
self.client.get(url)
request, template_name, context = render.call_args[0]
form = context['form']
self.assertNotIn('privacy', form.fields)
self.assertEqual(self.user['lang'], form.initial['lang'])
form = context["form"]
self.assertNotIn("privacy", form.fields)
self.assertEqual(self.user["lang"], form.initial["lang"])
@patch('bedrock.newsletter.utils.get_newsletters')
@patch("bedrock.newsletter.utils.get_newsletters")
def test_show(self, get_newsletters, mock_basket_request):
# Newsletters are only listed if the user is subscribed to them,
# or they are marked 'show' and 'active' in the settings
get_newsletters.return_value = newsletters
# Find a newsletter without 'show' and subscribe the user to it
for newsletter, data in newsletters.items():
if not data.get('show', False):
self.user['newsletters'] = [newsletter]
if not data.get("show", False):
self.user["newsletters"] = [newsletter]
break
url = reverse('newsletter.existing.token', args=(self.token,))
with patch.multiple('basket',
update_user=DEFAULT,
subscribe=DEFAULT,
unsubscribe=DEFAULT,
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render') as render:
basket_patches['request'].return_value = self.user
render.return_value = HttpResponse('')
url = reverse("newsletter.existing.token", args=(self.token,))
with patch.multiple("basket", update_user=DEFAULT, subscribe=DEFAULT, unsubscribe=DEFAULT, request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render") as render:
basket_patches["request"].return_value = self.user
render.return_value = HttpResponse("")
self.client.get(url)
request, template_name, context = render.call_args[0]
forms = context['formset'].initial_forms
forms = context["formset"].initial_forms
shown = set([form.initial['newsletter'] for form in forms])
inactive = set([newsletter for newsletter, data
in newsletters.items()
if not data.get('active', False)])
to_show = set([newsletter for newsletter, data
in newsletters.items()
if data.get('show', False)]) - inactive
subscribed = set(self.user['newsletters'])
shown = set([form.initial["newsletter"] for form in forms])
inactive = set([newsletter for newsletter, data in newsletters.items() if not data.get("active", False)])
to_show = set([newsletter for newsletter, data in newsletters.items() if data.get("show", False)]) - inactive
subscribed = set(self.user["newsletters"])
# All subscribed newsletters except inactive ones are shown
self.assertEqual(set(), subscribed - inactive - shown)
@ -163,372 +150,325 @@ class TestExistingNewsletterView(TestCase):
def test_get_no_token(self, mock_basket_request):
# No token in URL - should redirect to recovery
url = reverse('newsletter.existing.token', args=('',))
url = reverse("newsletter.existing.token", args=("",))
rsp = self.client.get(url)
self.assertEqual(302, rsp.status_code)
self.assertTrue(rsp['Location'].endswith(reverse('newsletter.recovery')))
self.assertTrue(rsp["Location"].endswith(reverse("newsletter.recovery")))
def test_get_user_not_found(self, mock_basket_request):
# Token in URL but not a valid token - should redirect to recovery
rand_token = str(uuid.uuid4())
url = reverse('newsletter.existing.token', args=(rand_token,))
with patch.multiple('basket',
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render') as render:
render.return_value = HttpResponse('')
with patch('django.contrib.messages.add_message') as add_msg:
basket_patches['request'].side_effect = basket.BasketException
url = reverse("newsletter.existing.token", args=(rand_token,))
with patch.multiple("basket", request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render") as render:
render.return_value = HttpResponse("")
with patch("django.contrib.messages.add_message") as add_msg:
basket_patches["request"].side_effect = basket.BasketException
rsp = self.client.get(url)
# Should have given a message
self.assertEqual(1, add_msg.call_count,
msg=repr(add_msg.call_args_list))
self.assertEqual(1, add_msg.call_count, msg=repr(add_msg.call_args_list))
# Should have been redirected to recovery page
self.assertEqual(302, rsp.status_code)
self.assertTrue(rsp['Location'].endswith(reverse('newsletter.recovery')))
self.assertTrue(rsp["Location"].endswith(reverse("newsletter.recovery")))
def test_invalid_token(self, mock_basket_request):
# "Token" in URL is not syntactically a UUID - should redirect to
# recovery *without* calling Exact Target
token = "not a token"
url = reverse('newsletter.existing.token', args=(token,))
with patch.multiple('basket', request=DEFAULT) as basket_patches:
with patch('django.contrib.messages.add_message') as add_msg:
url = reverse("newsletter.existing.token", args=(token,))
with patch.multiple("basket", request=DEFAULT) as basket_patches:
with patch("django.contrib.messages.add_message") as add_msg:
rsp = self.client.get(url, follow=False)
self.assertEqual(0, basket_patches['request'].call_count)
self.assertEqual(0, basket_patches["request"].call_count)
self.assertEqual(1, add_msg.call_count)
self.assertEqual(302, rsp.status_code)
self.assertTrue(rsp['Location'].endswith(reverse('newsletter.recovery')))
self.assertTrue(rsp["Location"].endswith(reverse("newsletter.recovery")))
def test_post_user_not_found(self, mock_basket_request):
# User submits form and passed token, but no user was found
# Should issue message and redirect to recovery
rand_token = str(uuid.uuid4())
url = reverse('newsletter.existing.token', args=(rand_token,))
with patch.multiple('basket',
update_user=DEFAULT,
subscribe=DEFAULT,
unsubscribe=DEFAULT,
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render') as render:
render.return_value = HttpResponse('')
with patch('django.contrib.messages.add_message') as add_msg:
basket_patches['request'].side_effect = basket.BasketException
url = reverse("newsletter.existing.token", args=(rand_token,))
with patch.multiple("basket", update_user=DEFAULT, subscribe=DEFAULT, unsubscribe=DEFAULT, request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render") as render:
render.return_value = HttpResponse("")
with patch("django.contrib.messages.add_message") as add_msg:
basket_patches["request"].side_effect = basket.BasketException
rsp = self.client.post(url, self.data)
# Shouldn't call basket except for the attempt to find the user
self.assertEqual(0, basket_patches['update_user'].call_count)
self.assertEqual(0, basket_patches['unsubscribe'].call_count)
self.assertEqual(0, basket_patches['subscribe'].call_count)
self.assertEqual(0, basket_patches["update_user"].call_count)
self.assertEqual(0, basket_patches["unsubscribe"].call_count)
self.assertEqual(0, basket_patches["subscribe"].call_count)
# Should have given a message
self.assertEqual(1, add_msg.call_count,
msg=repr(add_msg.call_args_list))
self.assertEqual(1, add_msg.call_count, msg=repr(add_msg.call_args_list))
# Should have been redirected to recovery page
self.assertEqual(302, rsp.status_code)
self.assertTrue(rsp['Location'].endswith(reverse('newsletter.recovery')))
self.assertTrue(rsp["Location"].endswith(reverse("newsletter.recovery")))
@patch('bedrock.newsletter.utils.get_newsletters')
@patch("bedrock.newsletter.utils.get_newsletters")
def test_subscribing(self, get_newsletters, mock_basket_request):
get_newsletters.return_value = newsletters
# They subscribe to firefox-tips
self.data['form-2-subscribed_check'] = u'true'
self.data["form-2-subscribed_check"] = "true"
# in English - and that's their language too
self.user['lang'] = u'en'
self.data['lang'] = u'en'
url = reverse('newsletter.existing.token', args=(self.token,))
with patch.multiple('basket',
update_user=DEFAULT,
subscribe=DEFAULT,
unsubscribe=DEFAULT,
request=DEFAULT) as basket_patches:
with patch('django.contrib.messages.add_message') as add_msg:
with patch('lib.l10n_utils.render'):
basket_patches['request'].return_value = self.user
self.user["lang"] = "en"
self.data["lang"] = "en"
url = reverse("newsletter.existing.token", args=(self.token,))
with patch.multiple("basket", update_user=DEFAULT, subscribe=DEFAULT, unsubscribe=DEFAULT, request=DEFAULT) as basket_patches:
with patch("django.contrib.messages.add_message") as add_msg:
with patch("lib.l10n_utils.render"):
basket_patches["request"].return_value = self.user
rsp = self.client.post(url, self.data)
# Should have given no messages
self.assertEqual(0, add_msg.call_count,
msg=repr(add_msg.call_args_list))
self.assertEqual(0, add_msg.call_count, msg=repr(add_msg.call_args_list))
# Should have called update_user with subscription list
self.assertEqual(1, basket_patches['update_user'].call_count)
kwargs = basket_patches['update_user'].call_args[1]
self.assertEqual(set(kwargs), set(['newsletters', 'lang']))
self.assertEqual(kwargs['lang'], 'en')
self.assertEqual(set(kwargs['newsletters'].split(',')), set(['mozilla-and-you', 'firefox-tips']))
self.assertEqual(1, basket_patches["update_user"].call_count)
kwargs = basket_patches["update_user"].call_args[1]
self.assertEqual(set(kwargs), set(["newsletters", "lang"]))
self.assertEqual(kwargs["lang"], "en")
self.assertEqual(set(kwargs["newsletters"].split(",")), set(["mozilla-and-you", "firefox-tips"]))
# Should not have called unsubscribe
self.assertEqual(0, basket_patches['unsubscribe'].call_count)
self.assertEqual(0, basket_patches["unsubscribe"].call_count)
# Should not have called subscribe
self.assertEqual(0, basket_patches['subscribe'].call_count)
self.assertEqual(0, basket_patches["subscribe"].call_count)
# Should redirect to the 'updated' view
url = reverse('newsletter.updated')
assert rsp['Location'] == url
url = reverse("newsletter.updated")
assert rsp["Location"] == url
@patch('bedrock.newsletter.utils.get_newsletters')
@patch("bedrock.newsletter.utils.get_newsletters")
def test_unsubscribing(self, get_newsletters, mock_basket_request):
get_newsletters.return_value = newsletters
# They unsubscribe from the one newsletter they're subscribed to
self.data['form-0-subscribed_radio'] = u'False'
url = reverse('newsletter.existing.token', args=(self.token,))
with patch.multiple('basket',
update_user=DEFAULT,
subscribe=DEFAULT,
unsubscribe=DEFAULT,
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render'):
basket_patches['request'].return_value = self.user
self.data["form-0-subscribed_radio"] = "False"
url = reverse("newsletter.existing.token", args=(self.token,))
with patch.multiple("basket", update_user=DEFAULT, subscribe=DEFAULT, unsubscribe=DEFAULT, request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render"):
basket_patches["request"].return_value = self.user
rsp = self.client.post(url, self.data)
# Should have called update_user with list of newsletters
self.assertEqual(1, basket_patches['update_user'].call_count)
kwargs = basket_patches['update_user'].call_args[1]
self.assertEqual(
{'newsletters': u'', 'lang': u'pt'},
kwargs
)
self.assertEqual(1, basket_patches["update_user"].call_count)
kwargs = basket_patches["update_user"].call_args[1]
self.assertEqual({"newsletters": "", "lang": "pt"}, kwargs)
# Should not have called subscribe
self.assertEqual(0, basket_patches['subscribe'].call_count)
self.assertEqual(0, basket_patches["subscribe"].call_count)
# Should not have called unsubscribe
self.assertEqual(0, basket_patches['unsubscribe'].call_count)
self.assertEqual(0, basket_patches["unsubscribe"].call_count)
# Should redirect to the 'updated' view
url = reverse('newsletter.updated')
assert rsp['Location'] == url
url = reverse("newsletter.updated")
assert rsp["Location"] == url
@patch('bedrock.newsletter.utils.get_newsletters')
@patch("bedrock.newsletter.utils.get_newsletters")
def test_remove_all(self, get_newsletters, mock_basket_request):
get_newsletters.return_value = newsletters
self.data['remove_all'] = 'on' # any value should do
self.data["remove_all"] = "on" # any value should do
url = reverse('newsletter.existing.token', args=(self.token,))
with patch.multiple('basket',
update_user=DEFAULT,
subscribe=DEFAULT,
unsubscribe=DEFAULT,
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render'):
basket_patches['request'].return_value = self.user
url = reverse("newsletter.existing.token", args=(self.token,))
with patch.multiple("basket", update_user=DEFAULT, subscribe=DEFAULT, unsubscribe=DEFAULT, request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render"):
basket_patches["request"].return_value = self.user
rsp = self.client.post(url, self.data)
# Should not have updated user details at all
self.assertEqual(0, basket_patches['update_user'].call_count)
self.assertEqual(0, basket_patches["update_user"].call_count)
# Should have called unsubscribe
self.assertEqual(1, basket_patches['unsubscribe'].call_count)
self.assertEqual(1, basket_patches["unsubscribe"].call_count)
# and said user opts out
args, kwargs = basket_patches['unsubscribe'].call_args
self.assertEqual((self.token, self.user['email']), args)
self.assertTrue(kwargs['optout'])
args, kwargs = basket_patches["unsubscribe"].call_args
self.assertEqual((self.token, self.user["email"]), args)
self.assertTrue(kwargs["optout"])
# Should redirect to the 'updated' view with unsub=1 and token
url = reverse('newsletter.updated') + "?unsub=1"
url = reverse("newsletter.updated") + "?unsub=1"
url += "&token=%s" % self.token
assert rsp['Location'] == url
assert rsp["Location"] == url
@patch('bedrock.newsletter.utils.get_newsletters')
@patch("bedrock.newsletter.utils.get_newsletters")
def test_change_lang_country(self, get_newsletters, mock_basket_request):
get_newsletters.return_value = newsletters
self.data['lang'] = 'en'
self.data['country'] = 'us'
self.data["lang"] = "en"
self.data["country"] = "us"
with self.activate('en-US'):
url = reverse('newsletter.existing.token', args=(self.token,))
with self.activate("en-US"):
url = reverse("newsletter.existing.token", args=(self.token,))
with patch.multiple('basket',
update_user=DEFAULT,
subscribe=DEFAULT,
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render'):
with patch('django.contrib.messages.add_message') as add_msg:
basket_patches['request'].return_value = self.user
with patch.multiple("basket", update_user=DEFAULT, subscribe=DEFAULT, request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render"):
with patch("django.contrib.messages.add_message") as add_msg:
basket_patches["request"].return_value = self.user
rsp = self.client.post(url, self.data)
# We have an existing user with a change to their email data,
# but none to their subscriptions.
# 'subscribe' should not be called
self.assertEqual(0, basket_patches['subscribe'].call_count)
self.assertEqual(0, basket_patches["subscribe"].call_count)
# update_user should be called once
self.assertEqual(1, basket_patches['update_user'].call_count)
self.assertEqual(1, basket_patches["update_user"].call_count)
# with the new lang and country and the newsletter list
kwargs = basket_patches['update_user'].call_args[1]
self.assertEqual(
{'lang': u'en',
'country': u'us',
'newsletters': u'mozilla-and-you'},
kwargs
)
kwargs = basket_patches["update_user"].call_args[1]
self.assertEqual({"lang": "en", "country": "us", "newsletters": "mozilla-and-you"}, kwargs)
# No messages should be emitted
self.assertEqual(0, add_msg.call_count,
msg=repr(add_msg.call_args_list))
self.assertEqual(0, add_msg.call_count, msg=repr(add_msg.call_args_list))
# Should redirect to the 'updated' view
url = reverse('newsletter.updated')
assert rsp['Location'] == url
url = reverse("newsletter.updated")
assert rsp["Location"] == url
@patch('bedrock.newsletter.utils.get_newsletters')
@patch("bedrock.newsletter.utils.get_newsletters")
def test_newsletter_ordering(self, get_newsletters, mock_basket_request):
# Newsletters are listed in 'order' order, if they have an 'order'
# field
get_newsletters.return_value = newsletters
url = reverse('newsletter.existing.token', args=(self.token,))
self.user['newsletters'] = [u'mozilla-and-you', u'firefox-tips',
u'beta']
with patch.multiple('basket',
update_user=DEFAULT,
subscribe=DEFAULT,
unsubscribe=DEFAULT,
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render') as render:
basket_patches['request'].return_value = self.user
render.return_value = HttpResponse('')
url = reverse("newsletter.existing.token", args=(self.token,))
self.user["newsletters"] = ["mozilla-and-you", "firefox-tips", "beta"]
with patch.multiple("basket", update_user=DEFAULT, subscribe=DEFAULT, unsubscribe=DEFAULT, request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render") as render:
basket_patches["request"].return_value = self.user
render.return_value = HttpResponse("")
self.client.get(url)
request, template_name, context = render.call_args[0]
forms = context['formset'].initial_forms
forms = context["formset"].initial_forms
newsletters_in_order = [form.initial['newsletter'] for form in forms]
self.assertEqual([u'firefox-tips', u'mozilla-and-you'],
newsletters_in_order)
newsletters_in_order = [form.initial["newsletter"] for form in forms]
self.assertEqual(["firefox-tips", "mozilla-and-you"], newsletters_in_order)
@patch('bedrock.newsletter.utils.get_newsletters')
@patch("bedrock.newsletter.utils.get_newsletters")
def test_newsletter_no_order(self, get_newsletters, mock_basket_request):
"""Newsletter views should work if we get no order from basket."""
orderless_newsletters = {}
for key, val in newsletters.items():
nl_copy = val.copy()
del nl_copy['order']
del nl_copy["order"]
orderless_newsletters[key] = nl_copy
get_newsletters.return_value = orderless_newsletters
url = reverse('newsletter.existing.token', args=(self.token,))
self.user['newsletters'] = [u'mozilla-and-you', u'firefox-tips',
u'beta']
with patch.multiple('basket',
update_user=DEFAULT,
subscribe=DEFAULT,
unsubscribe=DEFAULT,
request=DEFAULT) as basket_patches:
with patch('lib.l10n_utils.render') as render:
basket_patches['request'].return_value = self.user
render.return_value = HttpResponse('')
url = reverse("newsletter.existing.token", args=(self.token,))
self.user["newsletters"] = ["mozilla-and-you", "firefox-tips", "beta"]
with patch.multiple("basket", update_user=DEFAULT, subscribe=DEFAULT, unsubscribe=DEFAULT, request=DEFAULT) as basket_patches:
with patch("lib.l10n_utils.render") as render:
basket_patches["request"].return_value = self.user
render.return_value = HttpResponse("")
self.client.get(url)
request, template_name, context = render.call_args[0]
forms = context['formset'].initial_forms
forms = context["formset"].initial_forms
newsletters_in_order = [form.initial['newsletter'] for form in forms]
self.assertEqual([u'mozilla-and-you', u'firefox-tips'],
newsletters_in_order)
newsletters_in_order = [form.initial["newsletter"] for form in forms]
self.assertEqual(["mozilla-and-you", "firefox-tips"], newsletters_in_order)
class TestConfirmView(TestCase):
def setUp(self):
self.token = str(uuid.uuid4())
self.url = reverse('newsletter.confirm', kwargs={'token': self.token})
self.url = reverse("newsletter.confirm", kwargs={"token": self.token})
def test_normal(self):
"""Confirm works with a valid token"""
with patch('basket.confirm') as confirm:
confirm.return_value = {'status': 'ok'}
with patch("basket.confirm") as confirm:
confirm.return_value = {"status": "ok"}
rsp = self.client.get(self.url)
self.assertEqual(302, rsp.status_code)
self.assertTrue(rsp['Location'].endswith("%s?confirm=1" %
reverse('newsletter.existing.token',
kwargs={'token': self.token})))
self.assertTrue(rsp["Location"].endswith("%s?confirm=1" % reverse("newsletter.existing.token", kwargs={"token": self.token})))
def test_normal_with_query_params(self):
"""Confirm works with a valid token"""
with patch('basket.confirm') as confirm:
confirm.return_value = {'status': 'ok'}
rsp = self.client.get(self.url + '?utm_tracking=oh+definitely+yes&utm_source=malibu')
with patch("basket.confirm") as confirm:
confirm.return_value = {"status": "ok"}
rsp = self.client.get(self.url + "?utm_tracking=oh+definitely+yes&utm_source=malibu")
self.assertEqual(302, rsp.status_code)
self.assertTrue(rsp['Location'].endswith("%s?confirm=1&utm_tracking=oh+definitely+yes&"
"utm_source=malibu" %
reverse('newsletter.existing.token',
kwargs={'token': self.token})))
self.assertTrue(
rsp["Location"].endswith(
"%s?confirm=1&utm_tracking=oh+definitely+yes&"
"utm_source=malibu" % reverse("newsletter.existing.token", kwargs={"token": self.token})
)
)
def test_basket_down(self):
"""If basket is down, we report the appropriate error"""
with patch('basket.confirm') as confirm:
with patch("basket.confirm") as confirm:
confirm.side_effect = basket.BasketException()
with patch('lib.l10n_utils.render') as mock_render:
mock_render.return_value = HttpResponse('')
with patch("lib.l10n_utils.render") as mock_render:
mock_render.return_value = HttpResponse("")
rsp = self.client.get(self.url, follow=True)
self.assertEqual(200, rsp.status_code)
confirm.assert_called_with(self.token)
context = mock_render.call_args[0][2]
self.assertFalse(context['success'])
self.assertTrue(context['generic_error'])
self.assertFalse(context['token_error'])
self.assertFalse(context["success"])
self.assertTrue(context["generic_error"])
self.assertFalse(context["token_error"])
def test_bad_token(self):
"""If the token is bad, we report the appropriate error"""
with patch('basket.confirm') as confirm:
confirm.side_effect = basket.BasketException(status_code=403,
code=basket.errors.BASKET_UNKNOWN_TOKEN)
with patch('lib.l10n_utils.render') as mock_render:
mock_render.return_value = HttpResponse('')
with patch("basket.confirm") as confirm:
confirm.side_effect = basket.BasketException(status_code=403, code=basket.errors.BASKET_UNKNOWN_TOKEN)
with patch("lib.l10n_utils.render") as mock_render:
mock_render.return_value = HttpResponse("")
rsp = self.client.get(self.url, follow=True)
self.assertEqual(200, rsp.status_code)
confirm.assert_called_with(self.token)
context = mock_render.call_args[0][2]
self.assertFalse(context['success'])
self.assertFalse(context['generic_error'])
self.assertTrue(context['token_error'])
self.assertFalse(context["success"])
self.assertFalse(context["generic_error"])
self.assertTrue(context["token_error"])
class TestSetCountryView(TestCase):
def setUp(self):
self.token = str(uuid.uuid4())
self.url = reverse('newsletter.country', kwargs={'token': self.token})
self.url = reverse("newsletter.country", kwargs={"token": self.token})
def test_normal_submit(self):
"""Confirm works with a valid token"""
with patch('basket.request') as basket_mock:
basket_mock.return_value = {'status': 'ok'}
rsp = self.client.post(self.url, {'country': 'gb'})
with patch("basket.request") as basket_mock:
basket_mock.return_value = {"status": "ok"}
rsp = self.client.post(self.url, {"country": "gb"})
self.assertEqual(302, rsp.status_code)
basket_mock.assert_called_with('post', 'user-meta', data={'country': 'gb'}, token=self.token)
assert rsp['Location'] == reverse('newsletter.country_success')
basket_mock.assert_called_with("post", "user-meta", data={"country": "gb"}, token=self.token)
assert rsp["Location"] == reverse("newsletter.country_success")
@patch('basket.request')
@patch('bedrock.newsletter.views.messages')
@patch("basket.request")
@patch("bedrock.newsletter.views.messages")
def test_basket_down(self, messages_mock, basket_mock):
"""If basket is down, we report the appropriate error"""
basket_mock.side_effect = basket.BasketException()
rsp = self.client.post(self.url, {'country': 'gb'})
rsp = self.client.post(self.url, {"country": "gb"})
self.assertEqual(200, rsp.status_code)
basket_mock.assert_called_with('post', 'user-meta', data={'country': 'gb'}, token=self.token)
basket_mock.assert_called_with("post", "user-meta", data={"country": "gb"}, token=self.token)
messages_mock.add_message.assert_called_with(ANY, messages_mock.ERROR, ANY)
class TestRecoveryView(TestCase):
def setUp(self):
with self.activate('en-US'):
self.url = reverse('newsletter.recovery')
with self.activate("en-US"):
self.url = reverse("newsletter.recovery")
def test_bad_email(self):
"""Email syntax errors are caught"""
data = {'email': 'not_an_email'}
data = {"email": "not_an_email"}
rsp = self.client.post(self.url, data)
self.assertEqual(200, rsp.status_code)
self.assertIn('email', rsp.context['form'].errors)
self.assertIn("email", rsp.context["form"].errors)
@patch('basket.send_recovery_message', autospec=True)
@patch("basket.send_recovery_message", autospec=True)
def test_unknown_email(self, mock_basket):
"""Unknown email addresses give helpful error message"""
data = {'email': 'unknown@example.com'}
mock_basket.side_effect = basket.BasketException(
status_code=404, code=basket.errors.BASKET_UNKNOWN_EMAIL)
data = {"email": "unknown@example.com"}
mock_basket.side_effect = basket.BasketException(status_code=404, code=basket.errors.BASKET_UNKNOWN_EMAIL)
rsp = self.client.post(self.url, data)
self.assertTrue(mock_basket.called)
self.assertEqual(200, rsp.status_code)
@patch('django.contrib.messages.add_message', autospec=True)
@patch('basket.send_recovery_message', autospec=True)
@patch("django.contrib.messages.add_message", autospec=True)
@patch("basket.send_recovery_message", autospec=True)
def test_good_email(self, mock_basket, add_msg):
"""If basket returns success, don't report errors"""
data = {'email': 'known@example.com'}
mock_basket.return_value = {'status': 'ok'}
data = {"email": "known@example.com"}
mock_basket.return_value = {"status": "ok"}
rsp = self.client.post(self.url, data)
self.assertTrue(mock_basket.called)
# On successful submit, we redirect
self.assertEqual(302, rsp.status_code)
rsp = self.client.get(rsp['Location'])
rsp = self.client.get(rsp["Location"])
self.assertEqual(200, rsp.status_code)
self.assertFalse(rsp.context['form'])
self.assertFalse(rsp.context["form"])
# We also give them a success message
self.assertEqual(1, add_msg.call_count,
msg=repr(add_msg.call_args_list))
self.assertEqual(1, add_msg.call_count, msg=repr(add_msg.call_args_list))
self.assertIn(recovery_text, add_msg.call_args[0])
@ -537,170 +477,158 @@ class TestNewsletterSubscribe(TestCase):
self.rf = RequestFactory()
def ajax_request(self, data, **kwargs):
return self.request(data, HTTP_X_REQUESTED_WITH='XMLHttpRequest', **kwargs)
return self.request(data, HTTP_X_REQUESTED_WITH="XMLHttpRequest", **kwargs)
def request(self, data=None, **kwargs):
if data:
req = self.rf.post('/', data, **kwargs)
req = self.rf.post("/", data, **kwargs)
else:
req = self.rf.get('/', **kwargs)
req = self.rf.get("/", **kwargs)
return newsletter_subscribe(req)
@patch('bedrock.newsletter.views.basket')
@patch("bedrock.newsletter.views.basket")
def test_returns_ajax_errors(self, basket_mock):
"""Incomplete data should return specific errors in JSON"""
data = {
'newsletters': 'flintstones',
'email': 'fred@example.com',
'fmt': 'H',
"newsletters": "flintstones",
"email": "fred@example.com",
"fmt": "H",
}
resp = self.ajax_request(data)
resp_data = json.loads(resp.content)
self.assertFalse(resp_data['success'])
self.assertEqual(len(resp_data['errors']), 1)
self.assertIn('privacy', resp_data['errors'][0])
self.assertFalse(resp_data["success"])
self.assertEqual(len(resp_data["errors"]), 1)
self.assertIn("privacy", resp_data["errors"][0])
self.assertFalse(basket_mock.called)
@patch('bedrock.newsletter.views.basket')
@patch("bedrock.newsletter.views.basket")
def test_returns_sanitized_ajax_errors(self, basket_mock):
"""Error messages should be HTML escaped.
Bug 1116754
"""
data = {
'newsletters': 'flintstones',
'email': 'fred@example.com',
'fmt': 'H',
'privacy': True,
'country': '<svg/onload=alert("NEFARIOUSNESS")>',
"newsletters": "flintstones",
"email": "fred@example.com",
"fmt": "H",
"privacy": True,
"country": '<svg/onload=alert("NEFARIOUSNESS")>',
}
resp = self.ajax_request(data)
resp_data = json.loads(resp.content)
self.assertFalse(resp_data['success'])
self.assertEqual(len(resp_data['errors']), 1)
self.assertNotIn(data['country'], resp_data['errors'][0])
self.assertIn('NEFARIOUSNESS', resp_data['errors'][0])
self.assertIn('&lt;svg', resp_data['errors'][0])
self.assertFalse(resp_data["success"])
self.assertEqual(len(resp_data["errors"]), 1)
self.assertNotIn(data["country"], resp_data["errors"][0])
self.assertIn("NEFARIOUSNESS", resp_data["errors"][0])
self.assertIn("&lt;svg", resp_data["errors"][0])
self.assertFalse(basket_mock.called)
@patch('bedrock.newsletter.views.basket')
@patch("bedrock.newsletter.views.basket")
def test_no_source_url_use_referrer(self, basket_mock):
"""Should set source_url to referrer if not sent"""
data = {
'newsletters': 'flintstones',
'email': 'fred@example.com',
'fmt': 'H',
'privacy': True,
"newsletters": "flintstones",
"email": "fred@example.com",
"fmt": "H",
"privacy": True,
}
source_url = 'https://example.com/bambam'
source_url = "https://example.com/bambam"
resp = self.ajax_request(data, HTTP_REFERER=source_url)
resp_data = json.loads(resp.content)
self.assertDictEqual(resp_data, {'success': True})
basket_mock.subscribe.assert_called_with('fred@example.com', 'flintstones',
format='H', source_url=source_url)
self.assertDictEqual(resp_data, {"success": True})
basket_mock.subscribe.assert_called_with("fred@example.com", "flintstones", format="H", source_url=source_url)
@patch('bedrock.newsletter.views.basket')
@patch("bedrock.newsletter.views.basket")
def test_use_source_url_with_referer(self, basket_mock):
"""Should use source_url even if there's a good referrer"""
source_url = 'https://example.com/bambam'
data = {
'newsletters': 'flintstones',
'email': 'fred@example.com',
'fmt': 'H',
'privacy': True,
'source_url': source_url
}
resp = self.ajax_request(data, HTTP_REFERER=source_url + '_WILMA')
source_url = "https://example.com/bambam"
data = {"newsletters": "flintstones", "email": "fred@example.com", "fmt": "H", "privacy": True, "source_url": source_url}
resp = self.ajax_request(data, HTTP_REFERER=source_url + "_WILMA")
resp_data = json.loads(resp.content)
self.assertDictEqual(resp_data, {'success': True})
basket_mock.subscribe.assert_called_with('fred@example.com', 'flintstones',
format='H', source_url=source_url)
self.assertDictEqual(resp_data, {"success": True})
basket_mock.subscribe.assert_called_with("fred@example.com", "flintstones", format="H", source_url=source_url)
@patch('bedrock.newsletter.views.basket')
@patch("bedrock.newsletter.views.basket")
def test_returns_ajax_success(self, basket_mock):
"""Good post should return success JSON"""
data = {
'newsletters': 'flintstones',
'email': 'fred@example.com',
'fmt': 'H',
'privacy': True,
"newsletters": "flintstones",
"email": "fred@example.com",
"fmt": "H",
"privacy": True,
}
resp = self.ajax_request(data)
resp_data = json.loads(resp.content)
self.assertDictEqual(resp_data, {'success': True})
basket_mock.subscribe.assert_called_with('fred@example.com', 'flintstones',
format='H')
self.assertDictEqual(resp_data, {"success": True})
basket_mock.subscribe.assert_called_with("fred@example.com", "flintstones", format="H")
@patch.object(basket, 'subscribe')
@patch.object(basket, "subscribe")
def test_returns_ajax_invalid_email(self, subscribe_mock):
"""Invalid email AJAX post should return proper error."""
subscribe_mock.side_effect = basket.BasketException(
code=basket.errors.BASKET_INVALID_EMAIL)
subscribe_mock.side_effect = basket.BasketException(code=basket.errors.BASKET_INVALID_EMAIL)
data = {
'newsletters': 'flintstones',
'email': 'fred@example.com',
'fmt': 'H',
'privacy': True,
"newsletters": "flintstones",
"email": "fred@example.com",
"fmt": "H",
"privacy": True,
}
resp = self.ajax_request(data)
resp_data = json.loads(resp.content)
self.assertFalse(resp_data['success'])
self.assertEqual(resp_data['errors'][0], str(invalid_email_address))
self.assertFalse(resp_data["success"])
self.assertEqual(resp_data["errors"][0], str(invalid_email_address))
@patch.object(basket, 'subscribe')
@patch.object(basket, "subscribe")
def test_returns_ajax_basket_error(self, subscribe_mock):
"""Basket error AJAX post should return proper error."""
subscribe_mock.side_effect = basket.BasketException(
code=basket.errors.BASKET_NETWORK_FAILURE)
subscribe_mock.side_effect = basket.BasketException(code=basket.errors.BASKET_NETWORK_FAILURE)
data = {
'newsletters': 'flintstones',
'email': 'fred@example.com',
'fmt': 'H',
'privacy': True,
"newsletters": "flintstones",
"email": "fred@example.com",
"fmt": "H",
"privacy": True,
}
resp = self.ajax_request(data)
resp_data = json.loads(resp.content)
self.assertFalse(resp_data['success'])
self.assertEqual(resp_data['errors'][0], str(general_error))
self.assertFalse(resp_data["success"])
self.assertEqual(resp_data["errors"][0], str(general_error))
def test_shows_normal_form(self):
"""A normal GET should show the form."""
resp = self.request()
doc = pq(resp.content)
self.assertTrue(doc('#newsletter-form'))
self.assertTrue(doc("#newsletter-form"))
self.assertTrue(doc('input[value="mozilla-foundation"]'))
@patch('bedrock.newsletter.views.basket')
@patch("bedrock.newsletter.views.basket")
def test_returns_success(self, basket_mock):
"""Good non-ajax post should return thank-you page."""
data = {
'newsletters': 'flintstones',
'email': 'fred@example.com',
'fmt': 'H',
'privacy': True,
"newsletters": "flintstones",
"email": "fred@example.com",
"fmt": "H",
"privacy": True,
}
resp = self.request(data)
doc = pq(resp.content)
self.assertFalse(doc('#newsletter-submit'))
self.assertFalse(doc("#newsletter-submit"))
self.assertFalse(doc('input[value="mozilla-and-you"]'))
self.assertTrue(doc('#newsletter-thanks'))
basket_mock.subscribe.assert_called_with('fred@example.com', 'flintstones',
format='H')
self.assertTrue(doc("#newsletter-thanks"))
basket_mock.subscribe.assert_called_with("fred@example.com", "flintstones", format="H")
@patch('bedrock.newsletter.views.basket')
@patch("bedrock.newsletter.views.basket")
def test_returns_failure(self, basket_mock):
"""Bad non-ajax post should return form with errors."""
data = {
'newsletters': 'flintstones',
'email': 'fred@example.com',
'fmt': 'H',
"newsletters": "flintstones",
"email": "fred@example.com",
"fmt": "H",
}
resp = self.request(data)
doc = pq(resp.content)
self.assertTrue(doc('#newsletter-form'))
self.assertTrue(doc("#newsletter-form"))
self.assertFalse(doc('input[value="mozilla-and-you"]'))
self.assertTrue(doc('input[value="flintstones"]'))
self.assertFalse(doc('#email-form'))
self.assertIn('privacy', doc('#newsletter-errors .mzp-u-list-styled li').eq(0).text())
self.assertFalse(doc("#email-form"))
self.assertIn("privacy", doc("#newsletter-errors .mzp-u-list-styled li").eq(0).text())
self.assertFalse(basket_mock.subscribe.called)

Просмотреть файл

@ -8,53 +8,31 @@ from bedrock.mozorg.util import page
# A UUID looks like: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
# Here's a regex to match a UUID:
uuid_regex = r'[0-Fa-f]{8}-[0-Fa-f]{4}-[0-Fa-f]{4}-[0-Fa-f]{4}-[0-Fa-f]{12}'
uuid_regex = r"[0-Fa-f]{8}-[0-Fa-f]{4}-[0-Fa-f]{4}-[0-Fa-f]{4}-[0-Fa-f]{12}"
urlpatterns = (
# view.existing allows a user who has a link including their token to
# subscribe, unsubscribe, change their preferences. Each newsletter
# includes that link for them.
url('^newsletter/existing/(?P<token>[^/]*)/?$',
views.existing,
name='newsletter.existing.token'),
url("^newsletter/existing/(?P<token>[^/]*)/?$", views.existing, name="newsletter.existing.token"),
# After submitting on the `existing` page, users end up on the
# `updated` page. There are optional query params; see the view.
url('^newsletter/updated/$',
views.updated,
name='newsletter.updated'),
url("^newsletter/updated/$", views.updated, name="newsletter.updated"),
# Confirm subscriptions
url('^newsletter/confirm/(?P<token>%s)/$' % uuid_regex,
views.confirm,
name='newsletter.confirm'),
url("^newsletter/confirm/(?P<token>%s)/$" % uuid_regex, views.confirm, name="newsletter.confirm"),
# Update country
url('^newsletter/country/(?P<token>%s)/$' % uuid_regex,
views.set_country,
name='newsletter.country'),
url("^newsletter/country/(?P<token>%s)/$" % uuid_regex, views.set_country, name="newsletter.country"),
# Request recovery message with link to manage subscriptions
url('^newsletter/recovery/$',
views.recovery,
name='newsletter.recovery'),
url("^newsletter/recovery/$", views.recovery, name="newsletter.recovery"),
# Receives POSTs from all subscribe forms
url('^newsletter/$',
views.newsletter_subscribe,
name='newsletter.subscribe'),
url("^newsletter/$", views.newsletter_subscribe, name="newsletter.subscribe"),
# Welcome program out-out confirmation page (bug 1442129)
url('^newsletter/opt-out-confirmation/$',
views.recovery,
name='newsletter.opt-out-confirmation'),
url("^newsletter/opt-out-confirmation/$", views.recovery, name="newsletter.opt-out-confirmation"),
# Branded signup pages for individual newsletters
page('newsletter/mozilla', 'newsletter/mozilla.html', ftl_files=['mozorg/newsletters']),
page('newsletter/firefox', 'newsletter/firefox.html', ftl_files=['mozorg/newsletters']),
page('newsletter/developer', 'newsletter/developer.html', ftl_files=['mozorg/newsletters']),
page('newsletter/country/success', 'newsletter/country_success.html', ftl_files=['mozorg/newsletters']),
page('newsletter/fxa-error', 'newsletter/fxa-error.html', ftl_files=['mozorg/newsletters']),
page("newsletter/mozilla", "newsletter/mozilla.html", ftl_files=["mozorg/newsletters"]),
page("newsletter/firefox", "newsletter/firefox.html", ftl_files=["mozorg/newsletters"]),
page("newsletter/developer", "newsletter/developer.html", ftl_files=["mozorg/newsletters"]),
page("newsletter/country/success", "newsletter/country_success.html", ftl_files=["mozorg/newsletters"]),
page("newsletter/fxa-error", "newsletter/fxa-error.html", ftl_files=["mozorg/newsletters"]),
)

Просмотреть файл

@ -22,12 +22,12 @@ def get_languages_for_newsletters(newsletters=None):
newsletters = list(all_newsletters.values())
else:
if isinstance(newsletters, str):
newsletters = [nl.strip() for nl in newsletters.split(',')]
newsletters = [nl.strip() for nl in newsletters.split(",")]
newsletters = [all_newsletters.get(nl, {}) for nl in newsletters]
langs = set()
for newsletter in newsletters:
langs.update(newsletter.get('languages', []))
langs.update(newsletter.get("languages", []))
return langs
@ -38,7 +38,7 @@ def custom_unsub_reason(token, reason):
This is calling a basket API that's custom to Mozilla, that's
why there's not a helper in the basket-client package."""
data = {
'token': token,
'reason': reason,
"token": token,
"reason": reason,
}
return basket.request('post', 'custom_unsub_reason', data=data)
return basket.request("post", "custom_unsub_reason", data=data)

Просмотреть файл

@ -25,154 +25,163 @@ from lib.l10n_utils.fluent import ftl, ftl_lazy
from bedrock.base import waffle
from bedrock.base.urlresolvers import reverse
# Cannot use short "from . import utils" because we need to mock
# utils.get_newsletters in our tests
from bedrock.base.geo import get_country_from_request
from bedrock.newsletter import utils
from .forms import (CountrySelectForm, EmailForm, ManageSubscriptionsForm,
NewsletterFooterForm, NewsletterForm)
from .forms import CountrySelectForm, EmailForm, ManageSubscriptionsForm, NewsletterFooterForm, NewsletterForm
log = commonware.log.getLogger('b.newsletter')
log = commonware.log.getLogger("b.newsletter")
FTL_FILES = ['mozorg/newsletters']
FTL_FILES = ["mozorg/newsletters"]
general_error = ftl_lazy('newsletters-we-are-sorry-but-there', ftl_files=FTL_FILES)
thank_you = ftl_lazy('newsletters-your-email-preferences',
fallback='newsletters-thanks-for-updating-your',
ftl_files=FTL_FILES)
bad_token = ftl_lazy('newsletters-the-supplied-link-has-expired-long', ftl_files=FTL_FILES)
recovery_text = ftl_lazy('newsletters-success-an-email-has-been-sent', ftl_files=FTL_FILES)
invalid_email_address = ftl_lazy('newsletters-this-is-not-a-valid-email', ftl_files=FTL_FILES)
general_error = ftl_lazy("newsletters-we-are-sorry-but-there", ftl_files=FTL_FILES)
thank_you = ftl_lazy("newsletters-your-email-preferences", fallback="newsletters-thanks-for-updating-your", ftl_files=FTL_FILES)
bad_token = ftl_lazy("newsletters-the-supplied-link-has-expired-long", ftl_files=FTL_FILES)
recovery_text = ftl_lazy("newsletters-success-an-email-has-been-sent", ftl_files=FTL_FILES)
invalid_email_address = ftl_lazy("newsletters-this-is-not-a-valid-email", ftl_files=FTL_FILES)
NEWSLETTER_STRINGS = {
u'about-mozilla': {
'description': ftl_lazy('newsletters-join-mozillians-all-around', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-mozilla-community', ftl_files=FTL_FILES)},
u'about-standards': {
'title': ftl_lazy('newsletters-about-standards', ftl_files=FTL_FILES)},
u'addon-dev': {
'title': ftl_lazy('newsletters-addon-development', ftl_files=FTL_FILES)},
u'affiliates': {
'description': ftl_lazy('newsletters-a-monthly-newsletter-affiliates', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-affiliates', ftl_files=FTL_FILES)},
u'ambassadors': {
'description': ftl_lazy('newsletters-a-monthly-newsletter-ambassadors', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-student-ambassadors', ftl_files=FTL_FILES)},
u'app-dev': {
'description': ftl_lazy('newsletters-a-developers-guide', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-developer-newsletter', ftl_files=FTL_FILES)},
u'aurora': {
'description': ftl_lazy('newsletters-aurora', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-aurora', ftl_files=FTL_FILES)},
u'beta': {
'description': ftl_lazy('newsletters-read-about-the-latest-features', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-beta-news', ftl_files=FTL_FILES)},
u'download-firefox-android': {
'title': ftl_lazy('newsletters-download-firefox-for-android', ftl_files=FTL_FILES)},
u'download-firefox-androidsn': {
'title': ftl_lazy('newsletters-get-firefox-for-android', ftl_files=FTL_FILES)},
u'download-firefox-androidsnus': {
'title': ftl_lazy('newsletters-get-firefox-for-android', ftl_files=FTL_FILES)},
u'download-firefox-ios': {
'title': ftl_lazy('newsletters-download-firefox-for-ios', ftl_files=FTL_FILES)},
u'download-firefox-mobile': {
'title': ftl_lazy('newsletters-download-firefox-for-mobile', ftl_files=FTL_FILES)},
u'drumbeat': {
'title': ftl_lazy('newsletters-drumbeat-newsgroup', ftl_files=FTL_FILES)},
u'firefox-accounts-journey': {
'description': ftl_lazy('newsletters-get-the-most-firefox-account', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-accounts-tips', ftl_files=FTL_FILES)},
u'firefox-desktop': {
'description': ftl_lazy('newsletters-dont-miss-the-latest', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-for-desktop', ftl_files=FTL_FILES)},
u'firefox-flicks': {
'description': ftl_lazy('newsletters-periodic-email-updates', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-flicks', ftl_files=FTL_FILES)},
u'firefox-ios': {
'description': ftl_lazy('newsletters-be-the-first-to-know', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-ios', ftl_files=FTL_FILES)},
u'firefox-os': {
'description': ftl_lazy('newsletters-dont-miss-important-news', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-os-smartphone-owner', ftl_files=FTL_FILES)},
u'firefox-os-news': {
'description': ftl_lazy('newsletters-a-monthly-newsletter-and-special', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-os-and-you', ftl_files=FTL_FILES)},
u'firefox-tips': {
'description': ftl_lazy('newsletters-get-a-weekly-tip', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-weekly-tips', ftl_files=FTL_FILES)},
u'get-android-embed': {
'title': ftl_lazy('newsletters-get-firefox-for-android', ftl_files=FTL_FILES)},
u'get-android-notembed': {
'title': ftl_lazy('newsletters-get-firefox-for-android', ftl_files=FTL_FILES)},
u'get-involved': {
'title': ftl_lazy('newsletters-get-involved', ftl_files=FTL_FILES)},
u'internet-health-report': {
'title': ftl_lazy('newsletters-insights',
fallback='newsletters-internet-health-report',
ftl_files=FTL_FILES),
'description': ftl_lazy('newsletters-mozilla-published-articles-and-deep',
fallback='newsletters-keep-up-with-our-annual',
ftl_files=FTL_FILES)},
u'join-mozilla': {
'title': ftl_lazy('newsletters-join-mozilla', ftl_files=FTL_FILES)},
u'knowledge-is-power': {
'description': ftl_lazy('newsletters-get-all-the-knowledge', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-knowledge-is-power', ftl_files=FTL_FILES)},
u'labs': {
'title': ftl_lazy('newsletters-about-labs', ftl_files=FTL_FILES)},
u'maker-party': {
'description': ftl_lazy('newsletters-mozillas-largest-celebration', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-maker-party', ftl_files=FTL_FILES)},
u'marketplace': {
'description': ftl_lazy('newsletters-discover-the-latest', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-os', ftl_files=FTL_FILES)},
u'marketplace-android': {
'title': ftl_lazy('newsletters-android', ftl_files=FTL_FILES)},
u'marketplace-desktop': {
'title': ftl_lazy('newsletters-desktop', ftl_files=FTL_FILES)},
u'mobile': {
'description': ftl_lazy('newsletters-keep-up-with-releases', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-for-android', ftl_files=FTL_FILES)},
u'mozilla-and-you': {
'description': ftl_lazy('newsletters-get-how-tos', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-news', ftl_files=FTL_FILES)},
u'mozilla-festival': {
'description': ftl_lazy('newsletters-special-announcements-about-mozilla-v2', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-mozilla-festival', ftl_files=FTL_FILES)},
u'mozilla-foundation': {
'description': ftl_lazy('newsletters-regular-updates-to-keep-v2', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-mozilla-news', ftl_files=FTL_FILES)},
u'mozilla-general': {
'description': ftl_lazy('newsletters-special-accouncements-and-messages', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-mozilla', ftl_files=FTL_FILES)},
u'mozilla-learning-network': {
'description': ftl_lazy('newsletters-updates-from-our-global', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-mozilla-learning-network', ftl_files=FTL_FILES)},
u'mozilla-phone': {
'description': ftl_lazy('newsletters-email-updates-from-vouched', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-mozillians', ftl_files=FTL_FILES)},
u'mozilla-technology': {
'description': ftl_lazy('newsletters-were-building-the-technology', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-mozilla-labs', ftl_files=FTL_FILES)},
u'os': {
'description': ftl_lazy('newsletters-firefox-os-news', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-firefox-os', ftl_files=FTL_FILES)},
u'shape-web': {
'description': ftl_lazy('newsletters-news-and-information', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-shapre-of-the-web', ftl_files=FTL_FILES)},
u'student-reps': {
'description': ftl_lazy('newsletters-former-university-program', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-student-reps', ftl_files=FTL_FILES)},
u'take-action-for-the-internet': {
'description': ftl_lazy('newsletters-add-your-voice', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-take-action', ftl_files=FTL_FILES)},
u'test-pilot': {
'description': ftl_lazy('newsletters-help-us-make-a-better', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-new-product-testing', ftl_files=FTL_FILES)},
u'webmaker': {
'description': ftl_lazy('newsletters-special-announcements-helping-you', ftl_files=FTL_FILES),
'title': ftl_lazy('newsletters-webmaker', ftl_files=FTL_FILES)},
"about-mozilla": {
"description": ftl_lazy("newsletters-join-mozillians-all-around", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-mozilla-community", ftl_files=FTL_FILES),
},
"about-standards": {"title": ftl_lazy("newsletters-about-standards", ftl_files=FTL_FILES)},
"addon-dev": {"title": ftl_lazy("newsletters-addon-development", ftl_files=FTL_FILES)},
"affiliates": {
"description": ftl_lazy("newsletters-a-monthly-newsletter-affiliates", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-affiliates", ftl_files=FTL_FILES),
},
"ambassadors": {
"description": ftl_lazy("newsletters-a-monthly-newsletter-ambassadors", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-student-ambassadors", ftl_files=FTL_FILES),
},
"app-dev": {
"description": ftl_lazy("newsletters-a-developers-guide", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-developer-newsletter", ftl_files=FTL_FILES),
},
"aurora": {"description": ftl_lazy("newsletters-aurora", ftl_files=FTL_FILES), "title": ftl_lazy("newsletters-aurora", ftl_files=FTL_FILES)},
"beta": {
"description": ftl_lazy("newsletters-read-about-the-latest-features", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-beta-news", ftl_files=FTL_FILES),
},
"download-firefox-android": {"title": ftl_lazy("newsletters-download-firefox-for-android", ftl_files=FTL_FILES)},
"download-firefox-androidsn": {"title": ftl_lazy("newsletters-get-firefox-for-android", ftl_files=FTL_FILES)},
"download-firefox-androidsnus": {"title": ftl_lazy("newsletters-get-firefox-for-android", ftl_files=FTL_FILES)},
"download-firefox-ios": {"title": ftl_lazy("newsletters-download-firefox-for-ios", ftl_files=FTL_FILES)},
"download-firefox-mobile": {"title": ftl_lazy("newsletters-download-firefox-for-mobile", ftl_files=FTL_FILES)},
"drumbeat": {"title": ftl_lazy("newsletters-drumbeat-newsgroup", ftl_files=FTL_FILES)},
"firefox-accounts-journey": {
"description": ftl_lazy("newsletters-get-the-most-firefox-account", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-accounts-tips", ftl_files=FTL_FILES),
},
"firefox-desktop": {
"description": ftl_lazy("newsletters-dont-miss-the-latest", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-for-desktop", ftl_files=FTL_FILES),
},
"firefox-flicks": {
"description": ftl_lazy("newsletters-periodic-email-updates", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-flicks", ftl_files=FTL_FILES),
},
"firefox-ios": {
"description": ftl_lazy("newsletters-be-the-first-to-know", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-ios", ftl_files=FTL_FILES),
},
"firefox-os": {
"description": ftl_lazy("newsletters-dont-miss-important-news", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-os-smartphone-owner", ftl_files=FTL_FILES),
},
"firefox-os-news": {
"description": ftl_lazy("newsletters-a-monthly-newsletter-and-special", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-os-and-you", ftl_files=FTL_FILES),
},
"firefox-tips": {
"description": ftl_lazy("newsletters-get-a-weekly-tip", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-weekly-tips", ftl_files=FTL_FILES),
},
"get-android-embed": {"title": ftl_lazy("newsletters-get-firefox-for-android", ftl_files=FTL_FILES)},
"get-android-notembed": {"title": ftl_lazy("newsletters-get-firefox-for-android", ftl_files=FTL_FILES)},
"get-involved": {"title": ftl_lazy("newsletters-get-involved", ftl_files=FTL_FILES)},
"internet-health-report": {
"title": ftl_lazy("newsletters-insights", fallback="newsletters-internet-health-report", ftl_files=FTL_FILES),
"description": ftl_lazy(
"newsletters-mozilla-published-articles-and-deep", fallback="newsletters-keep-up-with-our-annual", ftl_files=FTL_FILES
),
},
"join-mozilla": {"title": ftl_lazy("newsletters-join-mozilla", ftl_files=FTL_FILES)},
"knowledge-is-power": {
"description": ftl_lazy("newsletters-get-all-the-knowledge", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-knowledge-is-power", ftl_files=FTL_FILES),
},
"labs": {"title": ftl_lazy("newsletters-about-labs", ftl_files=FTL_FILES)},
"maker-party": {
"description": ftl_lazy("newsletters-mozillas-largest-celebration", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-maker-party", ftl_files=FTL_FILES),
},
"marketplace": {
"description": ftl_lazy("newsletters-discover-the-latest", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-os", ftl_files=FTL_FILES),
},
"marketplace-android": {"title": ftl_lazy("newsletters-android", ftl_files=FTL_FILES)},
"marketplace-desktop": {"title": ftl_lazy("newsletters-desktop", ftl_files=FTL_FILES)},
"mobile": {
"description": ftl_lazy("newsletters-keep-up-with-releases", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-for-android", ftl_files=FTL_FILES),
},
"mozilla-and-you": {
"description": ftl_lazy("newsletters-get-how-tos", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-news", ftl_files=FTL_FILES),
},
"mozilla-festival": {
"description": ftl_lazy("newsletters-special-announcements-about-mozilla-v2", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-mozilla-festival", ftl_files=FTL_FILES),
},
"mozilla-foundation": {
"description": ftl_lazy("newsletters-regular-updates-to-keep-v2", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-mozilla-news", ftl_files=FTL_FILES),
},
"mozilla-general": {
"description": ftl_lazy("newsletters-special-accouncements-and-messages", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-mozilla", ftl_files=FTL_FILES),
},
"mozilla-learning-network": {
"description": ftl_lazy("newsletters-updates-from-our-global", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-mozilla-learning-network", ftl_files=FTL_FILES),
},
"mozilla-phone": {
"description": ftl_lazy("newsletters-email-updates-from-vouched", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-mozillians", ftl_files=FTL_FILES),
},
"mozilla-technology": {
"description": ftl_lazy("newsletters-were-building-the-technology", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-mozilla-labs", ftl_files=FTL_FILES),
},
"os": {
"description": ftl_lazy("newsletters-firefox-os-news", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-firefox-os", ftl_files=FTL_FILES),
},
"shape-web": {
"description": ftl_lazy("newsletters-news-and-information", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-shapre-of-the-web", ftl_files=FTL_FILES),
},
"student-reps": {
"description": ftl_lazy("newsletters-former-university-program", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-student-reps", ftl_files=FTL_FILES),
},
"take-action-for-the-internet": {
"description": ftl_lazy("newsletters-add-your-voice", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-take-action", ftl_files=FTL_FILES),
},
"test-pilot": {
"description": ftl_lazy("newsletters-help-us-make-a-better", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-new-product-testing", ftl_files=FTL_FILES),
},
"webmaker": {
"description": ftl_lazy("newsletters-special-announcements-helping-you", ftl_files=FTL_FILES),
"title": ftl_lazy("newsletters-webmaker", ftl_files=FTL_FILES),
},
}
@ -181,8 +190,7 @@ UNSUB_REASONS_SUBMITTED = 2
# A UUID looks like: f81d4fae-7dec-11d0-a765-00a0c91e6bf6
# Here's a regex to match a UUID:
UUID_REGEX = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
re.IGNORECASE)
UUID_REGEX = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE)
def set_country(request, token):
@ -190,21 +198,19 @@ def set_country(request, token):
initial = {}
countrycode = get_country_from_request(request)
if countrycode:
initial['country'] = countrycode.lower()
initial["country"] = countrycode.lower()
form = CountrySelectForm('en-US', data=request.POST or None, initial=initial)
form = CountrySelectForm("en-US", data=request.POST or None, initial=initial)
if form.is_valid():
try:
basket.request('post', 'user-meta', data=form.cleaned_data, token=token)
basket.request("post", "user-meta", data=form.cleaned_data, token=token)
except basket.BasketException:
log.exception("Error updating user's country in basket")
messages.add_message(
request, messages.ERROR, general_error
)
messages.add_message(request, messages.ERROR, general_error)
else:
return redirect(reverse('newsletter.country_success'))
return redirect(reverse("newsletter.country_success"))
return l10n_utils.render(request, 'newsletter/country.html', {'form': form})
return l10n_utils.render(request, "newsletter/country.html", {"form": form})
@never_cache
@ -226,7 +232,7 @@ def confirm(request, token):
# Any other exception
generic_error = True
else:
if result['status'] == 'ok':
if result["status"] == "ok":
success = True
else:
# Shouldn't happen (errors should raise exception),
@ -236,21 +242,15 @@ def confirm(request, token):
# Assume rate limit error means user already confirmed and clicked confirm
# link twice in quick succession
if success or rate_limit_error:
qparams = ['confirm=1']
qs = request.META.get('QUERY_STRING', '')
qparams = ["confirm=1"]
qs = request.META.get("QUERY_STRING", "")
if qs:
qparams.append(qs)
return HttpResponseRedirect("%s?%s" % (reverse('newsletter.existing.token',
kwargs={'token': token}),
'&'.join(qparams)))
return HttpResponseRedirect("%s?%s" % (reverse("newsletter.existing.token", kwargs={"token": token}), "&".join(qparams)))
else:
return l10n_utils.render(
request,
'newsletter/confirm.html',
{'success': success,
'generic_error': generic_error,
'token_error': token_error},
ftl_files=FTL_FILES)
request, "newsletter/confirm.html", {"success": success, "generic_error": generic_error, "token_error": token_error}, ftl_files=FTL_FILES
)
@never_cache
@ -270,16 +270,16 @@ def existing(request, token=None):
locale = l10n_utils.get_locale(request)
if not token:
return redirect(reverse('newsletter.recovery'))
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'))
return redirect(reverse("newsletter.recovery"))
if waffle.switch('newsletter-maintenance-mode'):
return l10n_utils.render(request, 'newsletter/existing.html', ftl_files=FTL_FILES)
if waffle.switch("newsletter-maintenance-mode"):
return l10n_utils.render(request, "newsletter/existing.html", ftl_files=FTL_FILES)
unsub_parm = None
@ -294,19 +294,19 @@ def existing(request, token=None):
# u'email': u'user@example.com'
# }
has_fxa = 'fxa' in request.GET
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)
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', ftl_files=FTL_FILES)
return l10n_utils.render(request, "newsletter/existing.html", ftl_files=FTL_FILES)
except basket.BasketException as e:
log.exception("FAILED to get user from token (%s)", e.desc)
@ -314,10 +314,10 @@ def existing(request, token=None):
# Bad or no token
messages.add_message(request, messages.ERROR, bad_token)
# Redirect to the recovery page
return redirect(reverse('newsletter.recovery'))
return redirect(reverse("newsletter.recovery"))
# if `has_fxa` not returned from basket, set it from the URL
user.setdefault('has_fxa', has_fxa)
user.setdefault("has_fxa", has_fxa)
# Get the newsletter data - it's a dictionary of dictionaries
newsletter_data = utils.get_newsletters()
@ -327,54 +327,55 @@ def existing(request, token=None):
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):
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']
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'):
if newsletter == "firefox-accounts-journey" and locale.startswith("en"):
# alternate english title
title = u'Firefox Account Tips'
title = "Firefox Account Tips"
else:
title = nstrings['title']
description = nstrings.get('description', u'')
title = nstrings["title"]
description = nstrings.get("description", "")
else:
title = data['title']
description = data['description']
title = data["title"]
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),
"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']
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'
keyfield = "order" if "order" in initial[0] else "title"
initial.sort(key=itemgetter(keyfield))
NewsletterFormSet = formset_factory(NewsletterForm, extra=0,
max_num=len(initial))
NewsletterFormSet = formset_factory(NewsletterForm, extra=0, max_num=len(initial))
if request.method == 'POST':
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']
remove_all = form.is_valid() and form.cleaned_data["remove_all"]
formset_is_valid = False
@ -392,14 +393,16 @@ def existing(request, token=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
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)
form = ManageSubscriptionsForm(locale, data=request.POST, initial=user, **form_kwargs)
if formset_is_valid and form.is_valid():
@ -410,56 +413,45 @@ def existing(request, token=None):
# 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']:
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)
kwargs["newsletters"] = ",".join(newsletters)
if kwargs:
# always send lang so basket doesn't try to guess
kwargs['lang'] = data['lang']
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',
ftl_files=FTL_FILES)
messages.add_message(request, messages.ERROR, general_error)
return l10n_utils.render(request, "newsletter/existing.html", ftl_files=FTL_FILES)
# If they chose to remove all, tell basket that they've opted out
if remove_all:
try:
basket.unsubscribe(token, user['email'], optout=True)
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',
ftl_files=FTL_FILES)
messages.add_message(request, messages.ERROR, general_error)
return l10n_utils.render(request, "newsletter/existing.html", ftl_files=FTL_FILES)
# We need to pass their token to the next view
url = reverse('newsletter.updated') \
+ "?unsub=%s&token=%s" % (UNSUB_UNSUBSCRIBED_ALL, token)
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')
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
)
form = ManageSubscriptionsForm(locale, initial=user)
formset = NewsletterFormSet(initial=initial)
# For the template, we want a dictionary whose keys are language codes
@ -467,35 +459,32 @@ def existing(request, token=None):
# that language code.
newsletter_languages = defaultdict(list)
for newsletter, data in newsletter_data.items():
for lang in data['languages']:
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']))
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'],
"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,
ftl_files=FTL_FILES)
return l10n_utils.render(request, "newsletter/existing.html", context, ftl_files=FTL_FILES)
# Possible reasons for unsubscribing
REASONS = [
ftl_lazy('newsletters-you-send-too-many-emails', ftl_files=FTL_FILES),
ftl_lazy('newsletters-your-content-wasnt-relevant', ftl_files=FTL_FILES),
ftl_lazy('newsletters-your-email-design', ftl_files=FTL_FILES),
ftl_lazy('newsletters-i-didnt-sign-up', ftl_files=FTL_FILES),
ftl_lazy('newsletters-im-keeping-in-touch-v2', ftl_files=FTL_FILES),
ftl_lazy("newsletters-you-send-too-many-emails", ftl_files=FTL_FILES),
ftl_lazy("newsletters-your-content-wasnt-relevant", ftl_files=FTL_FILES),
ftl_lazy("newsletters-your-email-design", ftl_files=FTL_FILES),
ftl_lazy("newsletters-i-didnt-sign-up", ftl_files=FTL_FILES),
ftl_lazy("newsletters-im-keeping-in-touch-v2", ftl_files=FTL_FILES),
]
@ -515,7 +504,7 @@ def updated(request):
all.
"""
unsub = _post_or_get(request, 'unsub', '0')
unsub = _post_or_get(request, "unsub", "0")
try:
unsub = int(unsub)
except ValueError:
@ -527,7 +516,7 @@ def updated(request):
reasons_submitted = unsub == UNSUB_REASONS_SUBMITTED
# Token might also have been passed (on remove_all only)
token = _post_or_get(request, 'token', None)
token = _post_or_get(request, "token", None)
# token must be a UUID
if token is not None and not UUID_REGEX.match(token):
token = None
@ -536,7 +525,7 @@ def updated(request):
if not unsub:
messages.add_message(request, messages.INFO, thank_you)
if request.method == 'POST' and reasons_submitted and token:
if request.method == "POST" and reasons_submitted and token:
# Tell basket about their reasons
reasons = []
@ -544,25 +533,22 @@ def updated(request):
# paste together the English versions of the reasons they submitted,
# so we can read them. (Well, except for the free-form reason.)
for i, reason in enumerate(REASONS):
if _post_or_get(request, 'reason%d' % i):
if _post_or_get(request, "reason%d" % i):
reasons.append(str(reason))
if _post_or_get(request, 'reason-text-p'):
reasons.append(_post_or_get(request, 'reason-text', ''))
if _post_or_get(request, "reason-text-p"):
reasons.append(_post_or_get(request, "reason-text", ""))
reason_text = "\n\n".join(reasons) + "\n\n"
utils.custom_unsub_reason(token, reason_text)
context = {
'unsubscribed_all': unsubscribed_all,
'reasons_submitted': reasons_submitted,
'token': token,
'reasons': enumerate(REASONS),
"unsubscribed_all": unsubscribed_all,
"reasons_submitted": reasons_submitted,
"token": token,
"reasons": enumerate(REASONS),
}
return l10n_utils.render(request,
'newsletter/updated.html',
context,
ftl_files=FTL_FILES)
return l10n_utils.render(request, "newsletter/updated.html", context, ftl_files=FTL_FILES)
@never_cache
@ -572,34 +558,30 @@ def recovery(request):
to manage their subscriptions.
"""
if request.method == 'POST':
if request.method == "POST":
form = EmailForm(request.POST)
if form.is_valid():
email = form.cleaned_data['email']
email = form.cleaned_data["email"]
try:
# Try it - basket will return an error if the email is unknown
basket.send_recovery_message(email)
except basket.BasketException as e:
# Was it that their email was not known? Or it could be invalid,
# but that doesn't really make a difference.
if e.code in (basket.errors.BASKET_UNKNOWN_EMAIL,
basket.errors.BASKET_INVALID_EMAIL):
if e.code in (basket.errors.BASKET_UNKNOWN_EMAIL, basket.errors.BASKET_INVALID_EMAIL):
# Tell them, give them a link to go subscribe if they want
url = reverse('newsletter.subscribe')
form.errors['email'] = \
form.error_class([ftl('newsletters-this-email-address-is-not',
url=url,
ftl_files=FTL_FILES)])
url = reverse("newsletter.subscribe")
form.errors["email"] = form.error_class([ftl("newsletters-this-email-address-is-not", url=url, ftl_files=FTL_FILES)])
else:
# Log the details
log.exception("Error sending recovery message")
# and tell the user that something went wrong
form.errors['__all__'] = form.error_class([general_error])
form.errors["__all__"] = form.error_class([general_error])
else:
messages.add_message(request, messages.INFO, recovery_text)
# Redir as GET, signalling success
return redirect(request.path + "?success")
elif 'success' in request.GET:
elif "success" in request.GET:
# We were redirected after a successful submission.
# A message will be displayed; don't display the form again.
form = None
@ -607,57 +589,60 @@ def recovery(request):
form = EmailForm()
# This view is shared between two different templates. For context see bug 1442129.
if '/newsletter/opt-out-confirmation/' in request.get_full_path():
if "/newsletter/opt-out-confirmation/" in request.get_full_path():
template = "newsletter/opt-out-confirmation.html"
ftl_files = ['newsletter/opt-out-confirmation']
ftl_files = ["newsletter/opt-out-confirmation"]
else:
template = "newsletter/recovery.html"
ftl_files = FTL_FILES
return l10n_utils.render(request, template, {'form': form}, ftl_files=ftl_files)
return l10n_utils.render(request, template, {"form": form}, ftl_files=ftl_files)
def newsletter_subscribe(request):
if request.method == 'POST':
newsletters = request.POST.get('newsletters', None)
form = NewsletterFooterForm(newsletters,
l10n_utils.get_locale(request),
request.POST)
if request.method == "POST":
newsletters = request.POST.get("newsletters", None)
form = NewsletterFooterForm(newsletters, l10n_utils.get_locale(request), request.POST)
errors = []
if form.is_valid():
data = form.cleaned_data
kwargs = {'format': data['fmt']}
kwargs = {"format": data["fmt"]}
# add optional data
kwargs.update(dict((k, data[k]) for k in ['country',
'lang',
'source_url',
'first_name',
'last_name', ]
if data[k]))
kwargs.update(
dict(
(k, data[k])
for k in [
"country",
"lang",
"source_url",
"first_name",
"last_name",
]
if data[k]
)
)
# NOTE this is not a typo; Referrer is misspelled in the HTTP spec
# https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.36
if not kwargs.get('source_url') and request.META.get('HTTP_REFERER'):
kwargs['source_url'] = request.META['HTTP_REFERER']
if not kwargs.get("source_url") and request.META.get("HTTP_REFERER"):
kwargs["source_url"] = request.META["HTTP_REFERER"]
try:
basket.subscribe(data['email'], data['newsletters'],
**kwargs)
basket.subscribe(data["email"], data["newsletters"], **kwargs)
except basket.BasketException as e:
if e.code == basket.errors.BASKET_INVALID_EMAIL:
errors.append(str(invalid_email_address))
else:
log.exception("Error subscribing %s to newsletter %s" %
(data['email'], data['newsletters']))
log.exception("Error subscribing %s to newsletter %s" % (data["email"], data["newsletters"]))
errors.append(str(general_error))
else:
if 'email' in form.errors:
errors.append(ftl('newsletter-form-please-enter-a-valid'))
if 'privacy' in form.errors:
errors.append(ftl('newsletter-form-you-must-agree-to'))
for fieldname in ('fmt', 'lang', 'country'):
if "email" in form.errors:
errors.append(ftl("newsletter-form-please-enter-a-valid"))
if "privacy" in form.errors:
errors.append(ftl("newsletter-form-you-must-agree-to"))
for fieldname in ("fmt", "lang", "country"):
if fieldname in form.errors:
errors.extend(form.errors[fieldname])
@ -668,18 +653,18 @@ def newsletter_subscribe(request):
# return JSON
if errors:
resp = {
'success': False,
'errors': errors,
"success": False,
"errors": errors,
}
else:
resp = {'success': True}
resp = {"success": True}
return JsonResponse(resp)
else:
ctx = {'newsletter_form': form}
ctx = {"newsletter_form": form}
if not errors:
ctx['success'] = True
ctx["success"] = True
return l10n_utils.render(request, 'newsletter/index.html', ctx, ftl_files=FTL_FILES)
return l10n_utils.render(request, "newsletter/index.html", ctx, ftl_files=FTL_FILES)
return l10n_utils.render(request, 'newsletter/index.html', ftl_files=FTL_FILES)
return l10n_utils.render(request, "newsletter/index.html", ftl_files=FTL_FILES)

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше