зеркало из https://github.com/mozilla/bedrock.git
Django 2.2 (#7196)
* Complete removal of nose test artifacts * Update base requirements to Django 2.2 and python 3 compatibile packages
This commit is contained in:
Родитель
301bcfbd28
Коммит
bf67e8bf34
|
@ -1,2 +1,4 @@
|
||||||
.git
|
.git
|
||||||
.env
|
.env
|
||||||
|
**/__pycache__
|
||||||
|
**/*.pyc
|
||||||
|
|
|
@ -23,7 +23,7 @@ RUN gulp build --production
|
||||||
########
|
########
|
||||||
# Python dependencies builder
|
# Python dependencies builder
|
||||||
#
|
#
|
||||||
FROM python:2-stretch AS python-builder
|
FROM python:3-slim AS python-builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
|
@ -34,6 +34,7 @@ ENV PATH="/venv/bin:$PATH"
|
||||||
COPY docker/bin/apt-install /usr/local/bin/
|
COPY docker/bin/apt-install /usr/local/bin/
|
||||||
RUN apt-install gettext build-essential libxml2-dev libxslt1-dev libxslt1.1
|
RUN apt-install gettext build-essential libxml2-dev libxslt1-dev libxslt1.1
|
||||||
|
|
||||||
|
RUN pip install virtualenv
|
||||||
RUN virtualenv /venv
|
RUN virtualenv /venv
|
||||||
|
|
||||||
COPY requirements/base.txt requirements/prod.txt ./requirements/
|
COPY requirements/base.txt requirements/prod.txt ./requirements/
|
||||||
|
@ -45,7 +46,7 @@ RUN pip install --no-cache-dir -r requirements/prod.txt
|
||||||
########
|
########
|
||||||
# django app container
|
# django app container
|
||||||
#
|
#
|
||||||
FROM python:2-slim-stretch AS app-base
|
FROM python:3-slim AS app-base
|
||||||
|
|
||||||
# Extra python env
|
# Extra python env
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -79,7 +79,7 @@ clean:
|
||||||
git clean -f
|
git clean -f
|
||||||
|
|
||||||
lint: .docker-build-pull
|
lint: .docker-build-pull
|
||||||
${DC} run test flake8 bedrock lib tests
|
${DC} run test flake8
|
||||||
${DC} run assets gulp js:lint css:lint
|
${DC} run assets gulp js:lint css:lint
|
||||||
|
|
||||||
test: .docker-build-pull
|
test: .docker-build-pull
|
||||||
|
|
|
@ -12,7 +12,7 @@ class SimpleDictCache(LocMemCache):
|
||||||
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
with self._lock.writer():
|
with self._lock:
|
||||||
if self._has_expired(key):
|
if self._has_expired(key):
|
||||||
self._set(key, value, timeout)
|
self._set(key, value, timeout)
|
||||||
return True
|
return True
|
||||||
|
@ -22,13 +22,13 @@ class SimpleDictCache(LocMemCache):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
value = default
|
value = default
|
||||||
with self._lock.reader():
|
with self._lock:
|
||||||
if not self._has_expired(key):
|
if not self._has_expired(key):
|
||||||
value = self._cache[key]
|
value = self._cache[key]
|
||||||
if value is not default:
|
if value is not default:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
with self._lock.writer():
|
with self._lock:
|
||||||
try:
|
try:
|
||||||
del self._cache[key]
|
del self._cache[key]
|
||||||
del self._expire_info[key]
|
del self._expire_info[key]
|
||||||
|
@ -39,7 +39,7 @@ class SimpleDictCache(LocMemCache):
|
||||||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
self.validate_key(key)
|
self.validate_key(key)
|
||||||
with self._lock.writer():
|
with self._lock:
|
||||||
self._set(key, value, timeout)
|
self._set(key, value, timeout)
|
||||||
|
|
||||||
def incr(self, key, delta=1, version=None):
|
def incr(self, key, delta=1, version=None):
|
||||||
|
@ -48,6 +48,6 @@ class SimpleDictCache(LocMemCache):
|
||||||
raise ValueError("Key '%s' not found" % key)
|
raise ValueError("Key '%s' not found" % key)
|
||||||
new_value = value + delta
|
new_value = value + delta
|
||||||
key = self.make_key(key, version=version)
|
key = self.make_key(key, version=version)
|
||||||
with self._lock.writer():
|
with self._lock:
|
||||||
self._cache[key] = new_value
|
self._cache[key] = new_value
|
||||||
return new_value
|
return new_value
|
||||||
|
|
|
@ -91,7 +91,7 @@ for key, value in settings.LOGGING.items():
|
||||||
cfg[key] = value
|
cfg[key] = value
|
||||||
|
|
||||||
# Set the level and handlers for all loggers.
|
# Set the level and handlers for all loggers.
|
||||||
for logger in cfg['loggers'].values() + [cfg['root']]:
|
for logger in list(cfg['loggers'].values()) + [cfg['root']]:
|
||||||
if 'handlers' not in logger:
|
if 'handlers' not in logger:
|
||||||
logger['handlers'] = ['syslog' if use_syslog else 'console']
|
logger['handlers'] = ['syslog' if use_syslog else 'console']
|
||||||
if 'level' not in logger:
|
if 'level' not in logger:
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ def refresh_db_values():
|
||||||
|
|
||||||
ConfigValue.objects.all().delete()
|
ConfigValue.objects.all().delete()
|
||||||
count = 0
|
count = 0
|
||||||
for name, value in values.iteritems():
|
for name, value in values.items():
|
||||||
if value:
|
if value:
|
||||||
ConfigValue.objects.create(name=name, value=value)
|
ConfigValue.objects.create(name=name, value=value)
|
||||||
count += 1
|
count += 1
|
||||||
|
|
|
@ -5,30 +5,41 @@ This is django-localeurl, but with mozilla style capital letters in
|
||||||
the locale codes.
|
the locale codes.
|
||||||
"""
|
"""
|
||||||
import base64
|
import base64
|
||||||
import urllib
|
import urllib.parse
|
||||||
|
from urllib.parse import unquote
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
|
from commonware.middleware import FrameOptionsHeader as OldFrameOptionsHeader
|
||||||
|
from commonware.middleware import RobotsTagHeader as OldRobotsTagHeader
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import MiddlewareNotUsed
|
from django.core.exceptions import MiddlewareNotUsed
|
||||||
from django.http import HttpResponsePermanentRedirect, HttpResponse
|
from django.http import HttpResponse, HttpResponsePermanentRedirect
|
||||||
from django.utils.encoding import force_text
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
|
||||||
from . import urlresolvers
|
|
||||||
from lib.l10n_utils import translation
|
from lib.l10n_utils import translation
|
||||||
|
|
||||||
|
from . import urlresolvers
|
||||||
|
|
||||||
class LocaleURLMiddleware(object):
|
|
||||||
|
class LocaleURLMiddleware:
|
||||||
"""
|
"""
|
||||||
1. Search for the locale.
|
1. Search for the locale.
|
||||||
2. Save it in the request.
|
2. Save it in the request.
|
||||||
3. Strip them from the URL.
|
3. Strip them from the URL.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, get_response=None):
|
||||||
if not settings.USE_I18N or not settings.USE_L10N:
|
if not settings.USE_I18N or not settings.USE_L10N:
|
||||||
warn("USE_I18N or USE_L10N is False but LocaleURLMiddleware is "
|
warn("USE_I18N or USE_L10N is False but LocaleURLMiddleware is "
|
||||||
"loaded. Consider removing bedrock.base.middleware."
|
"loaded. Consider removing bedrock.base.middleware."
|
||||||
"LocaleURLMiddleware from your MIDDLEWARE_CLASSES setting.")
|
"LocaleURLMiddleware from your MIDDLEWARE setting.")
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.process_request(request)
|
||||||
|
if response:
|
||||||
|
return response
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
prefixer = urlresolvers.Prefixer(request)
|
prefixer = urlresolvers.Prefixer(request)
|
||||||
|
@ -37,11 +48,11 @@ class LocaleURLMiddleware(object):
|
||||||
|
|
||||||
if full_path != request.path:
|
if full_path != request.path:
|
||||||
query_string = request.META.get('QUERY_STRING', '')
|
query_string = request.META.get('QUERY_STRING', '')
|
||||||
full_path = urllib.quote(full_path.encode('utf-8'))
|
full_path = urllib.parse.quote(full_path.encode('utf-8'))
|
||||||
|
|
||||||
if query_string:
|
if query_string:
|
||||||
full_path = '?'.join(
|
full_path = '?'.join(
|
||||||
[full_path, force_text(query_string, errors='ignore')])
|
[full_path, unquote(query_string, errors='ignore')])
|
||||||
|
|
||||||
response = HttpResponsePermanentRedirect(full_path)
|
response = HttpResponsePermanentRedirect(full_path)
|
||||||
|
|
||||||
|
@ -58,14 +69,21 @@ class LocaleURLMiddleware(object):
|
||||||
translation.activate(prefixer.locale or settings.LANGUAGE_CODE)
|
translation.activate(prefixer.locale or settings.LANGUAGE_CODE)
|
||||||
|
|
||||||
|
|
||||||
class BasicAuthMiddleware(object):
|
class BasicAuthMiddleware:
|
||||||
"""
|
"""
|
||||||
Middleware to protect the entire site with a single basic-auth username and password.
|
Middleware to protect the entire site with a single basic-auth username and password.
|
||||||
Set the BASIC_AUTH_CREDS environment variable to enable.
|
Set the BASIC_AUTH_CREDS environment variable to enable.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, get_response=None):
|
||||||
if not settings.BASIC_AUTH_CREDS:
|
if not settings.BASIC_AUTH_CREDS:
|
||||||
raise MiddlewareNotUsed
|
raise MiddlewareNotUsed
|
||||||
|
self.get_response = None
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.process_request(request)
|
||||||
|
if response:
|
||||||
|
return response
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
required_auth = settings.BASIC_AUTH_CREDS
|
required_auth = settings.BASIC_AUTH_CREDS
|
||||||
|
@ -85,3 +103,11 @@ class BasicAuthMiddleware(object):
|
||||||
realm = settings.APP_NAME or 'bedrock-demo'
|
realm = settings.APP_NAME or 'bedrock-demo'
|
||||||
response['WWW-Authenticate'] = 'Basic realm="{}"'.format(realm)
|
response['WWW-Authenticate'] = 'Basic realm="{}"'.format(realm)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class RobotsTagHeader(OldRobotsTagHeader, MiddlewareMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FrameOptionsHeader(OldFrameOptionsHeader, MiddlewareMixin):
|
||||||
|
pass
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ class ConfigValue(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'base'
|
app_label = 'base'
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return '%s=%s' % (self.name, self.value)
|
return '%s=%s' % (self.name, self.value)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
import urllib
|
import urllib.parse
|
||||||
import urlparse
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
|
@ -73,43 +72,43 @@ def urlparams(url_, hash=None, **query):
|
||||||
New query params will be appended to exising parameters, except duplicate
|
New query params will be appended to exising parameters, except duplicate
|
||||||
names, which will be replaced.
|
names, which will be replaced.
|
||||||
"""
|
"""
|
||||||
url = urlparse.urlparse(url_)
|
url = urllib.parse.urlparse(url_)
|
||||||
fragment = hash if hash is not None else url.fragment
|
fragment = hash if hash is not None else url.fragment
|
||||||
|
|
||||||
# Use dict(parse_qsl) so we don't get lists of values.
|
# Use dict(parse_qsl) so we don't get lists of values.
|
||||||
q = url.query
|
q = url.query
|
||||||
query_dict = dict(urlparse.parse_qsl(smart_str(q))) if q else {}
|
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_dict.update((k, v) for k, v in query.items())
|
||||||
|
|
||||||
query_string = _urlencode([(k, v) for k, v in query_dict.items()
|
query_string = _urlencode([(k, v) for k, v in query_dict.items()
|
||||||
if v is not None])
|
if v is not None])
|
||||||
new = urlparse.ParseResult(url.scheme, url.netloc, url.path, url.params,
|
new = urllib.parse.ParseResult(
|
||||||
query_string, fragment)
|
url.scheme, url.netloc, url.path, url.params, query_string, fragment)
|
||||||
return new.geturl()
|
return new.geturl()
|
||||||
|
|
||||||
|
|
||||||
def _urlencode(items):
|
def _urlencode(items):
|
||||||
"""A Unicode-safe URLencoder."""
|
"""A Unicode-safe URLencoder."""
|
||||||
try:
|
try:
|
||||||
return urllib.urlencode(items)
|
return urllib.parse.urlencode(items)
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
return urllib.urlencode([(k, smart_str(v)) for k, v in items])
|
return urllib.parse.urlencode([(k, smart_str(v)) for k, v in items])
|
||||||
|
|
||||||
|
|
||||||
@library.filter
|
@library.filter
|
||||||
def mailtoencode(txt):
|
def mailtoencode(txt):
|
||||||
"""Url encode a string using %20 for spaces."""
|
"""Url encode a string using %20 for spaces."""
|
||||||
if isinstance(txt, unicode):
|
if isinstance(txt, str):
|
||||||
txt = txt.encode('utf-8')
|
txt = txt.encode('utf-8')
|
||||||
return urllib.quote(txt)
|
return urllib.parse.quote(txt)
|
||||||
|
|
||||||
|
|
||||||
@library.filter
|
@library.filter
|
||||||
def urlencode(txt):
|
def urlencode(txt):
|
||||||
"""Url encode a string using + for spaces."""
|
"""Url encode a string using + for spaces."""
|
||||||
if isinstance(txt, unicode):
|
if isinstance(txt, str):
|
||||||
txt = txt.encode('utf-8')
|
txt = txt.encode('utf-8')
|
||||||
return urllib.quote_plus(txt)
|
return urllib.parse.quote_plus(txt)
|
||||||
|
|
||||||
|
|
||||||
@library.global_function
|
@library.global_function
|
||||||
|
|
|
@ -82,9 +82,9 @@ class AcceptedLocalesTest(TestCase):
|
||||||
# simulate the successful result of the DEV_LANGUAGES list
|
# simulate the successful result of the DEV_LANGUAGES list
|
||||||
# comprehension defined in settings.
|
# comprehension defined in settings.
|
||||||
settings.DEV_LANGUAGES = ['en-US', 'fr']
|
settings.DEV_LANGUAGES = ['en-US', 'fr']
|
||||||
assert settings.LANGUAGE_URL_MAP == {'en-us': 'en-US', 'fr': 'fr'}, \
|
assert settings.LANGUAGE_URL_MAP == {'en-us': 'en-US', 'fr': 'fr'}, (
|
||||||
('DEV is True, but DEV_LANGUAGES are not used to define the '
|
'DEV is True, but DEV_LANGUAGES are not used to define the '
|
||||||
'allowed locales.')
|
'allowed locales.')
|
||||||
|
|
||||||
def test_prod_languages(self):
|
def test_prod_languages(self):
|
||||||
"""Test the accepted locales on prod instances.
|
"""Test the accepted locales on prod instances.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
@ -15,7 +17,7 @@ class TestLocaleURLMiddleware(TestCase):
|
||||||
"""Should redirect to lang prefixed url."""
|
"""Should redirect to lang prefixed url."""
|
||||||
path = '/the/dude/'
|
path = '/the/dude/'
|
||||||
req = self.rf.get(path, HTTP_ACCEPT_LANGUAGE='de')
|
req = self.rf.get(path, HTTP_ACCEPT_LANGUAGE='de')
|
||||||
resp = LocaleURLMiddleware().process_request(req)
|
resp = self.middleware.process_request(req)
|
||||||
self.assertEqual(resp['Location'], '/de' + path)
|
self.assertEqual(resp['Location'], '/de' + path)
|
||||||
|
|
||||||
@override_settings(DEV_LANGUAGES=('es', 'fr'),
|
@override_settings(DEV_LANGUAGES=('es', 'fr'),
|
||||||
|
@ -24,17 +26,18 @@ class TestLocaleURLMiddleware(TestCase):
|
||||||
"""Should redirect to default lang if not in settings."""
|
"""Should redirect to default lang if not in settings."""
|
||||||
path = '/the/dude/'
|
path = '/the/dude/'
|
||||||
req = self.rf.get(path, HTTP_ACCEPT_LANGUAGE='de')
|
req = self.rf.get(path, HTTP_ACCEPT_LANGUAGE='de')
|
||||||
resp = LocaleURLMiddleware().process_request(req)
|
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):
|
def test_redirects_to_correct_language_despite_unicode_errors(self):
|
||||||
"""Should redirect to lang prefixed url, stripping invalid chars."""
|
"""Should redirect to lang prefixed url, stripping invalid chars."""
|
||||||
path = '/the/dude/'
|
path = '/the/dude/'
|
||||||
corrupt_querystring = '?a\xa4\x91b\xa4\x91i\xc0de=s'
|
corrupt_querystring = '?' + urlencode(
|
||||||
|
{b'a\xa4\x91b\xa4\x91i\xc0de': 's'})
|
||||||
corrected_querystring = '?abide=s'
|
corrected_querystring = '?abide=s'
|
||||||
req = self.rf.get(path + corrupt_querystring,
|
req = self.rf.get(path + corrupt_querystring,
|
||||||
HTTP_ACCEPT_LANGUAGE='de')
|
HTTP_ACCEPT_LANGUAGE='de')
|
||||||
resp = LocaleURLMiddleware().process_request(req)
|
resp = self.middleware.process_request(req)
|
||||||
self.assertEqual(resp['Location'],
|
self.assertEqual(resp['Location'],
|
||||||
'/de' + path + corrected_querystring)
|
'/de' + path + corrected_querystring)
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
# and instead uses a simple dict for in-memory storage. These were adapted
|
# and instead uses a simple dict for in-memory storage. These were adapted
|
||||||
# from the cache backend tests in Django itself.
|
# from the cache backend tests in Django itself.
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -81,7 +79,7 @@ class SimpleDictCacheTests(TestCase):
|
||||||
cache.set('somekey', 'value')
|
cache.set('somekey', 'value')
|
||||||
|
|
||||||
# should not be set in the prefixed cache
|
# should not be set in the prefixed cache
|
||||||
self.assertFalse(caches['prefix'].has_key('somekey')) # noqa
|
self.assertFalse('somekey' in caches['prefix']) # noqa
|
||||||
|
|
||||||
caches['prefix'].set('somekey', 'value2')
|
caches['prefix'].set('somekey', 'value2')
|
||||||
|
|
||||||
|
@ -120,10 +118,10 @@ class SimpleDictCacheTests(TestCase):
|
||||||
def test_has_key(self):
|
def test_has_key(self):
|
||||||
# The cache can be inspected for cache keys
|
# The cache can be inspected for cache keys
|
||||||
cache.set("hello1", "goodbye1")
|
cache.set("hello1", "goodbye1")
|
||||||
self.assertEqual(cache.has_key("hello1"), True) # noqa
|
self.assertEqual("hello1" in cache, True) # noqa
|
||||||
self.assertEqual(cache.has_key("goodbye1"), False) # noqa
|
self.assertEqual("goodbye1" in cache, False) # noqa
|
||||||
cache.set("no_expiry", "here", None)
|
cache.set("no_expiry", "here", None)
|
||||||
self.assertEqual(cache.has_key("no_expiry"), True) # noqa
|
self.assertEqual("no_expiry" in cache, True) # noqa
|
||||||
|
|
||||||
def test_in(self):
|
def test_in(self):
|
||||||
# The in operator can be used to inspect cache contents
|
# The in operator can be used to inspect cache contents
|
||||||
|
@ -180,7 +178,7 @@ class SimpleDictCacheTests(TestCase):
|
||||||
|
|
||||||
cache.add("expire2", "newvalue")
|
cache.add("expire2", "newvalue")
|
||||||
self.assertEqual(cache.get("expire2"), "newvalue")
|
self.assertEqual(cache.get("expire2"), "newvalue")
|
||||||
self.assertEqual(cache.has_key("expire3"), False) # noqa
|
self.assertEqual("expire3" in cache, False) # noqa
|
||||||
|
|
||||||
def test_unicode(self):
|
def test_unicode(self):
|
||||||
# Unicode values can be cached
|
# Unicode values can be cached
|
||||||
|
@ -191,21 +189,21 @@ class SimpleDictCacheTests(TestCase):
|
||||||
'ascii2': {'x': 1}
|
'ascii2': {'x': 1}
|
||||||
}
|
}
|
||||||
# Test `set`
|
# Test `set`
|
||||||
for (key, value) in stuff.items():
|
for key, value in stuff.items():
|
||||||
cache.set(key, value)
|
cache.set(key, value)
|
||||||
self.assertEqual(cache.get(key), value)
|
self.assertEqual(cache.get(key), value)
|
||||||
|
|
||||||
# Test `add`
|
# Test `add`
|
||||||
for (key, value) in stuff.items():
|
for key, value in stuff.items():
|
||||||
cache.delete(key)
|
cache.delete(key)
|
||||||
cache.add(key, value)
|
cache.add(key, value)
|
||||||
self.assertEqual(cache.get(key), value)
|
self.assertEqual(cache.get(key), value)
|
||||||
|
|
||||||
# Test `set_many`
|
# Test `set_many`
|
||||||
for (key, value) in stuff.items():
|
for key, value in stuff.items():
|
||||||
cache.delete(key)
|
cache.delete(key)
|
||||||
cache.set_many(stuff)
|
cache.set_many(stuff)
|
||||||
for (key, value) in stuff.items():
|
for key, value in stuff.items():
|
||||||
self.assertEqual(cache.get(key), value)
|
self.assertEqual(cache.get(key), value)
|
||||||
|
|
||||||
def test_binary_string(self):
|
def test_binary_string(self):
|
||||||
|
@ -324,7 +322,7 @@ class SimpleDictCacheTests(TestCase):
|
||||||
count = 0
|
count = 0
|
||||||
# Count how many keys are left in the cache.
|
# Count how many keys are left in the cache.
|
||||||
for i in range(1, initial_count):
|
for i in range(1, initial_count):
|
||||||
if cull_cache.has_key('cull%d' % i): # noqa
|
if 'cull%d' % i in cull_cache: # noqa
|
||||||
count = count + 1
|
count = count + 1
|
||||||
self.assertEqual(count, final_count)
|
self.assertEqual(count, final_count)
|
||||||
|
|
||||||
|
@ -451,11 +449,11 @@ class SimpleDictCacheTests(TestCase):
|
||||||
cache.set('answer1', 42)
|
cache.set('answer1', 42)
|
||||||
|
|
||||||
# has_key
|
# has_key
|
||||||
self.assertTrue(cache.has_key('answer1')) # noqa
|
self.assertTrue('answer1' in cache) # noqa
|
||||||
self.assertTrue(cache.has_key('answer1', version=1)) # noqa
|
self.assertTrue(cache.has_key('answer1', version=1)) # noqa
|
||||||
self.assertFalse(cache.has_key('answer1', version=2)) # noqa
|
self.assertFalse(cache.has_key('answer1', version=2)) # noqa
|
||||||
|
|
||||||
self.assertFalse(caches['v2'].has_key('answer1')) # noqa
|
self.assertFalse('answer1' in caches['v2']) # noqa
|
||||||
self.assertTrue(caches['v2'].has_key('answer1', version=1)) # noqa
|
self.assertTrue(caches['v2'].has_key('answer1', version=1)) # noqa
|
||||||
self.assertFalse(caches['v2'].has_key('answer1', version=2)) # noqa
|
self.assertFalse(caches['v2'].has_key('answer1', version=2)) # noqa
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
@ -26,11 +26,11 @@ def test_split_path(path, result):
|
||||||
|
|
||||||
# Test urlpatterns
|
# Test urlpatterns
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^test/$', lambda r: None, name='test.view')
|
re_path(r'^test/$', lambda r: None, name='test.view')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class FakePrefixer(object):
|
class FakePrefixer:
|
||||||
def __init__(self, fix):
|
def __init__(self, fix):
|
||||||
self.fix = fix
|
self.fix = fix
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from threading import local
|
from threading import local
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse as django_reverse
|
from django.urls import reverse as django_reverse
|
||||||
from django.utils.encoding import iri_to_uri
|
from django.utils.encoding import iri_to_uri
|
||||||
from django.utils.functional import lazy
|
from django.utils.functional import lazy
|
||||||
from django.utils.translation.trans_real import parse_accept_lang_header
|
from django.utils.translation.trans_real import parse_accept_lang_header
|
||||||
|
@ -45,7 +45,7 @@ def _get_language_map():
|
||||||
:return: dict
|
:return: dict
|
||||||
"""
|
"""
|
||||||
LUM = settings.LANGUAGE_URL_MAP
|
LUM = settings.LANGUAGE_URL_MAP
|
||||||
langs = dict(LUM.items() + settings.CANONICAL_LOCALES.items())
|
langs = dict(list(LUM.items()) + list(settings.CANONICAL_LOCALES.items()))
|
||||||
# Add missing short locales to the list. This will automatically map
|
# Add missing short locales to the list. This will automatically map
|
||||||
# en to en-GB (not en-US), es to es-AR (not es-ES), etc. in alphabetical
|
# 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
|
# order. To override this behavior, explicitly define a preferred locale
|
||||||
|
@ -86,7 +86,7 @@ def split_path(path_):
|
||||||
return '', path
|
return '', path
|
||||||
|
|
||||||
|
|
||||||
class Prefixer(object):
|
class Prefixer:
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
self.request = request
|
self.request = request
|
||||||
split = split_path(request.path_info)
|
split = split_path(request.path_info)
|
||||||
|
|
|
@ -5,19 +5,17 @@ from datetime import datetime
|
||||||
from os import getenv
|
from os import getenv
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
import timeago
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest
|
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.http import require_POST, require_safe
|
from django.views.decorators.http import require_POST, require_safe
|
||||||
|
from lib import l10n_utils
|
||||||
import timeago
|
|
||||||
from raven.contrib.django.models import client
|
from raven.contrib.django.models import client
|
||||||
|
|
||||||
from bedrock.mozorg.util import HttpResponseJSON
|
|
||||||
from bedrock.utils import git
|
from bedrock.utils import git
|
||||||
from lib import l10n_utils
|
|
||||||
|
|
||||||
|
|
||||||
def get_geo_from_request(request):
|
def get_geo_from_request(request):
|
||||||
|
@ -46,7 +44,7 @@ def geolocate(request):
|
||||||
"""
|
"""
|
||||||
country_code = get_geo_from_request(request)
|
country_code = get_geo_from_request(request)
|
||||||
if country_code is None:
|
if country_code is None:
|
||||||
return HttpResponseJSON({
|
return JsonResponse({
|
||||||
"error": {
|
"error": {
|
||||||
"errors": [{
|
"errors": [{
|
||||||
"domain": "geolocation",
|
"domain": "geolocation",
|
||||||
|
@ -58,7 +56,7 @@ def geolocate(request):
|
||||||
}
|
}
|
||||||
}, status=404)
|
}, status=404)
|
||||||
|
|
||||||
return HttpResponseJSON({
|
return JsonResponse({
|
||||||
'country_code': country_code,
|
'country_code': country_code,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django_extensions.db.fields.json
|
import django_extensions.db.fields.json
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ class ContentCard(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return '{} ({})'.format(self.card_name, self.locale)
|
return '{} ({})'.format(self.card_name, self.locale)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from bedrock.mozorg.util import page
|
from bedrock.mozorg.util import page
|
||||||
|
|
||||||
import views
|
from bedrock.etc import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = (
|
urlpatterns = (
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
from __future__ import unicode_literals, print_function
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from datetime import datetime
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
|
from django.utils.encoding import force_text
|
||||||
from icalendar import Calendar
|
from icalendar import Calendar
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ class Event(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('start_time',)
|
ordering = ('start_time',)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -142,13 +142,14 @@ class Event(models.Model):
|
||||||
return u'<abbr>{0:%b}</abbr>'.format(self.start_time)
|
return u'<abbr>{0:%b}</abbr>'.format(self.start_time)
|
||||||
|
|
||||||
def update_from_ical(self, ical_event):
|
def update_from_ical(self, ical_event):
|
||||||
for field, ical_prop in self.field_to_ical.iteritems():
|
for field, ical_prop in self.field_to_ical.items():
|
||||||
try:
|
try:
|
||||||
value = ical_event.decoded(ical_prop)
|
value = ical_event.decoded(ical_prop)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if isinstance(value, basestring):
|
value = force_text(value, strings_only=True)
|
||||||
|
if isinstance(value, str):
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
setattr(self, field, value)
|
setattr(self, field, value)
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ class TestFutureQuerySet(TestCase):
|
||||||
"""
|
"""
|
||||||
Should not raise error during DST change
|
Should not raise error during DST change
|
||||||
"""
|
"""
|
||||||
mock_datetime.utcnow.return_value = datetime(2014, 11, 02, 01, 01)
|
mock_datetime.utcnow.return_value = datetime(2014, 11, 0o2, 0o1, 0o1)
|
||||||
assert Event.objects.future().count() == 0
|
assert Event.objects.future().count() == 0
|
||||||
|
|
||||||
@override_settings(USE_TZ=False)
|
@override_settings(USE_TZ=False)
|
||||||
|
@ -82,7 +82,7 @@ class TestFutureQuerySet(TestCase):
|
||||||
"""
|
"""
|
||||||
Should not raise error during DST change
|
Should not raise error during DST change
|
||||||
"""
|
"""
|
||||||
mock_datetime.utcnow.return_value = datetime(2014, 11, 02, 01, 01)
|
mock_datetime.utcnow.return_value = datetime(2014, 11, 0o2, 0o1, 0o1)
|
||||||
assert Event.objects.future().count() == 0
|
assert Event.objects.future().count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ class TestQuerySets(TestCase):
|
||||||
self.mock_datetime = datetime_patcher.start()
|
self.mock_datetime = datetime_patcher.start()
|
||||||
self.addCleanup(datetime_patcher.stop)
|
self.addCleanup(datetime_patcher.stop)
|
||||||
|
|
||||||
self.mock_datetime.utcnow.return_value = datetime(2015, 05, 04, 12, 00)
|
self.mock_datetime.utcnow.return_value = datetime(2015, 0o5, 0o4, 12, 00)
|
||||||
|
|
||||||
def test_past(self):
|
def test_past(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,10 +6,7 @@ import codecs
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
from time import mktime
|
from time import mktime
|
||||||
try:
|
from io import StringIO
|
||||||
from cStringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import caches
|
from django.core.cache import caches
|
||||||
|
@ -19,7 +16,7 @@ from django.utils.http import http_date
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ExternalFile(object):
|
class ExternalFile:
|
||||||
def __init__(self, file_id):
|
def __init__(self, file_id):
|
||||||
try:
|
try:
|
||||||
fileinfo = settings.EXTERNAL_FILES[file_id]
|
fileinfo = settings.EXTERNAL_FILES[file_id]
|
||||||
|
@ -87,7 +84,7 @@ class ExternalFile(object):
|
||||||
return self.validate_content(content)
|
return self.validate_content(content)
|
||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
return self.file_object.content.encode('utf-8')
|
return self.file_object.content
|
||||||
|
|
||||||
def readlines(self):
|
def readlines(self):
|
||||||
return StringIO(self.read()).readlines()
|
return StringIO(self.read()).readlines()
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from urllib import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ class FirefoxDesktop(_ProductDetails):
|
||||||
"""
|
"""
|
||||||
if classified:
|
if classified:
|
||||||
platforms = OrderedDict()
|
platforms = OrderedDict()
|
||||||
for k, v in self.platform_classification.iteritems():
|
for k, v in self.platform_classification.items():
|
||||||
for platform in v:
|
for platform in v:
|
||||||
platforms[platform] = self.platform_labels[platform]
|
platforms[platform] = self.platform_labels[platform]
|
||||||
else:
|
else:
|
||||||
|
@ -88,7 +88,7 @@ class FirefoxDesktop(_ProductDetails):
|
||||||
del platforms['win64-msi']
|
del platforms['win64-msi']
|
||||||
del platforms['win-msi']
|
del platforms['win-msi']
|
||||||
|
|
||||||
return platforms.items()
|
return list(platforms.items())
|
||||||
|
|
||||||
def latest_version(self, channel='release'):
|
def latest_version(self, channel='release'):
|
||||||
version = self.version_map.get(channel, 'LATEST_FIREFOX_VERSION')
|
version = self.version_map.get(channel, 'LATEST_FIREFOX_VERSION')
|
||||||
|
@ -167,7 +167,7 @@ class FirefoxDesktop(_ProductDetails):
|
||||||
version = version or self.latest_version(channel)
|
version = version or self.latest_version(channel)
|
||||||
|
|
||||||
f_builds = []
|
f_builds = []
|
||||||
for locale, build in builds.iteritems():
|
for locale, build in builds.items():
|
||||||
if locale not in self.languages or not build.get(version):
|
if locale not in self.languages or not build.get(version):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ class FirefoxDesktop(_ProductDetails):
|
||||||
if query is not None and not self._matches_query(build_info, query):
|
if query is not None and not self._matches_query(build_info, query):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for platform, label in self.platform_labels.iteritems():
|
for platform, label in self.platform_labels.items():
|
||||||
build_info['platforms'][platform] = {
|
build_info['platforms'][platform] = {
|
||||||
'download_url': self.get_download_url(channel, version,
|
'download_url': self.get_download_url(channel, version,
|
||||||
platform, locale,
|
platform, locale,
|
||||||
|
@ -384,10 +384,10 @@ class FirefoxAndroid(_ProductDetails):
|
||||||
min_version = '4.0.3' if version_int < 56 else '4.1'
|
min_version = '4.0.3' if version_int < 56 else '4.1'
|
||||||
|
|
||||||
# key is a bouncer platform name, value is the human-readable label
|
# key is a bouncer platform name, value is the human-readable label
|
||||||
for arch, platform in self.platform_map.iteritems():
|
for arch, platform in self.platform_map.items():
|
||||||
platforms[platform] = self.platform_labels[arch] % min_version
|
platforms[platform] = self.platform_labels[arch] % min_version
|
||||||
|
|
||||||
return platforms.items()
|
return list(platforms.items())
|
||||||
|
|
||||||
def latest_version(self, channel):
|
def latest_version(self, channel):
|
||||||
version = self.version_map.get(channel, 'version')
|
version = self.version_map.get(channel, 'version')
|
||||||
|
@ -442,7 +442,7 @@ class FirefoxAndroid(_ProductDetails):
|
||||||
if query is not None and not self._matches_query(build_info, query):
|
if query is not None and not self._matches_query(build_info, query):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for arch, platform in self.platform_map.iteritems():
|
for arch, platform in self.platform_map.items():
|
||||||
# x86 builds are not localized yet
|
# 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
|
continue
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -178,15 +178,15 @@ redirectpatterns = (
|
||||||
'https://support.mozilla.org/kb/how-does-phishing-and-malware-protection-work'),
|
'https://support.mozilla.org/kb/how-does-phishing-and-malware-protection-work'),
|
||||||
|
|
||||||
# bug 1006079
|
# bug 1006079
|
||||||
redirect(r'^mobile/home/?(?:index.html)?$',
|
redirect(r'^mobile/home/?(?:index\.html)?$',
|
||||||
'https://blog.mozilla.org/services/2012/08/31/retiring-firefox-home/'),
|
'https://blog.mozilla.org/services/2012/08/31/retiring-firefox-home/'),
|
||||||
|
|
||||||
# bug 949562
|
# bug 949562
|
||||||
redirect(r'^mobile/home/1\.0/releasenotes(?:/(?:index.html)?)?$',
|
redirect(r'^mobile/home/1\.0/releasenotes(?:/(?:index\.html)?)?$',
|
||||||
'http://website-archive.mozilla.org/www.mozilla.org/firefox_home/mobile/home/1.0/releasenotes/'),
|
'http://website-archive.mozilla.org/www.mozilla.org/firefox_home/mobile/home/1.0/releasenotes/'),
|
||||||
redirect(r'^mobile/home/1\.0\.2/releasenotes(?:/(?:index.html)?)?$',
|
redirect(r'^mobile/home/1\.0\.2/releasenotes(?:/(?:index\.html)?)?$',
|
||||||
'http://website-archive.mozilla.org/www.mozilla.org/firefox_home/mobile/home/1.0.2/releasenotes/'),
|
'http://website-archive.mozilla.org/www.mozilla.org/firefox_home/mobile/home/1.0.2/releasenotes/'),
|
||||||
redirect(r'^mobile/home/faq(?:/(?:index.html)?)?$',
|
redirect(r'^mobile/home/faq(?:/(?:index\.html)?)?$',
|
||||||
'http://website-archive.mozilla.org/www.mozilla.org/firefox_home/mobile/home/faq/'),
|
'http://website-archive.mozilla.org/www.mozilla.org/firefox_home/mobile/home/faq/'),
|
||||||
|
|
||||||
# bug 960064
|
# bug 960064
|
||||||
|
@ -200,7 +200,7 @@ redirectpatterns = (
|
||||||
'https://support.mozilla.org/kb/will-firefox-work-my-mobile-device'),
|
'https://support.mozilla.org/kb/will-firefox-work-my-mobile-device'),
|
||||||
|
|
||||||
# bug 858315
|
# bug 858315
|
||||||
redirect(r'^projects/devpreview/firstrun(?:/(?:index.html)?)?$', '/firefox/firstrun/'),
|
redirect(r'^projects/devpreview/firstrun(?:/(?:index\.html)?)?$', '/firefox/firstrun/'),
|
||||||
redirect(r'^projects/devpreview/(?P<page>[\/\w\.-]+)?$',
|
redirect(r'^projects/devpreview/(?P<page>[\/\w\.-]+)?$',
|
||||||
'http://website-archive.mozilla.org/www.mozilla.org/devpreview_releasenotes/projects/devpreview/{page}'),
|
'http://website-archive.mozilla.org/www.mozilla.org/devpreview_releasenotes/projects/devpreview/{page}'),
|
||||||
|
|
||||||
|
@ -274,9 +274,9 @@ redirectpatterns = (
|
||||||
'http://www.seamonkey-project.org/dev/review-and-flags'),
|
'http://www.seamonkey-project.org/dev/review-and-flags'),
|
||||||
redirect(r'^projects/seamonkey/releases/(?P<vers>1\..*)\.html$',
|
redirect(r'^projects/seamonkey/releases/(?P<vers>1\..*)\.html$',
|
||||||
'http://www.seamonkey-project.org/releases/{vers}'),
|
'http://www.seamonkey-project.org/releases/{vers}'),
|
||||||
redirect(r'^projects/seamonkey/releases/seamonkey(?P<x>.*)/index.html$',
|
redirect(r'^projects/seamonkey/releases/seamonkey(?P<x>.*)/index\.html$',
|
||||||
'http://www.seamonkey-project.org/releases/seamonkey{x}/'),
|
'http://www.seamonkey-project.org/releases/seamonkey{x}/'),
|
||||||
redirect(r'^projects/seamonkey/releases/seamonkey(?P<x>.*/.*).html$',
|
redirect(r'^projects/seamonkey/releases/seamonkey(?P<x>.*/.*)\.html$',
|
||||||
'http://www.seamonkey-project.org/releases/seamonkey{x}'),
|
'http://www.seamonkey-project.org/releases/seamonkey{x}'),
|
||||||
redirect(r'^projects/seamonkey/releases/updates/(?P<x>.*)$',
|
redirect(r'^projects/seamonkey/releases/updates/(?P<x>.*)$',
|
||||||
'http://www.seamonkey-project.org/releases/updates/{x}'),
|
'http://www.seamonkey-project.org/releases/updates/{x}'),
|
||||||
|
@ -313,39 +313,39 @@ redirectpatterns = (
|
||||||
redirect(r'^firefox/ie', 'firefox.new'),
|
redirect(r'^firefox/ie', 'firefox.new'),
|
||||||
|
|
||||||
# must go above the bug 1255882 stuff below
|
# must go above the bug 1255882 stuff below
|
||||||
redirect('^projects/xul/joy-of-xul\.html$',
|
redirect(r'^projects/xul/joy-of-xul\.html$',
|
||||||
'https://developer.mozilla.org/docs/Mozilla/Tech/XUL/The_Joy_of_XUL'),
|
'https://developer.mozilla.org/docs/Mozilla/Tech/XUL/The_Joy_of_XUL'),
|
||||||
redirect('^projects/xul/xre(old)?\.html$',
|
redirect(r'^projects/xul/xre(old)?\.html$',
|
||||||
'https://developer.mozilla.org/docs/Archive/Mozilla/XULRunner'),
|
'https://developer.mozilla.org/docs/Archive/Mozilla/XULRunner'),
|
||||||
redirect('^projects/xslt/js-interface\.html$',
|
redirect(r'^projects/xslt/js-interface\.html$',
|
||||||
'https://developer.mozilla.org/docs/'
|
'https://developer.mozilla.org/docs/'
|
||||||
'Web/XSLT/Using_the_Mozilla_JavaScript_interface_to_XSL_Transformations'),
|
'Web/XSLT/Using_the_Mozilla_JavaScript_interface_to_XSL_Transformations'),
|
||||||
redirect('^projects/xslt/faq\.html$',
|
redirect(r'^projects/xslt/faq\.html$',
|
||||||
'https://developer.mozilla.org/docs/'
|
'https://developer.mozilla.org/docs/'
|
||||||
'Web/API/XSLTProcessor/XSL_Transformations_in_Mozilla_FAQ'),
|
'Web/API/XSLTProcessor/XSL_Transformations_in_Mozilla_FAQ'),
|
||||||
redirect('^projects/xslt/standalone\.html$',
|
redirect(r'^projects/xslt/standalone\.html$',
|
||||||
'https://developer.mozilla.org/docs/'
|
'https://developer.mozilla.org/docs/'
|
||||||
'Archive/Mozilla/Building_TransforMiiX_standalone'),
|
'Archive/Mozilla/Building_TransforMiiX_standalone'),
|
||||||
redirect('^projects/plugins/first-install-problem\.html$',
|
redirect(r'^projects/plugins/first-install-problem\.html$',
|
||||||
'https://developer.mozilla.org/Add-ons/Plugins/The_First_Install_Problem'),
|
'https://developer.mozilla.org/Add-ons/Plugins/The_First_Install_Problem'),
|
||||||
redirect('^projects/plugins/install-scheme\.html$',
|
redirect(r'^projects/plugins/install-scheme\.html$',
|
||||||
'https://developer.mozilla.org/docs/'
|
'https://developer.mozilla.org/docs/'
|
||||||
'Installing_plugins_to_Gecko_embedding_browsers_on_Windows'),
|
'Installing_plugins_to_Gecko_embedding_browsers_on_Windows'),
|
||||||
redirect('^projects/plugins/npruntime-sample-in-visual-studio\.html$',
|
redirect(r'^projects/plugins/npruntime-sample-in-visual-studio\.html$',
|
||||||
'https://developer.mozilla.org/docs/'
|
'https://developer.mozilla.org/docs/'
|
||||||
'Compiling_The_npruntime_Sample_Plugin_in_Visual_Studio'),
|
'Compiling_The_npruntime_Sample_Plugin_in_Visual_Studio'),
|
||||||
redirect('^projects/plugins/npruntime\.html$',
|
redirect(r'^projects/plugins/npruntime\.html$',
|
||||||
'https://developer.mozilla.org/docs/Plugins/Guide/Scripting_plugins'),
|
'https://developer.mozilla.org/docs/Plugins/Guide/Scripting_plugins'),
|
||||||
redirect('^projects/plugins/plugin-host-control\.html$',
|
redirect(r'^projects/plugins/plugin-host-control\.html$',
|
||||||
'https://developer.mozilla.org/docs/'
|
'https://developer.mozilla.org/docs/'
|
||||||
'Archive/Mozilla/ActiveX_Control_for_Hosting_Netscape_Plug-ins_in_IE'),
|
'Archive/Mozilla/ActiveX_Control_for_Hosting_Netscape_Plug-ins_in_IE'),
|
||||||
redirect('^projects/plugins/xembed-plugin-extension\.html$',
|
redirect(r'^projects/plugins/xembed-plugin-extension\.html$',
|
||||||
'https://developer.mozilla.org/Add-ons/Plugins/XEmbed_Extension_for_Mozilla_Plugins'),
|
'https://developer.mozilla.org/Add-ons/Plugins/XEmbed_Extension_for_Mozilla_Plugins'),
|
||||||
redirect('^projects/netlib/http/http-debugging\.html$',
|
redirect(r'^projects/netlib/http/http-debugging\.html$',
|
||||||
'https://developer.mozilla.org/docs/Mozilla/Debugging/HTTP_logging'),
|
'https://developer.mozilla.org/docs/Mozilla/Debugging/HTTP_logging'),
|
||||||
redirect('^projects/netlib/integrated-auth\.html$',
|
redirect(r'^projects/netlib/integrated-auth\.html$',
|
||||||
'https://developer.mozilla.org/docs/Mozilla/Integrated_authentication'),
|
'https://developer.mozilla.org/docs/Mozilla/Integrated_authentication'),
|
||||||
redirect('^projects/netlib/Link_Prefetching_FAQ\.html$',
|
redirect(r'^projects/netlib/Link_Prefetching_FAQ\.html$',
|
||||||
'https://developer.mozilla.org/docs/Web/HTTP/Link_prefetching_FAQ'),
|
'https://developer.mozilla.org/docs/Web/HTTP/Link_prefetching_FAQ'),
|
||||||
redirect(r'^projects/embedding/GRE\.html$',
|
redirect(r'^projects/embedding/GRE\.html$',
|
||||||
'https://developer.mozilla.org/docs/Archive/Mozilla/GRE'),
|
'https://developer.mozilla.org/docs/Archive/Mozilla/GRE'),
|
||||||
|
@ -450,42 +450,42 @@ redirectpatterns = (
|
||||||
# Bug 654614 /blocklist -> addons.m.o/blocked
|
# Bug 654614 /blocklist -> addons.m.o/blocked
|
||||||
redirect(r'^blocklist(/.*)?$', 'https://addons.mozilla.org/blocked/'),
|
redirect(r'^blocklist(/.*)?$', 'https://addons.mozilla.org/blocked/'),
|
||||||
|
|
||||||
redirect('^products/firebird$', 'firefox'),
|
redirect(r'^products/firebird$', 'firefox'),
|
||||||
redirect('^products/firebird/download/$', 'firefox.new'),
|
redirect(r'^products/firebird/download/$', 'firefox.new'),
|
||||||
redirect('^products/firefox/add-engines\.html$',
|
redirect(r'^products/firefox/add-engines\.html$',
|
||||||
'https://addons.mozilla.org/search-engines.php'),
|
'https://addons.mozilla.org/search-engines.php'),
|
||||||
redirect('^products/firefox/all$', '/firefox/all/'),
|
redirect(r'^products/firefox/all$', '/firefox/all/'),
|
||||||
redirect('^products/firefox/all\.html$', '/firefox/all/'),
|
redirect(r'^products/firefox/all\.html$', '/firefox/all/'),
|
||||||
redirect('^products/firefox/banners\.html$', '/contribute/friends/'),
|
redirect(r'^products/firefox/banners\.html$', '/contribute/friends/'),
|
||||||
redirect('^products/firefox/buttons\.html$', '/contribute/friends/'),
|
redirect(r'^products/firefox/buttons\.html$', '/contribute/friends/'),
|
||||||
redirect('^products/firefox/download', 'firefox.new'),
|
redirect(r'^products/firefox/download', 'firefox.new'),
|
||||||
redirect('^products/firefox/get$', 'firefox.new'),
|
redirect(r'^products/firefox/get$', 'firefox.new'),
|
||||||
redirect('^products/firefox/$', 'firefox'),
|
redirect(r'^products/firefox/$', 'firefox'),
|
||||||
redirect('^products/firefox/live-bookmarks', '/firefox/features/'),
|
redirect(r'^products/firefox/live-bookmarks', '/firefox/features/'),
|
||||||
redirect('^products/firefox/mirrors\.html$', 'http://www-archive.mozilla.org/mirrors.html'),
|
redirect(r'^products/firefox/mirrors\.html$', 'http://www-archive.mozilla.org/mirrors.html'),
|
||||||
redirect('^products/firefox/releases/$', '/firefox/releases/'),
|
redirect(r'^products/firefox/releases/$', '/firefox/releases/'),
|
||||||
redirect('^products/firefox/releases/0\.9\.2\.html$',
|
redirect(r'^products/firefox/releases/0\.9\.2\.html$',
|
||||||
'http://website-archive.mozilla.org/www.mozilla.org/firefox_releasenotes'
|
'http://website-archive.mozilla.org/www.mozilla.org/firefox_releasenotes'
|
||||||
'/en-US/firefox/releases/0.9.1.html'),
|
'/en-US/firefox/releases/0.9.1.html'),
|
||||||
redirect('^products/firefox/releases/0\.10\.1\.html$',
|
redirect(r'^products/firefox/releases/0\.10\.1\.html$',
|
||||||
'http://website-archive.mozilla.org/www.mozilla.org/firefox_releasenotes'
|
'http://website-archive.mozilla.org/www.mozilla.org/firefox_releasenotes'
|
||||||
'/en-US/firefox/releases/0.10.html'),
|
'/en-US/firefox/releases/0.10.html'),
|
||||||
redirect('^products/firefox/search', '/firefox/features/'),
|
redirect(r'^products/firefox/search', '/firefox/features/'),
|
||||||
redirect('^products/firefox/shelf\.html$', 'https://blog.mozilla.org/press/awards/'),
|
redirect(r'^products/firefox/shelf\.html$', 'https://blog.mozilla.org/press/awards/'),
|
||||||
redirect('^products/firefox/smart-keywords\.html$',
|
redirect(r'^products/firefox/smart-keywords\.html$',
|
||||||
'https://support.mozilla.org/en-US/kb/Smart+keywords'),
|
'https://support.mozilla.org/en-US/kb/Smart+keywords'),
|
||||||
redirect('^products/firefox/support/$', 'https://support.mozilla.org/'),
|
redirect(r'^products/firefox/support/$', 'https://support.mozilla.org/'),
|
||||||
redirect('^products/firefox/switch', 'firefox.new'),
|
redirect(r'^products/firefox/switch', 'firefox.new'),
|
||||||
redirect('^products/firefox/system-requirements', '/firefox/system-requirements/'),
|
redirect(r'^products/firefox/system-requirements', '/firefox/system-requirements/'),
|
||||||
redirect('^products/firefox/tabbed-browsing', 'firefox'),
|
redirect(r'^products/firefox/tabbed-browsing', 'firefox'),
|
||||||
redirect('^products/firefox/text-zoom\.html$',
|
redirect(r'^products/firefox/text-zoom\.html$',
|
||||||
'https://support.mozilla.org/kb/font-size-and-zoom-increase-size-of-web-pages'),
|
'https://support.mozilla.org/kb/font-size-and-zoom-increase-size-of-web-pages'),
|
||||||
redirect('^products/firefox/themes$', 'https://addons.mozilla.org/themes/'),
|
redirect(r'^products/firefox/themes$', 'https://addons.mozilla.org/themes/'),
|
||||||
redirect('^products/firefox/themes\.html$', 'https://addons.mozilla.org/themes/'),
|
redirect(r'^products/firefox/themes\.html$', 'https://addons.mozilla.org/themes/'),
|
||||||
redirect('^products/firefox/ui-customize\.html$',
|
redirect(r'^products/firefox/ui-customize\.html$',
|
||||||
'https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars'),
|
'https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars'),
|
||||||
redirect('^products/firefox/upgrade', 'firefox.new'),
|
redirect(r'^products/firefox/upgrade', 'firefox.new'),
|
||||||
redirect('^products/firefox/why/$', 'firefox'),
|
redirect(r'^products/firefox/why/$', 'firefox'),
|
||||||
|
|
||||||
# bug 857246 redirect /products/firefox/start/ to start.mozilla.org
|
# bug 857246 redirect /products/firefox/start/ to start.mozilla.org
|
||||||
redirect(r'^products/firefox/start/?$', 'http://start.mozilla.org'),
|
redirect(r'^products/firefox/start/?$', 'http://start.mozilla.org'),
|
||||||
|
@ -549,7 +549,7 @@ redirectpatterns = (
|
||||||
redirect(r'^firefox/(new/)?addon', 'https://addons.mozilla.org'),
|
redirect(r'^firefox/(new/)?addon', 'https://addons.mozilla.org'),
|
||||||
redirect(r'^firefox/tips', '/firefox/features/'),
|
redirect(r'^firefox/tips', '/firefox/features/'),
|
||||||
redirect(r'^firefox/new/.+', '/firefox/new/'),
|
redirect(r'^firefox/new/.+', '/firefox/new/'),
|
||||||
redirect(r'^firefox/38.0.3/releasenotes/$', '/firefox/38.0.5/releasenotes/'),
|
redirect(r'^firefox/38\.0\.3/releasenotes/$', '/firefox/38.0.5/releasenotes/'),
|
||||||
redirect(r'^firefox/default\.htm', '/firefox/'),
|
redirect(r'^firefox/default\.htm', '/firefox/'),
|
||||||
redirect(r'^firefox/android/(?P<version>\d+\.\d+(?:\.\d+)?)$', '/firefox/android/{version}/releasenotes/'),
|
redirect(r'^firefox/android/(?P<version>\d+\.\d+(?:\.\d+)?)$', '/firefox/android/{version}/releasenotes/'),
|
||||||
redirect(r'^firefox/stats/', '/firefox/'),
|
redirect(r'^firefox/stats/', '/firefox/'),
|
||||||
|
@ -569,9 +569,11 @@ redirectpatterns = (
|
||||||
redirect(r'^firefox/organizations/faq/?$', 'firefox.organizations.organizations'),
|
redirect(r'^firefox/organizations/faq/?$', 'firefox.organizations.organizations'),
|
||||||
|
|
||||||
# bug 1425865 - Amazon Fire TV goes to SUMO until we have a product page.
|
# bug 1425865 - Amazon Fire TV goes to SUMO until we have a product page.
|
||||||
redirect(r'^firefox/fire-tv/?$',
|
redirect(
|
||||||
|
r'^firefox/fire-tv/?$',
|
||||||
'https://support.mozilla.org/products/firefox-fire-tv/',
|
'https://support.mozilla.org/products/firefox-fire-tv/',
|
||||||
permanent=False),
|
permanent=False,
|
||||||
|
),
|
||||||
|
|
||||||
# bug 1430894
|
# bug 1430894
|
||||||
redirect(r'^firefox/interest-dashboard/?', 'https://support.mozilla.org/kb/firefox-add-technology-modernizing'),
|
redirect(r'^firefox/interest-dashboard/?', 'https://support.mozilla.org/kb/firefox-add-technology-modernizing'),
|
||||||
|
|
|
@ -15,8 +15,7 @@ from pyquery import PyQuery as pq
|
||||||
|
|
||||||
from bedrock.base.urlresolvers import reverse
|
from bedrock.base.urlresolvers import reverse
|
||||||
from bedrock.firefox import views as fx_views
|
from bedrock.firefox import views as fx_views
|
||||||
from bedrock.firefox.firefox_details import FirefoxDesktop, FirefoxAndroid, FirefoxIOS
|
from bedrock.firefox.firefox_details import FirefoxDesktop, FirefoxAndroid
|
||||||
from bedrock.firefox.utils import product_details
|
|
||||||
from bedrock.mozorg.tests import TestCase
|
from bedrock.mozorg.tests import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,10 +24,6 @@ PROD_DETAILS_DIR = os.path.join(TEST_DATA_DIR, 'product_details_json')
|
||||||
GOOD_PLATS = {'Windows': {}, 'OS X': {}, 'Linux': {}}
|
GOOD_PLATS = {'Windows': {}, 'OS X': {}, 'Linux': {}}
|
||||||
jinja_env = Jinja2.get_default().env
|
jinja_env = Jinja2.get_default().env
|
||||||
|
|
||||||
firefox_desktop = FirefoxDesktop(json_dir=PROD_DETAILS_DIR)
|
|
||||||
firefox_android = FirefoxAndroid(json_dir=PROD_DETAILS_DIR)
|
|
||||||
firefox_ios = FirefoxIOS(json_dir=PROD_DETAILS_DIR)
|
|
||||||
|
|
||||||
|
|
||||||
class TestInstallerHelp(TestCase):
|
class TestInstallerHelp(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -94,12 +89,19 @@ class TestInstallerHelp(TestCase):
|
||||||
locale=None)
|
locale=None)
|
||||||
|
|
||||||
|
|
||||||
@patch.object(fx_views, 'firefox_desktop', firefox_desktop)
|
|
||||||
class TestFirefoxAll(TestCase):
|
class TestFirefoxAll(TestCase):
|
||||||
pd_cache = caches['product-details']
|
pd_cache = caches['product-details']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.pd_cache.clear()
|
self.pd_cache.clear()
|
||||||
|
self.firefox_desktop = FirefoxDesktop(json_dir=PROD_DETAILS_DIR)
|
||||||
|
self.firefox_android = FirefoxAndroid(json_dir=PROD_DETAILS_DIR)
|
||||||
|
self.patcher = patch.object(
|
||||||
|
fx_views, 'firefox_desktop', self.firefox_desktop)
|
||||||
|
self.patcher.start()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.patcher.stop()
|
||||||
|
|
||||||
def _get_url(self, platform='desktop', channel='release'):
|
def _get_url(self, platform='desktop', channel='release'):
|
||||||
with self.activate('en-US'):
|
with self.activate('en-US'):
|
||||||
|
@ -121,35 +123,35 @@ class TestFirefoxAll(TestCase):
|
||||||
doc = pq(resp.content)
|
doc = pq(resp.content)
|
||||||
assert len(doc('.c-all-downloads-build')) == 8
|
assert len(doc('.c-all-downloads-build')) == 8
|
||||||
|
|
||||||
desktop_release_builds = len(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')) == desktop_release_builds
|
||||||
assert len(doc('.c-locale-list[data-product="desktop_release"] > li[data-language="en-US"] > ul > li > a')) == 7
|
assert len(doc('.c-locale-list[data-product="desktop_release"] > li[data-language="en-US"] > ul > li > a')) == 7
|
||||||
|
|
||||||
desktop_beta_builds = len(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')) == desktop_beta_builds
|
||||||
assert len(doc('.c-locale-list[data-product="desktop_beta"] > li[data-language="en-US"] > ul > li > a')) == 7
|
assert len(doc('.c-locale-list[data-product="desktop_beta"] > li[data-language="en-US"] > ul > li > a')) == 7
|
||||||
|
|
||||||
desktop_developer_builds = len(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')) == desktop_developer_builds
|
||||||
assert len(doc('.c-locale-list[data-product="desktop_developer"] > li[data-language="en-US"] > ul > li > a')) == 7
|
assert len(doc('.c-locale-list[data-product="desktop_developer"] > li[data-language="en-US"] > ul > li > a')) == 7
|
||||||
|
|
||||||
desktop_nightly_builds = len(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')) == desktop_nightly_builds
|
||||||
assert len(doc('.c-locale-list[data-product="desktop_nightly"] > li[data-language="en-US"] > ul > li > a')) == 7
|
assert len(doc('.c-locale-list[data-product="desktop_nightly"] > li[data-language="en-US"] > ul > li > a')) == 7
|
||||||
|
|
||||||
desktop_esr_builds = len(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')) == desktop_esr_builds
|
||||||
assert len(doc('.c-locale-list[data-product="desktop_esr"] > li[data-language="en-US"] > ul > li > a')) == 5
|
assert len(doc('.c-locale-list[data-product="desktop_esr"] > li[data-language="en-US"] > ul > li > a')) == 5
|
||||||
|
|
||||||
android_release_builds = len(firefox_android.get_filtered_full_builds('release'))
|
android_release_builds = len(self.firefox_android.get_filtered_full_builds('release'))
|
||||||
assert len(doc('.c-locale-list[data-product="android_release"] > li')) == android_release_builds
|
assert len(doc('.c-locale-list[data-product="android_release"] > li')) == android_release_builds
|
||||||
assert len(doc('.c-locale-list[data-product="android_release"] > li[data-language="multi"] > ul > li > a')) == 2
|
assert len(doc('.c-locale-list[data-product="android_release"] > li[data-language="multi"] > ul > li > a')) == 2
|
||||||
|
|
||||||
android_beta_builds = len(firefox_android.get_filtered_full_builds('beta'))
|
android_beta_builds = len(self.firefox_android.get_filtered_full_builds('beta'))
|
||||||
assert len(doc('.c-locale-list[data-product="android_beta"] > li')) == android_beta_builds
|
assert len(doc('.c-locale-list[data-product="android_beta"] > li')) == android_beta_builds
|
||||||
assert len(doc('.c-locale-list[data-product="android_beta"] > li[data-language="multi"] > ul > li > a')) == 2
|
assert len(doc('.c-locale-list[data-product="android_beta"] > li[data-language="multi"] > ul > li > a')) == 2
|
||||||
|
|
||||||
android_nightly_builds = len(firefox_android.get_filtered_full_builds('nightly'))
|
android_nightly_builds = len(self.firefox_android.get_filtered_full_builds('nightly'))
|
||||||
assert len(doc('.c-locale-list[data-product="android_nightly"] > li')) == android_nightly_builds
|
assert len(doc('.c-locale-list[data-product="android_nightly"] > li')) == android_nightly_builds
|
||||||
assert len(doc('.c-locale-list[data-product="android_nightly"] > li[data-language="multi"] > ul > li > a')) == 2
|
assert len(doc('.c-locale-list[data-product="android_nightly"] > li[data-language="multi"] > ul > li > a')) == 2
|
||||||
|
|
||||||
|
@ -174,8 +176,10 @@ class TestFirefoxAll(TestCase):
|
||||||
assert len(doc('.build-table')) == 1
|
assert len(doc('.build-table')) == 1
|
||||||
assert len(doc('.not-found.hide')) == 1
|
assert len(doc('.not-found.hide')) == 1
|
||||||
|
|
||||||
num_builds = len(firefox_desktop.get_filtered_full_builds('release'))
|
num_builds = len(
|
||||||
num_builds += len(firefox_desktop.get_filtered_test_builds('release'))
|
self.firefox_desktop.get_filtered_full_builds('release'))
|
||||||
|
num_builds += len(
|
||||||
|
self.firefox_desktop.get_filtered_test_builds('release'))
|
||||||
assert len(doc('tr[data-search]')) == num_builds
|
assert len(doc('tr[data-search]')) == num_builds
|
||||||
assert len(doc('tr#en-US a')) == 7
|
assert len(doc('tr#en-US a')) == 7
|
||||||
|
|
||||||
|
@ -185,9 +189,9 @@ class TestFirefoxAll(TestCase):
|
||||||
locale details are not updated yet, the filtered build list should not
|
locale details are not updated yet, the filtered build list should not
|
||||||
include the localized build.
|
include the localized build.
|
||||||
"""
|
"""
|
||||||
builds = firefox_desktop.get_filtered_full_builds('release')
|
builds = self.firefox_desktop.get_filtered_full_builds('release')
|
||||||
assert 'uz' in firefox_desktop.firefox_primary_builds
|
assert 'uz' in self.firefox_desktop.firefox_primary_builds
|
||||||
assert 'uz' not in firefox_desktop.languages
|
assert 'uz' not in self.firefox_desktop.languages
|
||||||
assert len([build for build in builds if build['locale'] == 'uz']) == 0
|
assert len([build for build in builds if build['locale'] == 'uz']) == 0
|
||||||
|
|
||||||
def test_android(self):
|
def test_android(self):
|
||||||
|
@ -548,93 +552,6 @@ class TestFirstRun(TestCase):
|
||||||
assert resp['location'].endswith('/firefox/new/')
|
assert resp['location'].endswith('/firefox/new/')
|
||||||
|
|
||||||
|
|
||||||
@patch.object(fx_views, 'firefox_desktop', firefox_desktop)
|
|
||||||
class FxVersionRedirectsMixin(object):
|
|
||||||
@override_settings(DEV=True) # avoid https redirects
|
|
||||||
def assert_ua_redirects_to(self, ua, url_name, status_code=301):
|
|
||||||
response = self.client.get(self.url, HTTP_USER_AGENT=ua)
|
|
||||||
assert response.status_code == status_code
|
|
||||||
assert response['Cache-Control'] == 'max-age=0'
|
|
||||||
assert response['Location'] == reverse(url_name)
|
|
||||||
|
|
||||||
# An additional redirect test with a query string
|
|
||||||
query = '?ref=getfirefox'
|
|
||||||
response = self.client.get(self.url + query, HTTP_USER_AGENT=ua)
|
|
||||||
assert response.status_code == status_code
|
|
||||||
assert response['Cache-Control'] == 'max-age=0'
|
|
||||||
assert response['Location'] == reverse(url_name) + query
|
|
||||||
|
|
||||||
def test_non_firefox(self):
|
|
||||||
"""
|
|
||||||
Any non-Firefox user agents should be permanently redirected to
|
|
||||||
/firefox/new/.
|
|
||||||
"""
|
|
||||||
user_agent = 'random'
|
|
||||||
self.assert_ua_redirects_to(user_agent, 'firefox.new')
|
|
||||||
|
|
||||||
@override_settings(DEV=True)
|
|
||||||
@patch.dict(product_details.firefox_versions,
|
|
||||||
LATEST_FIREFOX_VERSION='13.0.5')
|
|
||||||
@patch('bedrock.firefox.firefox_details.firefox_desktop.latest_builds',
|
|
||||||
return_value=('13.0.5', GOOD_PLATS))
|
|
||||||
def test_current_minor_version_firefox(self, latest_mock):
|
|
||||||
"""
|
|
||||||
Should show current even if behind by a patch version
|
|
||||||
"""
|
|
||||||
user_agent = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:13.0) '
|
|
||||||
'Gecko/20100101 Firefox/13.0')
|
|
||||||
response = self.client.get(self.url, HTTP_USER_AGENT=user_agent)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response['Cache-Control'] == 'max-age=0'
|
|
||||||
|
|
||||||
@override_settings(DEV=True)
|
|
||||||
@patch.dict(product_details.firefox_versions,
|
|
||||||
LATEST_FIREFOX_VERSION='25.0',
|
|
||||||
FIREFOX_ESR='24.1')
|
|
||||||
@patch('bedrock.firefox.firefox_details.firefox_desktop.latest_builds',
|
|
||||||
return_value=('25.0', GOOD_PLATS))
|
|
||||||
def test_esr_firefox(self, latest_mock):
|
|
||||||
"""
|
|
||||||
Currently released ESR firefoxen should not redirect. At present
|
|
||||||
that is 24.0.x.
|
|
||||||
"""
|
|
||||||
user_agent = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:24.0) '
|
|
||||||
'Gecko/20100101 Firefox/24.0')
|
|
||||||
response = self.client.get(self.url, HTTP_USER_AGENT=user_agent)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response['Cache-Control'] == 'max-age=0'
|
|
||||||
|
|
||||||
@override_settings(DEV=True)
|
|
||||||
@patch.dict(product_details.firefox_versions,
|
|
||||||
LATEST_FIREFOX_VERSION='16.0')
|
|
||||||
@patch('bedrock.firefox.firefox_details.firefox_desktop.latest_builds',
|
|
||||||
return_value=('16.0', GOOD_PLATS))
|
|
||||||
def test_current_firefox(self, latest_mock):
|
|
||||||
"""
|
|
||||||
Currently released firefoxen should not redirect.
|
|
||||||
"""
|
|
||||||
user_agent = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:16.0) '
|
|
||||||
'Gecko/20100101 Firefox/16.0')
|
|
||||||
response = self.client.get(self.url, HTTP_USER_AGENT=user_agent)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response['Cache-Control'] == 'max-age=0'
|
|
||||||
|
|
||||||
@override_settings(DEV=True)
|
|
||||||
@patch.dict(product_details.firefox_versions,
|
|
||||||
LATEST_FIREFOX_VERSION='16.0')
|
|
||||||
@patch('bedrock.firefox.firefox_details.firefox_desktop.latest_builds',
|
|
||||||
return_value=('16.0', GOOD_PLATS))
|
|
||||||
def test_future_firefox(self, latest_mock):
|
|
||||||
"""
|
|
||||||
Pre-release firefoxen should not redirect.
|
|
||||||
"""
|
|
||||||
user_agent = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:18.0) '
|
|
||||||
'Gecko/20100101 Firefox/18.0')
|
|
||||||
response = self.client.get(self.url, HTTP_USER_AGENT=user_agent)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response['Cache-Control'] == 'max-age=0'
|
|
||||||
|
|
||||||
|
|
||||||
@patch('bedrock.firefox.views.l10n_utils.render', return_value=HttpResponse())
|
@patch('bedrock.firefox.views.l10n_utils.render', return_value=HttpResponse())
|
||||||
class TestTrackingProtectionTour(TestCase):
|
class TestTrackingProtectionTour(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,4 +1,4 @@
|
||||||
from urlparse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
@ -6,7 +6,6 @@ from django.test.client import RequestFactory
|
||||||
from django_jinja.backend import Jinja2
|
from django_jinja.backend import Jinja2
|
||||||
from pyquery import PyQuery as pq
|
from pyquery import PyQuery as pq
|
||||||
|
|
||||||
from product_details import product_details
|
|
||||||
from bedrock.mozorg.tests import TestCase
|
from bedrock.mozorg.tests import TestCase
|
||||||
|
|
||||||
jinja_env = Jinja2.get_default()
|
jinja_env = Jinja2.get_default()
|
||||||
|
@ -20,6 +19,7 @@ def render(s, context=None):
|
||||||
class TestDownloadButtons(TestCase):
|
class TestDownloadButtons(TestCase):
|
||||||
|
|
||||||
def latest_version(self):
|
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):
|
def check_desktop_links(self, links):
|
||||||
|
@ -46,8 +46,10 @@ class TestDownloadButtons(TestCase):
|
||||||
|
|
||||||
# Check that the rest of the links are Android and iOS
|
# 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[4]).attr('href') == settings.GOOGLE_PLAY_FIREFOX_LINK_UTMS
|
||||||
assert (pq(links[5]).attr('href') ==
|
assert (
|
||||||
settings.APPLE_APPSTORE_FIREFOX_LINK.replace('/{country}/', '/'))
|
pq(links[5]).attr('href') ==
|
||||||
|
settings.APPLE_APPSTORE_FIREFOX_LINK.replace('/{country}/', '/')
|
||||||
|
)
|
||||||
|
|
||||||
def test_button_force_direct(self):
|
def test_button_force_direct(self):
|
||||||
"""
|
"""
|
||||||
|
@ -289,6 +291,7 @@ class TestDownloadButtons(TestCase):
|
||||||
class TestDownloadList(TestCase):
|
class TestDownloadList(TestCase):
|
||||||
|
|
||||||
def latest_version(self):
|
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):
|
def check_desktop_links(self, links):
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -3,12 +3,11 @@
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from bedrock.mozorg.util import page
|
|
||||||
|
|
||||||
import views
|
|
||||||
import bedrock.releasenotes.views
|
import bedrock.releasenotes.views
|
||||||
|
from bedrock.mozorg.util import page
|
||||||
from bedrock.releasenotes import version_re
|
from bedrock.releasenotes import version_re
|
||||||
|
|
||||||
|
from bedrock.firefox import views
|
||||||
|
|
||||||
latest_re = r'^firefox(?:/(?P<version>%s))?/%s/$'
|
latest_re = r'^firefox(?:/(?P<version>%s))?/%s/$'
|
||||||
firstrun_re = latest_re % (version_re, 'firstrun')
|
firstrun_re = latest_re % (version_re, 'firstrun')
|
||||||
|
@ -18,11 +17,11 @@ content_blocking_re = latest_re % (version_re, 'content-blocking/start')
|
||||||
platform_re = '(?P<platform>android|ios)'
|
platform_re = '(?P<platform>android|ios)'
|
||||||
channel_re = '(?P<channel>beta|aurora|developer|nightly|organizations)'
|
channel_re = '(?P<channel>beta|aurora|developer|nightly|organizations)'
|
||||||
releasenotes_re = latest_re % (version_re, r'(aurora|release)notes')
|
releasenotes_re = latest_re % (version_re, r'(aurora|release)notes')
|
||||||
android_releasenotes_re = releasenotes_re.replace('firefox', 'firefox/android')
|
android_releasenotes_re = releasenotes_re.replace(r'firefox', 'firefox/android')
|
||||||
ios_releasenotes_re = releasenotes_re.replace('firefox', 'firefox/ios')
|
ios_releasenotes_re = releasenotes_re.replace(r'firefox', 'firefox/ios')
|
||||||
sysreq_re = latest_re % (version_re, 'system-requirements')
|
sysreq_re = latest_re % (version_re, 'system-requirements')
|
||||||
android_sysreq_re = sysreq_re.replace('firefox', 'firefox/android')
|
android_sysreq_re = sysreq_re.replace(r'firefox', 'firefox/android')
|
||||||
ios_sysreq_re = sysreq_re.replace('firefox', 'firefox/ios')
|
ios_sysreq_re = sysreq_re.replace(r'firefox', 'firefox/ios')
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = (
|
urlpatterns = (
|
||||||
|
|
|
@ -7,33 +7,35 @@ import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from urlparse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.http import Http404, HttpResponseRedirect, HttpResponsePermanentRedirect
|
|
||||||
from django.utils.cache import patch_response_headers
|
|
||||||
from django.utils.encoding import force_text
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.views.decorators.http import require_POST, require_GET
|
|
||||||
from django.views.generic.base import TemplateView
|
|
||||||
|
|
||||||
import basket
|
import basket
|
||||||
import querystringsafe_base64
|
import querystringsafe_base64
|
||||||
from product_details.version_compare import Version
|
from django.conf import settings
|
||||||
|
from django.http import (
|
||||||
|
Http404,
|
||||||
|
HttpResponsePermanentRedirect,
|
||||||
|
HttpResponseRedirect,
|
||||||
|
JsonResponse,
|
||||||
|
)
|
||||||
|
from django.utils.cache import patch_response_headers
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.views.decorators.http import require_GET, require_POST
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
from lib import l10n_utils
|
from lib import l10n_utils
|
||||||
from lib.l10n_utils.dotlang import lang_file_is_active
|
from lib.l10n_utils.dotlang import lang_file_is_active
|
||||||
|
from product_details.version_compare import Version
|
||||||
|
|
||||||
from bedrock.base.urlresolvers import reverse
|
from bedrock.base.urlresolvers import reverse
|
||||||
from bedrock.base.waffle import switch
|
from bedrock.base.waffle import switch
|
||||||
from bedrock.contentcards.models import get_page_content_cards
|
from bedrock.contentcards.models import get_page_content_cards
|
||||||
from bedrock.firefox.firefox_details import firefox_desktop, firefox_android
|
from bedrock.firefox.firefox_details import firefox_android, firefox_desktop
|
||||||
from bedrock.firefox.forms import SendToDeviceWidgetForm
|
from bedrock.firefox.forms import SendToDeviceWidgetForm
|
||||||
from bedrock.mozorg.util import HttpResponseJSON
|
|
||||||
from bedrock.newsletter.forms import NewsletterFooterForm
|
from bedrock.newsletter.forms import NewsletterFooterForm
|
||||||
from bedrock.releasenotes import version_re
|
from bedrock.releasenotes import version_re
|
||||||
from bedrock.wordpress.views import BlogPostsView
|
from bedrock.wordpress.views import BlogPostsView
|
||||||
|
|
||||||
|
|
||||||
UA_REGEXP = re.compile(r"Firefox/(%s)" % version_re)
|
UA_REGEXP = re.compile(r"Firefox/(%s)" % version_re)
|
||||||
|
|
||||||
INSTALLER_CHANNElS = [
|
INSTALLER_CHANNElS = [
|
||||||
|
@ -57,10 +59,7 @@ STUB_VALUE_RE = re.compile(r'^[a-z0-9-.%():_]+$', flags=re.IGNORECASE)
|
||||||
def installer_help(request):
|
def installer_help(request):
|
||||||
installer_lang = request.GET.get('installer_lang', None)
|
installer_lang = request.GET.get('installer_lang', None)
|
||||||
installer_channel = request.GET.get('channel', None)
|
installer_channel = request.GET.get('channel', None)
|
||||||
context = {
|
context = {'installer_lang': None, 'installer_channel': None}
|
||||||
'installer_lang': None,
|
|
||||||
'installer_channel': None,
|
|
||||||
}
|
|
||||||
|
|
||||||
if installer_lang and installer_lang in firefox_desktop.languages:
|
if installer_lang and installer_lang in firefox_desktop.languages:
|
||||||
context['installer_lang'] = installer_lang
|
context['installer_lang'] = installer_lang
|
||||||
|
@ -75,14 +74,14 @@ def installer_help(request):
|
||||||
def stub_attribution_code(request):
|
def stub_attribution_code(request):
|
||||||
"""Return a JSON response containing the HMAC signed stub attribution value"""
|
"""Return a JSON response containing the HMAC signed stub attribution value"""
|
||||||
if not request.is_ajax():
|
if not request.is_ajax():
|
||||||
return HttpResponseJSON({'error': 'Resource only available via XHR'}, status=400)
|
return JsonResponse({'error': 'Resource only available via XHR'}, status=400)
|
||||||
|
|
||||||
response = None
|
response = None
|
||||||
if not settings.STUB_ATTRIBUTION_RATE:
|
if not settings.STUB_ATTRIBUTION_RATE:
|
||||||
# return as though it was rate limited, since it was
|
# return as though it was rate limited, since it was
|
||||||
response = HttpResponseJSON({'error': 'rate limited'}, status=429)
|
response = JsonResponse({'error': 'rate limited'}, status=429)
|
||||||
elif not settings.STUB_ATTRIBUTION_HMAC_KEY:
|
elif not settings.STUB_ATTRIBUTION_HMAC_KEY:
|
||||||
response = HttpResponseJSON({'error': 'service not configured'}, status=403)
|
response = JsonResponse({'error': 'service not configured'}, status=403)
|
||||||
|
|
||||||
if response:
|
if response:
|
||||||
patch_response_headers(response, 300) # 5 min
|
patch_response_headers(response, 300) # 5 min
|
||||||
|
@ -118,9 +117,9 @@ def stub_attribution_code(request):
|
||||||
|
|
||||||
code_data = sign_attribution_codes(codes)
|
code_data = sign_attribution_codes(codes)
|
||||||
if code_data:
|
if code_data:
|
||||||
response = HttpResponseJSON(code_data)
|
response = JsonResponse(code_data)
|
||||||
else:
|
else:
|
||||||
response = HttpResponseJSON({'error': 'Invalid code'}, status=400)
|
response = JsonResponse({'error': 'Invalid code'}, status=400)
|
||||||
|
|
||||||
patch_response_headers(response, 300) # 5 min
|
patch_response_headers(response, 300) # 5 min
|
||||||
return response
|
return response
|
||||||
|
@ -150,12 +149,9 @@ def sign_attribution_codes(codes):
|
||||||
if len(code) > settings.STUB_ATTRIBUTION_MAX_LEN:
|
if len(code) > settings.STUB_ATTRIBUTION_MAX_LEN:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
code = querystringsafe_base64.encode(code)
|
code = querystringsafe_base64.encode(code.encode())
|
||||||
sig = hmac.new(key, code, hashlib.sha256).hexdigest()
|
sig = hmac.new(key.encode(), code, hashlib.sha256).hexdigest()
|
||||||
return {
|
return {'attribution_code': code.decode(), 'attribution_sig': sig}
|
||||||
'attribution_code': code,
|
|
||||||
'attribution_sig': sig,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
|
@ -166,15 +162,13 @@ def send_to_device_ajax(request):
|
||||||
|
|
||||||
# ensure a value was entered in phone or email field
|
# ensure a value was entered in phone or email field
|
||||||
if not phone_or_email:
|
if not phone_or_email:
|
||||||
return HttpResponseJSON({'success': False, 'errors': ['phone-or-email']})
|
return JsonResponse({'success': False, 'errors': ['phone-or-email']})
|
||||||
|
|
||||||
# pull message set from POST (not part of form, so wont be in cleaned_data)
|
# pull message set from POST (not part of form, so wont be in cleaned_data)
|
||||||
message_set = request.POST.get('message-set', 'default')
|
message_set = request.POST.get('message-set', 'default')
|
||||||
|
|
||||||
# begin collecting data to pass to form constructor
|
# begin collecting data to pass to form constructor
|
||||||
data = {
|
data = {'platform': request.POST.get('platform')}
|
||||||
'platform': request.POST.get('platform'),
|
|
||||||
}
|
|
||||||
|
|
||||||
# determine if email or phone number was submitted
|
# determine if email or phone number was submitted
|
||||||
data_type = 'email' if '@' in phone_or_email else 'number'
|
data_type = 'email' if '@' in phone_or_email else 'number'
|
||||||
|
@ -214,32 +208,34 @@ def send_to_device_ajax(request):
|
||||||
basket.request('post', 'subscribe_sms', data=data)
|
basket.request('post', 'subscribe_sms', data=data)
|
||||||
except basket.BasketException as e:
|
except basket.BasketException as e:
|
||||||
if e.desc == 'mobile_number is invalid':
|
if e.desc == 'mobile_number is invalid':
|
||||||
return HttpResponseJSON({'success': False, 'errors': ['number']})
|
return JsonResponse({'success': False, 'errors': ['number']})
|
||||||
else:
|
else:
|
||||||
return HttpResponseJSON({'success': False, 'errors': ['system']},
|
return JsonResponse(
|
||||||
status=400)
|
{'success': False, 'errors': ['system']}, status=400
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return HttpResponseJSON({'success': False, 'errors': ['platform']})
|
return JsonResponse({'success': False, 'errors': ['platform']})
|
||||||
else: # email
|
else: # email
|
||||||
if platform in MESSAGES['email']:
|
if platform in MESSAGES['email']:
|
||||||
try:
|
try:
|
||||||
basket.subscribe(phone_or_email, MESSAGES['email'][platform],
|
basket.subscribe(
|
||||||
source_url=request.POST.get('source-url'),
|
phone_or_email,
|
||||||
lang=locale)
|
MESSAGES['email'][platform],
|
||||||
|
source_url=request.POST.get('source-url'),
|
||||||
|
lang=locale,
|
||||||
|
)
|
||||||
except basket.BasketException:
|
except basket.BasketException:
|
||||||
return HttpResponseJSON({'success': False, 'errors': ['system']},
|
return JsonResponse(
|
||||||
status=400)
|
{'success': False, 'errors': ['system']}, status=400
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return HttpResponseJSON({'success': False, 'errors': ['platform']})
|
return JsonResponse({'success': False, 'errors': ['platform']})
|
||||||
|
|
||||||
resp_data = {'success': True}
|
resp_data = {'success': True}
|
||||||
else:
|
else:
|
||||||
resp_data = {
|
resp_data = {'success': False, 'errors': list(form.errors)}
|
||||||
'success': False,
|
|
||||||
'errors': form.errors.keys(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return HttpResponseJSON(resp_data)
|
return JsonResponse(resp_data)
|
||||||
|
|
||||||
|
|
||||||
def firefox_all(request, platform, channel):
|
def firefox_all(request, platform, channel):
|
||||||
|
@ -247,7 +243,11 @@ def firefox_all(request, platform, channel):
|
||||||
|
|
||||||
# Only show the new template at /firefox/all/ until enough locales have it translated.
|
# Only show the new template at /firefox/all/ until enough locales have it translated.
|
||||||
# Once we no longer need the old template we can redirect the old URLs to the unified page.
|
# Once we no longer need the old template we can redirect the old URLs to the unified page.
|
||||||
if platform is None and channel is None and lang_file_is_active('firefox/all-unified', locale):
|
if (
|
||||||
|
platform is None
|
||||||
|
and channel is None
|
||||||
|
and lang_file_is_active('firefox/all-unified', locale)
|
||||||
|
):
|
||||||
return all_downloads_unified(request)
|
return all_downloads_unified(request)
|
||||||
|
|
||||||
return all_downloads(request, platform, channel)
|
return all_downloads(request, platform, channel)
|
||||||
|
@ -258,16 +258,18 @@ def all_downloads_unified(request):
|
||||||
product_desktop = firefox_desktop
|
product_desktop = firefox_desktop
|
||||||
|
|
||||||
# Human-readable product labels
|
# Human-readable product labels
|
||||||
products = OrderedDict([
|
products = OrderedDict(
|
||||||
('desktop_release', 'Firefox'),
|
[
|
||||||
('desktop_beta', 'Firefox Beta'),
|
('desktop_release', 'Firefox'),
|
||||||
('desktop_developer', 'Firefox Developer Edition'),
|
('desktop_beta', 'Firefox Beta'),
|
||||||
('desktop_nightly', 'Firefox Nightly'),
|
('desktop_developer', 'Firefox Developer Edition'),
|
||||||
('desktop_esr', 'Firefox Extended Support Release'),
|
('desktop_nightly', 'Firefox Nightly'),
|
||||||
('android_release', 'Firefox Android'),
|
('desktop_esr', 'Firefox Extended Support Release'),
|
||||||
('android_beta', 'Firefox Android Beta'),
|
('android_release', 'Firefox Android'),
|
||||||
('android_nightly', 'Firefox Android Nightly'),
|
('android_beta', 'Firefox Android Beta'),
|
||||||
])
|
('android_nightly', 'Firefox Android Nightly'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
channel_release = 'release'
|
channel_release = 'release'
|
||||||
channel_beta = 'beta'
|
channel_beta = 'beta'
|
||||||
|
@ -289,52 +291,84 @@ def all_downloads_unified(request):
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'products': products.items(),
|
'products': products.items(),
|
||||||
|
|
||||||
'desktop_release_platforms': product_desktop.platforms(channel_release),
|
'desktop_release_platforms': product_desktop.platforms(channel_release),
|
||||||
'desktop_release_full_builds': product_desktop.get_filtered_full_builds(channel_release, latest_release_version_desktop),
|
'desktop_release_full_builds': product_desktop.get_filtered_full_builds(
|
||||||
'desktop_release_channel_label': product_desktop.channel_labels.get(channel_release, 'Firefox'),
|
channel_release, latest_release_version_desktop
|
||||||
|
),
|
||||||
|
'desktop_release_channel_label': product_desktop.channel_labels.get(
|
||||||
|
channel_release, 'Firefox'
|
||||||
|
),
|
||||||
'desktop_release_latest_version': latest_release_version_desktop,
|
'desktop_release_latest_version': latest_release_version_desktop,
|
||||||
|
|
||||||
'desktop_beta_platforms': product_desktop.platforms(channel_beta),
|
'desktop_beta_platforms': product_desktop.platforms(channel_beta),
|
||||||
'desktop_beta_full_builds': product_desktop.get_filtered_full_builds(channel_beta, latest_beta_version_desktop),
|
'desktop_beta_full_builds': product_desktop.get_filtered_full_builds(
|
||||||
'desktop_beta_channel_label': product_desktop.channel_labels.get(channel_beta, 'Firefox'),
|
channel_beta, latest_beta_version_desktop
|
||||||
|
),
|
||||||
|
'desktop_beta_channel_label': product_desktop.channel_labels.get(
|
||||||
|
channel_beta, 'Firefox'
|
||||||
|
),
|
||||||
'desktop_beta_latest_version': latest_beta_version_desktop,
|
'desktop_beta_latest_version': latest_beta_version_desktop,
|
||||||
|
|
||||||
'desktop_developer_platforms': product_desktop.platforms(channel_dev),
|
'desktop_developer_platforms': product_desktop.platforms(channel_dev),
|
||||||
'desktop_developer_full_builds': product_desktop.get_filtered_full_builds(channel_dev, latest_developer_version_desktop),
|
'desktop_developer_full_builds': product_desktop.get_filtered_full_builds(
|
||||||
'desktop_developer_channel_label': product_desktop.channel_labels.get(channel_dev, 'Firefox'),
|
channel_dev, latest_developer_version_desktop
|
||||||
|
),
|
||||||
|
'desktop_developer_channel_label': product_desktop.channel_labels.get(
|
||||||
|
channel_dev, 'Firefox'
|
||||||
|
),
|
||||||
'desktop_developer_latest_version': latest_developer_version_desktop,
|
'desktop_developer_latest_version': latest_developer_version_desktop,
|
||||||
|
|
||||||
'desktop_nightly_platforms': product_desktop.platforms(channel_nightly),
|
'desktop_nightly_platforms': product_desktop.platforms(channel_nightly),
|
||||||
'desktop_nightly_full_builds': product_desktop.get_filtered_full_builds(channel_nightly, latest_nightly_version_desktop),
|
'desktop_nightly_full_builds': product_desktop.get_filtered_full_builds(
|
||||||
'desktop_nightly_channel_label': product_desktop.channel_labels.get(channel_nightly, 'Firefox'),
|
channel_nightly, latest_nightly_version_desktop
|
||||||
|
),
|
||||||
|
'desktop_nightly_channel_label': product_desktop.channel_labels.get(
|
||||||
|
channel_nightly, 'Firefox'
|
||||||
|
),
|
||||||
'desktop_nightly_latest_version': latest_nightly_version_desktop,
|
'desktop_nightly_latest_version': latest_nightly_version_desktop,
|
||||||
|
|
||||||
'desktop_esr_platforms': product_desktop.platforms(channel_esr),
|
'desktop_esr_platforms': product_desktop.platforms(channel_esr),
|
||||||
'desktop_esr_full_builds': product_desktop.get_filtered_full_builds(channel_esr, latest_esr_version_desktop),
|
'desktop_esr_full_builds': product_desktop.get_filtered_full_builds(
|
||||||
'desktop_esr_channel_label': product_desktop.channel_labels.get(channel_esr, 'Firefox'),
|
channel_esr, latest_esr_version_desktop
|
||||||
|
),
|
||||||
|
'desktop_esr_channel_label': product_desktop.channel_labels.get(
|
||||||
|
channel_esr, 'Firefox'
|
||||||
|
),
|
||||||
'desktop_esr_latest_version': latest_esr_version_desktop,
|
'desktop_esr_latest_version': latest_esr_version_desktop,
|
||||||
|
|
||||||
'android_release_platforms': product_android.platforms(channel_release),
|
'android_release_platforms': product_android.platforms(channel_release),
|
||||||
'android_release_full_builds': product_android.get_filtered_full_builds(channel_release, latest_release_version_android),
|
'android_release_full_builds': product_android.get_filtered_full_builds(
|
||||||
'android_release_channel_label': product_android.channel_labels.get(channel_release, 'Firefox'),
|
channel_release, latest_release_version_android
|
||||||
|
),
|
||||||
|
'android_release_channel_label': product_android.channel_labels.get(
|
||||||
|
channel_release, 'Firefox'
|
||||||
|
),
|
||||||
'android_release_latest_version': latest_release_version_android,
|
'android_release_latest_version': latest_release_version_android,
|
||||||
|
|
||||||
'android_beta_platforms': product_android.platforms(channel_beta),
|
'android_beta_platforms': product_android.platforms(channel_beta),
|
||||||
'android_beta_full_builds': product_android.get_filtered_full_builds(channel_beta, latest_beta_version_android),
|
'android_beta_full_builds': product_android.get_filtered_full_builds(
|
||||||
'android_beta_channel_label': product_android.channel_labels.get(channel_beta, 'Firefox'),
|
channel_beta, latest_beta_version_android
|
||||||
|
),
|
||||||
|
'android_beta_channel_label': product_android.channel_labels.get(
|
||||||
|
channel_beta, 'Firefox'
|
||||||
|
),
|
||||||
'android_beta_latest_version': latest_beta_version_android,
|
'android_beta_latest_version': latest_beta_version_android,
|
||||||
|
|
||||||
'android_nightly_platforms': product_android.platforms(channel_nightly),
|
'android_nightly_platforms': product_android.platforms(channel_nightly),
|
||||||
'android_nightly_full_builds': product_android.get_filtered_full_builds(channel_nightly, latest_nightly_version_android),
|
'android_nightly_full_builds': product_android.get_filtered_full_builds(
|
||||||
'android_nightly_channel_label': product_android.channel_labels.get(channel_nightly, 'Firefox'),
|
channel_nightly, latest_nightly_version_android
|
||||||
|
),
|
||||||
|
'android_nightly_channel_label': product_android.channel_labels.get(
|
||||||
|
channel_nightly, 'Firefox'
|
||||||
|
),
|
||||||
'android_nightly_latest_version': latest_nightly_version_android,
|
'android_nightly_latest_version': latest_nightly_version_android,
|
||||||
}
|
}
|
||||||
|
|
||||||
if latest_esr_next_version_desktop:
|
if latest_esr_next_version_desktop:
|
||||||
context['desktop_esr_platforms_next'] = product_desktop.platforms(channel_esr_next, True)
|
context['desktop_esr_platforms_next'] = product_desktop.platforms(
|
||||||
context['desktop_esr_full_builds_next'] = product_desktop.get_filtered_full_builds(channel_esr_next, latest_esr_next_version_desktop)
|
channel_esr_next, True
|
||||||
context['desktop_esr_channel_label_next'] = product_desktop.channel_labels.get(channel_esr_next, 'Firefox'),
|
)
|
||||||
|
context[
|
||||||
|
'desktop_esr_full_builds_next'
|
||||||
|
] = product_desktop.get_filtered_full_builds(
|
||||||
|
channel_esr_next, latest_esr_next_version_desktop
|
||||||
|
)
|
||||||
|
context['desktop_esr_channel_label_next'] = (
|
||||||
|
product_desktop.channel_labels.get(channel_esr_next, 'Firefox'),
|
||||||
|
)
|
||||||
context['desktop_esr_next_version'] = latest_esr_next_version_desktop
|
context['desktop_esr_next_version'] = latest_esr_next_version_desktop
|
||||||
|
|
||||||
return l10n_utils.render(request, 'firefox/all-unified.html', context)
|
return l10n_utils.render(request, 'firefox/all-unified.html', context)
|
||||||
|
@ -366,7 +400,8 @@ def all_downloads(request, platform, channel):
|
||||||
# Redirect /firefox/android/aurora/all/ to /firefox/android/nightly/all/
|
# Redirect /firefox/android/aurora/all/ to /firefox/android/nightly/all/
|
||||||
if platform == 'android' and channel == 'alpha':
|
if platform == 'android' and channel == 'alpha':
|
||||||
return HttpResponsePermanentRedirect(
|
return HttpResponsePermanentRedirect(
|
||||||
reverse('firefox.all', kwargs={'platform': 'android', 'channel': 'nightly'}))
|
reverse('firefox.all', kwargs={'platform': 'android', 'channel': 'nightly'})
|
||||||
|
)
|
||||||
|
|
||||||
version = product.latest_version(channel)
|
version = product.latest_version(channel)
|
||||||
query = request.GET.get('q')
|
query = request.GET.get('q')
|
||||||
|
@ -387,10 +422,12 @@ def all_downloads(request, platform, channel):
|
||||||
next_version = firefox_desktop.latest_version('esr_next')
|
next_version = firefox_desktop.latest_version('esr_next')
|
||||||
if next_version:
|
if next_version:
|
||||||
context['full_builds_next_version'] = next_version.split('.', 1)[0]
|
context['full_builds_next_version'] = next_version.split('.', 1)[0]
|
||||||
context['full_builds_next'] = firefox_desktop.get_filtered_full_builds('esr_next',
|
context['full_builds_next'] = firefox_desktop.get_filtered_full_builds(
|
||||||
next_version, query)
|
'esr_next', next_version, query
|
||||||
context['test_builds_next'] = firefox_desktop.get_filtered_test_builds('esr_next',
|
)
|
||||||
next_version, query)
|
context['test_builds_next'] = firefox_desktop.get_filtered_test_builds(
|
||||||
|
'esr_next', next_version, query
|
||||||
|
)
|
||||||
return l10n_utils.render(request, 'firefox/all.html', context)
|
return l10n_utils.render(request, 'firefox/all.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@ -491,8 +528,6 @@ class FirstrunView(l10n_utils.LangFilesMixin, TemplateView):
|
||||||
template = 'firefox/developer/firstrun.html'
|
template = 'firefox/developer/firstrun.html'
|
||||||
else:
|
else:
|
||||||
template = 'firefox/dev-firstrun.html'
|
template = 'firefox/dev-firstrun.html'
|
||||||
elif show_62_firstrun(version):
|
|
||||||
template = 'firefox/firstrun/firstrun-quantum.html'
|
|
||||||
else:
|
else:
|
||||||
template = 'firefox/firstrun/firstrun-quantum.html'
|
template = 'firefox/firstrun/firstrun-quantum.html'
|
||||||
|
|
||||||
|
@ -539,7 +574,7 @@ class WhatsnewView(l10n_utils.LangFilesMixin, TemplateView):
|
||||||
'fr',
|
'fr',
|
||||||
'ru',
|
'ru',
|
||||||
'de',
|
'de',
|
||||||
'pl'
|
'pl',
|
||||||
]
|
]
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
@ -599,12 +634,14 @@ class WhatsnewView(l10n_utils.LangFilesMixin, TemplateView):
|
||||||
|
|
||||||
class FeedbackView(TemplateView):
|
class FeedbackView(TemplateView):
|
||||||
|
|
||||||
donate_url = ('https://donate.mozilla.org/'
|
donate_url = (
|
||||||
'?utm_source=Heartbeat_survey&utm_medium=referral'
|
'https://donate.mozilla.org/'
|
||||||
'&utm_content=Heartbeat_{0}stars')
|
'?utm_source=Heartbeat_survey&utm_medium=referral'
|
||||||
|
'&utm_content=Heartbeat_{0}stars'
|
||||||
|
)
|
||||||
|
|
||||||
def get_score(self):
|
def get_score(self):
|
||||||
return self.request.GET.get('score', 0)
|
return self.request.GET.get('score', '0')
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
score = self.get_score()
|
score = self.get_score()
|
||||||
|
@ -626,12 +663,13 @@ class FeedbackView(TemplateView):
|
||||||
|
|
||||||
|
|
||||||
class TrackingProtectionTourView(l10n_utils.LangFilesMixin, TemplateView):
|
class TrackingProtectionTourView(l10n_utils.LangFilesMixin, TemplateView):
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
variation = self.request.GET.get('variation', None)
|
variation = self.request.GET.get('variation', None)
|
||||||
|
|
||||||
if variation in ['0', '1', '2']:
|
if variation in ['0', '1', '2']:
|
||||||
template = 'firefox/tracking-protection-tour/variation-{}.html'.format(variation)
|
template = 'firefox/tracking-protection-tour/variation-{}.html'.format(
|
||||||
|
variation
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
template = 'firefox/tracking-protection-tour/index.html'
|
template = 'firefox/tracking-protection-tour/index.html'
|
||||||
|
|
||||||
|
@ -639,12 +677,13 @@ class TrackingProtectionTourView(l10n_utils.LangFilesMixin, TemplateView):
|
||||||
|
|
||||||
|
|
||||||
class ContentBlockingTourView(l10n_utils.LangFilesMixin, TemplateView):
|
class ContentBlockingTourView(l10n_utils.LangFilesMixin, TemplateView):
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
variation = self.request.GET.get('variation', None)
|
variation = self.request.GET.get('variation', None)
|
||||||
|
|
||||||
if variation in ['2']:
|
if variation in ['2']:
|
||||||
template = 'firefox/content-blocking-tour/variation-{}.html'.format(variation)
|
template = 'firefox/content-blocking-tour/variation-{}.html'.format(
|
||||||
|
variation
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
template = 'firefox/content-blocking-tour/index.html'
|
template = 'firefox/content-blocking-tour/index.html'
|
||||||
|
|
||||||
|
@ -656,7 +695,21 @@ def download_thanks(request):
|
||||||
locale = l10n_utils.get_locale(request)
|
locale = l10n_utils.get_locale(request)
|
||||||
variant = request.GET.get('v', None)
|
variant = request.GET.get('v', None)
|
||||||
newsletter = request.GET.get('n', None)
|
newsletter = request.GET.get('n', None)
|
||||||
show_newsletter = locale in ['en-US', 'en-GB', 'en-CA', 'es-ES', 'es-AR', 'es-CL', 'es-MX', 'pt-BR', 'fr', 'ru', 'id', 'de', 'pl']
|
show_newsletter = locale in [
|
||||||
|
'en-US',
|
||||||
|
'en-GB',
|
||||||
|
'en-CA',
|
||||||
|
'es-ES',
|
||||||
|
'es-AR',
|
||||||
|
'es-CL',
|
||||||
|
'es-MX',
|
||||||
|
'pt-BR',
|
||||||
|
'fr',
|
||||||
|
'ru',
|
||||||
|
'id',
|
||||||
|
'de',
|
||||||
|
'pl',
|
||||||
|
]
|
||||||
|
|
||||||
# ensure variant matches pre-defined value
|
# ensure variant matches pre-defined value
|
||||||
if variant not in ['b']: # place expected ?v= values in this list
|
if variant not in ['b']: # place expected ?v= values in this list
|
||||||
|
@ -716,7 +769,9 @@ def new(request):
|
||||||
thanks_url = reverse('firefox.download.thanks')
|
thanks_url = reverse('firefox.download.thanks')
|
||||||
query_string = request.META.get('QUERY_STRING', '')
|
query_string = request.META.get('QUERY_STRING', '')
|
||||||
if query_string:
|
if query_string:
|
||||||
thanks_url = '?'.join([thanks_url, force_text(query_string, errors='ignore')])
|
thanks_url = '?'.join(
|
||||||
|
[thanks_url, force_text(query_string, errors='ignore')]
|
||||||
|
)
|
||||||
return HttpResponsePermanentRedirect(thanks_url)
|
return HttpResponsePermanentRedirect(thanks_url)
|
||||||
# if no/incorrect scene specified, show scene 1
|
# if no/incorrect scene specified, show scene 1
|
||||||
else:
|
else:
|
||||||
|
@ -729,7 +784,9 @@ def new(request):
|
||||||
|
|
||||||
# no harm done by passing 'v' to template, even when no experiment is running
|
# 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)
|
# (also makes tests easier to maintain by always sending a context)
|
||||||
return l10n_utils.render(request, template, {'experience': experience, 'v': variant})
|
return l10n_utils.render(
|
||||||
|
request, template, {'experience': experience, 'v': variant}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def campaign(request):
|
def campaign(request):
|
||||||
|
@ -779,16 +836,18 @@ def campaign(request):
|
||||||
|
|
||||||
# no harm done by passing 'v' to template, even when no experiment is running
|
# 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)
|
# (also makes tests easier to maintain by always sending a context)
|
||||||
return l10n_utils.render(request, template, {'experience': experience, 'v': variant})
|
return l10n_utils.render(
|
||||||
|
request, template, {'experience': experience, 'v': variant}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def ios_testflight(request):
|
def ios_testflight(request):
|
||||||
# no country field, so no need to send locale
|
# no country field, so no need to send locale
|
||||||
newsletter_form = NewsletterFooterForm('ios-beta-test-flight', '')
|
newsletter_form = NewsletterFooterForm('ios-beta-test-flight', '')
|
||||||
|
|
||||||
return l10n_utils.render(request,
|
return l10n_utils.render(
|
||||||
'firefox/testflight.html',
|
request, 'firefox/testflight.html', {'newsletter_form': newsletter_form}
|
||||||
{'newsletter_form': newsletter_form})
|
)
|
||||||
|
|
||||||
|
|
||||||
def ad_blocker(request):
|
def ad_blocker(request):
|
||||||
|
@ -847,10 +906,18 @@ def firefox_home(request):
|
||||||
locale = l10n_utils.get_locale(request)
|
locale = l10n_utils.get_locale(request)
|
||||||
variant = request.GET.get('v', None)
|
variant = request.GET.get('v', None)
|
||||||
newsletter_locales = ['en-US', 'en-GB', 'en-CA', 'en-ZA', 'fr', 'de']
|
newsletter_locales = ['en-US', 'en-GB', 'en-CA', 'en-ZA', 'fr', 'de']
|
||||||
show_newsletter = switch('firefox_pre_download_newsletter') and locale in newsletter_locales
|
show_newsletter = (
|
||||||
|
switch('firefox_pre_download_newsletter') and locale in newsletter_locales
|
||||||
|
)
|
||||||
|
|
||||||
# ensure variant matches pre-defined value
|
# ensure variant matches pre-defined value
|
||||||
if variant not in ['a', 'b', 'c', 'd', 'e']: # place expected ?v= values in this list
|
if variant not in [
|
||||||
|
'a',
|
||||||
|
'b',
|
||||||
|
'c',
|
||||||
|
'd',
|
||||||
|
'e',
|
||||||
|
]: # place expected ?v= values in this list
|
||||||
variant = None
|
variant = None
|
||||||
|
|
||||||
if locale == 'en-US' and variant is not None and variant != 'a':
|
if locale == 'en-US' and variant is not None and variant != 'a':
|
||||||
|
@ -858,9 +925,9 @@ def firefox_home(request):
|
||||||
else:
|
else:
|
||||||
template = 'firefox/home/index.html'
|
template = 'firefox/home/index.html'
|
||||||
|
|
||||||
return l10n_utils.render(request,
|
return l10n_utils.render(
|
||||||
template,
|
request, template, {'show_newsletter': show_newsletter, 'variation': variant}
|
||||||
{'show_newsletter': show_newsletter, 'variation': variant})
|
)
|
||||||
|
|
||||||
|
|
||||||
def firefox_concerts(request):
|
def firefox_concerts(request):
|
||||||
|
@ -881,13 +948,13 @@ def firefox_accounts(request):
|
||||||
locale = l10n_utils.get_locale(request)
|
locale = l10n_utils.get_locale(request)
|
||||||
|
|
||||||
# get localized blog post URL for 2019 page
|
# get localized blog post URL for 2019 page
|
||||||
promise_query = ('?utm_source=www.mozilla.org&utm_medium=referral&utm_campaign=accounts-trailhead'
|
promise_query = (
|
||||||
'&utm_content=accounts-value&utm_term=respect-you-deserve')
|
'?utm_source=www.mozilla.org&utm_medium=referral&utm_campaign=accounts-trailhead'
|
||||||
|
'&utm_content=accounts-value&utm_term=respect-you-deserve'
|
||||||
|
)
|
||||||
promise_url = PROMISE_BLOG_URLS.get(locale, PROMISE_BLOG_URLS['en-US'])
|
promise_url = PROMISE_BLOG_URLS.get(locale, PROMISE_BLOG_URLS['en-US'])
|
||||||
|
|
||||||
context = {
|
context = {'promise_url': promise_url + promise_query}
|
||||||
'promise_url': promise_url + promise_query,
|
|
||||||
}
|
|
||||||
|
|
||||||
if lang_file_is_active('firefox/accounts-2019', locale):
|
if lang_file_is_active('firefox/accounts-2019', locale):
|
||||||
template_name = 'firefox/accounts-2019.html'
|
template_name = 'firefox/accounts-2019.html'
|
||||||
|
@ -903,7 +970,7 @@ def election_with_cards(request):
|
||||||
locale = l10n_utils.get_locale(request)
|
locale = l10n_utils.get_locale(request)
|
||||||
ctx = {
|
ctx = {
|
||||||
'page_content_cards': get_page_content_cards('election-en', locale),
|
'page_content_cards': get_page_content_cards('election-en', locale),
|
||||||
'active_locales': ['de', 'fr', 'en-US']
|
'active_locales': ['de', 'fr', 'en-US'],
|
||||||
}
|
}
|
||||||
|
|
||||||
if locale == 'de':
|
if locale == 'de':
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
<h4>Filter by</h4>
|
<h4>Filter by</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{% for key, label in grant_labels.iteritems() %}
|
{% for key, label in grant_labels.items() %}
|
||||||
{% if key == filter %}
|
{% if key == filter %}
|
||||||
<li>{{ label }}</li>
|
<li>{{ label }}</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
@ -14,7 +14,7 @@ class TestGrants(TestCase):
|
||||||
def test_grant_url_slug(self):
|
def test_grant_url_slug(self):
|
||||||
"""Grant url slug must be composed of a-z, 0-9, _, and -."""
|
"""Grant url slug must be composed of a-z, 0-9, _, and -."""
|
||||||
for grant in GRANTS:
|
for grant in GRANTS:
|
||||||
self.assertTrue(re.match('^[a-z0-9_\-]+$', grant.url), "'%s' is not a valid url slug" % grant.url)
|
self.assertTrue(re.match(r'^[a-z0-9_\-]+$', grant.url), "'%s' is not a valid url slug" % grant.url)
|
||||||
|
|
||||||
def test_grant_grantee(self):
|
def test_grant_grantee(self):
|
||||||
"""Grant grantee must be a string."""
|
"""Grant grantee must be a string."""
|
||||||
|
@ -45,12 +45,12 @@ class TestGrants(TestCase):
|
||||||
def test_grant_total_support(self):
|
def test_grant_total_support(self):
|
||||||
"""Grant total_support must look like a monetary amount."""
|
"""Grant total_support must look like a monetary amount."""
|
||||||
for grant in GRANTS:
|
for grant in GRANTS:
|
||||||
self.assertTrue(re.match('^\$\d{1,3},\d{3}(\.\d{2})?$', grant.total_support), "'%s' is not a valid total_support" % grant.total_support)
|
self.assertTrue(re.match(r'^\$\d{1,3},\d{3}(\.\d{2})?$', grant.total_support), "'%s' is not a valid total_support" % grant.total_support)
|
||||||
|
|
||||||
def test_grant_year(self):
|
def test_grant_year(self):
|
||||||
"""Grant year must be in the range 2006 to next year."""
|
"""Grant year must be in the range 2006 to next year."""
|
||||||
next_year = date.today().year + 1
|
next_year = date.today().year + 1
|
||||||
valid_grant_years = range(2006, next_year)
|
valid_grant_years = list(range(2006, next_year))
|
||||||
for grant in GRANTS:
|
for grant in GRANTS:
|
||||||
self.assertIn(grant.year, valid_grant_years)
|
self.assertIn(grant.year, valid_grant_years)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.conf.urls import url
|
||||||
from bedrock.redirects.util import redirect
|
from bedrock.redirects.util import redirect
|
||||||
from bedrock.mozorg.util import page
|
from bedrock.mozorg.util import page
|
||||||
|
|
||||||
import views
|
from bedrock.grants import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = (
|
urlpatterns = (
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.http import Http404
|
||||||
from lib import l10n_utils
|
from lib import l10n_utils
|
||||||
import bleach
|
import bleach
|
||||||
|
|
||||||
from grants_db import GRANTS
|
from bedrock.grants.grants_db import GRANTS
|
||||||
|
|
||||||
grant_labels = {
|
grant_labels = {
|
||||||
'': 'All',
|
'': 'All',
|
||||||
|
@ -20,7 +20,7 @@ grant_labels = {
|
||||||
|
|
||||||
|
|
||||||
def grant_info(request, slug):
|
def grant_info(request, slug):
|
||||||
grant_data = filter(lambda k: k.url == slug, GRANTS)
|
grant_data = [k for k in GRANTS if k.url == slug]
|
||||||
|
|
||||||
if not grant_data:
|
if not grant_data:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
@ -38,7 +38,7 @@ def grants(request):
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
||||||
if type_filter:
|
if type_filter:
|
||||||
grants = filter(lambda k: k.type == type_filter, GRANTS)
|
grants = [k for k in GRANTS if k.type == type_filter]
|
||||||
else:
|
else:
|
||||||
grants = GRANTS
|
grants = GRANTS
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class TestFraudReport(TestCase):
|
||||||
return SimpleUploadedFile('image.png', io.read(), 'image/png')
|
return SimpleUploadedFile('image.png', io.read(), 'image/png')
|
||||||
|
|
||||||
def _create_text_file(self):
|
def _create_text_file(self):
|
||||||
return SimpleUploadedFile('stuff.txt', '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):
|
def test_view_post_valid_data(self):
|
||||||
"""
|
"""
|
||||||
|
@ -78,7 +78,7 @@ class TestFraudReport(TestCase):
|
||||||
response = legal_views.fraud_report(request)
|
response = legal_views.fraud_report(request)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.assertIn('Please enter a URL.', response.content)
|
self.assertIn(b'Please enter a URL.', response.content)
|
||||||
|
|
||||||
def test_view_post_honeypot(self):
|
def test_view_post_honeypot(self):
|
||||||
"""
|
"""
|
||||||
|
@ -96,7 +96,7 @@ class TestFraudReport(TestCase):
|
||||||
response = legal_views.fraud_report(request)
|
response = legal_views.fraud_report(request)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.assertIn('An error has occurred', response.content)
|
self.assertIn(b'An error has occurred', response.content)
|
||||||
|
|
||||||
def test_form_valid_data(self):
|
def test_form_valid_data(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -2,18 +2,14 @@
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
|
|
||||||
from lib import l10n_utils
|
|
||||||
|
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
from django.template.loader import render_to_string
|
||||||
from django.views.decorators.csrf import csrf_protect
|
from django.views.decorators.csrf import csrf_protect
|
||||||
|
|
||||||
from bedrock.base.urlresolvers import reverse
|
from bedrock.base.urlresolvers import reverse
|
||||||
|
from bedrock.legal.forms import FraudReportForm
|
||||||
from forms import FraudReportForm
|
from lib import l10n_utils
|
||||||
|
|
||||||
|
|
||||||
FRAUD_REPORT_EMAIL_FROM = 'Mozilla.com <noreply@mozilla.com>'
|
FRAUD_REPORT_EMAIL_FROM = 'Mozilla.com <noreply@mozilla.com>'
|
||||||
FRAUD_REPORT_EMAIL_SUBJECT = 'New trademark infringement report: %s; %s'
|
FRAUD_REPORT_EMAIL_SUBJECT = 'New trademark infringement report: %s; %s'
|
||||||
|
|
|
@ -25,11 +25,11 @@ class TestLoadLegalDoc(TestCase):
|
||||||
|
|
||||||
@patch('os.path.exists')
|
@patch('os.path.exists')
|
||||||
@patch.object(views, 'listdir')
|
@patch.object(views, 'listdir')
|
||||||
@patch.object(views, 'StringIO')
|
@patch.object(views.io, 'BytesIO')
|
||||||
@patch.object(views, 'md')
|
@patch.object(views, 'md')
|
||||||
def test_legal_doc_exists(self, md_mock, sio_mock, listdir_mock, exists_mock):
|
def test_legal_doc_exists(self, md_mock, bio_mock, listdir_mock, exists_mock):
|
||||||
"""Should return the content of the en-US file if it exists."""
|
"""Should return the content of the en-US file if it exists."""
|
||||||
sio_mock.StringIO.return_value.getvalue.return_value = "You're not wrong Walter..."
|
bio_mock().getvalue.return_value = b"You're not wrong Walter..."
|
||||||
exists_mock.return_value = False
|
exists_mock.return_value = False
|
||||||
listdir_mock.return_value = ['.mkdir', 'de.md', 'en-US.md']
|
listdir_mock.return_value = ['.mkdir', 'de.md', 'en-US.md']
|
||||||
doc = views.load_legal_doc('the_dude_exists', 'de')
|
doc = views.load_legal_doc('the_dude_exists', 'de')
|
||||||
|
@ -41,11 +41,11 @@ class TestLoadLegalDoc(TestCase):
|
||||||
|
|
||||||
@patch('os.path.exists')
|
@patch('os.path.exists')
|
||||||
@patch.object(views, 'listdir')
|
@patch.object(views, 'listdir')
|
||||||
@patch.object(views, 'StringIO')
|
@patch.object(views.io, 'BytesIO')
|
||||||
@patch.object(views, 'md')
|
@patch.object(views, 'md')
|
||||||
def test_localized_legal_doc_exists(self, md_mock, sio_mock, listdir_mock, exists_mock):
|
def test_localized_legal_doc_exists(self, md_mock, bio_mock, listdir_mock, exists_mock):
|
||||||
"""Localization works, and list of translations doesn't include non .md files and non-prod locales."""
|
"""Localization works, and list of translations doesn't include non .md files and non-prod locales."""
|
||||||
sio_mock.StringIO.return_value.getvalue.return_value = "You're not wrong Walter..."
|
bio_mock().getvalue.return_value = b"You're not wrong Walter..."
|
||||||
exists_mock.return_value = True
|
exists_mock.return_value = True
|
||||||
listdir_mock.return_value = ['.mkdir', 'de.md', 'en-US.md', 'sw.md']
|
listdir_mock.return_value = ['.mkdir', 'de.md', 'en-US.md', 'sw.md']
|
||||||
doc = views.load_legal_doc('the_dude_exists', 'de')
|
doc = views.load_legal_doc('the_dude_exists', 'de')
|
||||||
|
@ -57,14 +57,14 @@ class TestLoadLegalDoc(TestCase):
|
||||||
|
|
||||||
@patch('os.path.exists')
|
@patch('os.path.exists')
|
||||||
@patch.object(views, 'listdir')
|
@patch.object(views, 'listdir')
|
||||||
@patch.object(views, 'StringIO')
|
@patch.object(views.io, 'BytesIO')
|
||||||
@patch.object(views, 'md')
|
@patch.object(views, 'md')
|
||||||
def test_localized_legal_doc_mapped_locale(self, md_mock, sio_mock, listdir_mock, exists_mock):
|
def test_localized_legal_doc_mapped_locale(self, md_mock, bio_mock, listdir_mock, exists_mock):
|
||||||
"""Should output bedrock locale when legal-docs locale exists"""
|
"""Should output bedrock locale when legal-docs locale exists"""
|
||||||
bedrock_locale = 'hi-IN'
|
bedrock_locale = 'hi-IN'
|
||||||
ld_locale = 'hi'
|
ld_locale = 'hi'
|
||||||
ld_filename = '%s.md' % ld_locale
|
ld_filename = '%s.md' % ld_locale
|
||||||
sio_mock.StringIO.return_value.getvalue.return_value = "You're not wrong Walter..."
|
bio_mock().getvalue.return_value = b"You're not wrong Walter..."
|
||||||
exists_mock.return_value = True
|
exists_mock.return_value = True
|
||||||
listdir_mock.return_value = [ld_filename, 'en-US.md']
|
listdir_mock.return_value = [ld_filename, 'en-US.md']
|
||||||
doc = views.load_legal_doc('the_dude_exists', bedrock_locale)
|
doc = views.load_legal_doc('the_dude_exists', bedrock_locale)
|
||||||
|
@ -76,13 +76,13 @@ class TestLoadLegalDoc(TestCase):
|
||||||
|
|
||||||
@patch('os.path.exists')
|
@patch('os.path.exists')
|
||||||
@patch.object(views, 'listdir')
|
@patch.object(views, 'listdir')
|
||||||
@patch.object(views, 'StringIO')
|
@patch.object(views.io, 'BytesIO')
|
||||||
@patch.object(views, 'md')
|
@patch.object(views, 'md')
|
||||||
def test_localized_legal_doc_mapped_locale_fixed(self, md_mock, sio_mock, listdir_mock, exists_mock):
|
def test_localized_legal_doc_mapped_locale_fixed(self, md_mock, bio_mock, listdir_mock, exists_mock):
|
||||||
"""Should fallback to bedrock locale when legal-docs locale changes to match"""
|
"""Should fallback to bedrock locale when legal-docs locale changes to match"""
|
||||||
bedrock_locale = 'hi-IN'
|
bedrock_locale = 'hi-IN'
|
||||||
ld_filename = '%s.md' % bedrock_locale
|
ld_filename = '%s.md' % bedrock_locale
|
||||||
sio_mock.StringIO.return_value.getvalue.return_value = "You're not wrong Walter..."
|
bio_mock().getvalue.return_value = b"You're not wrong Walter..."
|
||||||
exists_mock.side_effect = [False, True]
|
exists_mock.side_effect = [False, True]
|
||||||
listdir_mock.return_value = [ld_filename, 'en-US.md']
|
listdir_mock.return_value = [ld_filename, 'en-US.md']
|
||||||
doc = views.load_legal_doc('the_dude_exists', bedrock_locale)
|
doc = views.load_legal_doc('the_dude_exists', bedrock_locale)
|
||||||
|
@ -123,7 +123,7 @@ class TestLegalDocView(TestCase):
|
||||||
legal_doc_name='the_dude_exists')
|
legal_doc_name='the_dude_exists')
|
||||||
resp = view(req)
|
resp = view(req)
|
||||||
assert resp['cache-control'] == 'max-age={0!s}'.format(views.CACHE_TIMEOUT)
|
assert resp['cache-control'] == 'max-age={0!s}'.format(views.CACHE_TIMEOUT)
|
||||||
assert resp.content == doc_value
|
assert resp.content.decode('utf-8') == doc_value
|
||||||
assert render_mock.call_args[0][2]['doc'] == doc_value
|
assert render_mock.call_args[0][2]['doc'] == doc_value
|
||||||
lld_mock.assert_called_with('the_dude_exists', 'de')
|
lld_mock.assert_called_with('the_dude_exists', 'de')
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
from os import path, listdir
|
from os import path, listdir
|
||||||
import StringIO
|
import io
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
@ -42,7 +42,7 @@ def load_legal_doc(doc_name, locale):
|
||||||
|
|
||||||
source_dir = path.join(LEGAL_DOCS_PATH, doc_name)
|
source_dir = path.join(LEGAL_DOCS_PATH, doc_name)
|
||||||
source_file = path.join(source_dir, locale + '.md')
|
source_file = path.join(source_dir, locale + '.md')
|
||||||
output = StringIO.StringIO()
|
output = io.BytesIO()
|
||||||
locales = [f.replace('.md', '') for f in listdir(source_dir) if f.endswith('.md')]
|
locales = [f.replace('.md', '') for f in listdir(source_dir) if f.endswith('.md')]
|
||||||
# convert legal-docs locales to bedrock equivalents
|
# convert legal-docs locales to bedrock equivalents
|
||||||
locales = [LEGAL_DOCS_LOCALES_TO_BEDROCK.get(l, l) for l in locales]
|
locales = [LEGAL_DOCS_LOCALES_TO_BEDROCK.get(l, l) for l in locales]
|
||||||
|
@ -60,10 +60,13 @@ def load_legal_doc(doc_name, locale):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Parse the Markdown file
|
# Parse the Markdown file
|
||||||
md.markdownFromFile(input=source_file, output=output,
|
md.markdownFromFile(
|
||||||
extensions=['attr_list', 'headerid',
|
input=source_file, output=output, extensions=[
|
||||||
OutlineExtension((('wrapper_cls', ''),))])
|
'markdown.extensions.attr_list',
|
||||||
content = output.getvalue().decode('utf8')
|
'markdown.extensions.toc',
|
||||||
|
OutlineExtension((('wrapper_cls', ''),))
|
||||||
|
])
|
||||||
|
content = output.getvalue().decode('utf-8')
|
||||||
except IOError:
|
except IOError:
|
||||||
content = None
|
content = None
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -6,7 +6,7 @@ import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from bedrock.mozorg.util import get_fb_like_locale
|
from bedrock.mozorg.util import get_fb_like_locale
|
||||||
from lib.l10n_utils import get_locale
|
from lib.l10n_utils import get_locale
|
||||||
|
|
|
@ -13,7 +13,7 @@ from bedrock.externalfiles import ExternalFile
|
||||||
|
|
||||||
class CreditsFile(ExternalFile):
|
class CreditsFile(ExternalFile):
|
||||||
def validate_content(self, content):
|
def validate_content(self, content):
|
||||||
rows = list(csv.reader(content.strip().encode('utf8').split('\n')))
|
rows = list(csv.reader(content.strip().split('\n')))
|
||||||
if len(rows) < 2200: # it's 2273 as of now
|
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)))
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ class CreditsFile(ExternalFile):
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sortkey = unicodedata.normalize('NFKD', sortkey.decode('utf8')).encode('ascii', 'ignore')
|
sortkey = unicodedata.normalize('NFKD', sortkey).encode('ascii', 'ignore').decode()
|
||||||
names.append([name.decode('utf8'), sortkey.upper()])
|
names.append([name, sortkey.upper()])
|
||||||
|
|
||||||
return sorted(names, key=itemgetter(1))
|
return sorted(names, key=itemgetter(1))
|
||||||
|
|
|
@ -8,13 +8,10 @@ import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from random import randrange
|
from random import randrange
|
||||||
|
|
||||||
from django import forms
|
from django.urls import reverse
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from localflavor.us.us_states import STATE_CHOICES
|
|
||||||
|
|
||||||
from lib.l10n_utils.dotlang import _
|
from lib.l10n_utils.dotlang import _
|
||||||
from lib.l10n_utils.dotlang import _lazy
|
from lib.l10n_utils.dotlang import _lazy
|
||||||
|
|
||||||
|
@ -37,7 +34,7 @@ class PrivacyWidget(widgets.CheckboxInput):
|
||||||
"""Render a checkbox with privacy text. Lots of pages need this so
|
"""Render a checkbox with privacy text. Lots of pages need this so
|
||||||
it should be standardized"""
|
it should be standardized"""
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None, renderer=None):
|
||||||
attrs['required'] = 'required'
|
attrs['required'] = 'required'
|
||||||
input_txt = super(PrivacyWidget, self).render(name, value, attrs)
|
input_txt = super(PrivacyWidget, self).render(name, value, attrs)
|
||||||
|
|
||||||
|
@ -55,7 +52,7 @@ class PrivacyWidget(widgets.CheckboxInput):
|
||||||
class HoneyPotWidget(widgets.TextInput):
|
class HoneyPotWidget(widgets.TextInput):
|
||||||
"""Render a text field to (hopefully) trick bots. Will be used on many pages."""
|
"""Render a text field to (hopefully) trick bots. Will be used on many pages."""
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None, renderer=None):
|
||||||
honeypot_txt = _(u'Leave this field empty.')
|
honeypot_txt = _(u'Leave this field empty.')
|
||||||
# semi-randomized in case we have more than one per page.
|
# semi-randomized in case we have more than one per page.
|
||||||
# this is maybe/probably overthought
|
# this is maybe/probably overthought
|
||||||
|
@ -89,20 +86,3 @@ class TelInput(widgets.TextInput):
|
||||||
|
|
||||||
class NumberInput(widgets.TextInput):
|
class NumberInput(widgets.TextInput):
|
||||||
input_type = 'number'
|
input_type = 'number'
|
||||||
|
|
||||||
|
|
||||||
class L10nSelect(forms.Select):
|
|
||||||
def render_option(self, selected_choices, option_value, option_label):
|
|
||||||
if option_value == '':
|
|
||||||
option_label = u'-- {0} --'.format(_('select'))
|
|
||||||
return super(L10nSelect, self).render_option(selected_choices, option_value, option_label)
|
|
||||||
|
|
||||||
|
|
||||||
class USStateSelectBlank(widgets.Select):
|
|
||||||
"""Version of USStateSelect widget with a blank first selection."""
|
|
||||||
|
|
||||||
def __init__(self, attrs=None, empty_msg=None):
|
|
||||||
if empty_msg is None:
|
|
||||||
empty_msg = ''
|
|
||||||
us_states_blank = (('', empty_msg),) + STATE_CHOICES
|
|
||||||
super(USStateSelectBlank, self).__init__(attrs, choices=us_states_blank)
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ class ForumsFile(ExternalFile):
|
||||||
raise ValueError('Error parsing forums file.')
|
raise ValueError('Error parsing forums file.')
|
||||||
|
|
||||||
# currently 15 categories
|
# currently 15 categories
|
||||||
if not len(forums.keys()) > 10:
|
if not len(forums) > 10:
|
||||||
raise ValueError('Forums file truncated or corrupted.')
|
raise ValueError('Forums file truncated or corrupted.')
|
||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
|
@ -7,7 +7,7 @@ from bedrock.base.urlresolvers import reverse
|
||||||
from bedrock.mozorg.util import page
|
from bedrock.mozorg.util import page
|
||||||
|
|
||||||
|
|
||||||
class PageNode(object):
|
class PageNode:
|
||||||
"""
|
"""
|
||||||
A utility for representing a hierarchical page structure.
|
A utility for representing a hierarchical page structure.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
|
@ -13,7 +13,14 @@ from django.utils.cache import add_never_cache_headers
|
||||||
from django_statsd.middleware import GraphiteRequestTimingMiddleware
|
from django_statsd.middleware import GraphiteRequestTimingMiddleware
|
||||||
|
|
||||||
|
|
||||||
class CacheMiddleware(object):
|
class CacheMiddleware:
|
||||||
|
|
||||||
|
def __init__(self, get_response=None):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.get_response(request)
|
||||||
|
return self.process_response(request, response)
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
cache = (request.method != 'POST' and
|
cache = (request.method != 'POST' and
|
||||||
|
@ -41,8 +48,16 @@ class MozorgRequestTimingMiddleware(GraphiteRequestTimingMiddleware):
|
||||||
f.process_view(request, view, view_args, view_kwargs)
|
f.process_view(request, view, view_args, view_kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ClacksOverheadMiddleware(object):
|
class ClacksOverheadMiddleware:
|
||||||
# bug 1144901
|
# bug 1144901
|
||||||
|
|
||||||
|
def __init__(self, get_response=None):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.get_response(request)
|
||||||
|
return self.process_response(request, response)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_response(request, response):
|
def process_response(request, response):
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
|
@ -50,23 +65,34 @@ class ClacksOverheadMiddleware(object):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class HostnameMiddleware(object):
|
class HostnameMiddleware:
|
||||||
def __init__(self):
|
def __init__(self, get_response=None):
|
||||||
if not settings.ENABLE_HOSTNAME_MIDDLEWARE:
|
if not settings.ENABLE_HOSTNAME_MIDDLEWARE:
|
||||||
raise MiddlewareNotUsed
|
raise MiddlewareNotUsed
|
||||||
|
|
||||||
values = [getattr(settings, x) for x in ['HOSTNAME', 'CLUSTER_NAME']]
|
values = [getattr(settings, x) for x in ['HOSTNAME', 'CLUSTER_NAME']]
|
||||||
self.backend_server = '.'.join(x for x in values if x)
|
self.backend_server = '.'.join(x for x in values if x)
|
||||||
|
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.get_response(request)
|
||||||
|
return self.process_response(request, response)
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
response['X-Backend-Server'] = self.backend_server
|
response['X-Backend-Server'] = self.backend_server
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class VaryNoCacheMiddleware(object):
|
class VaryNoCacheMiddleware:
|
||||||
def __init__(self):
|
def __init__(self, get_response=None):
|
||||||
if not settings.ENABLE_VARY_NOCACHE_MIDDLEWARE:
|
if not settings.ENABLE_VARY_NOCACHE_MIDDLEWARE:
|
||||||
raise MiddlewareNotUsed
|
raise MiddlewareNotUsed
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.get_response(request)
|
||||||
|
return self.process_response(request, response)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_response(request, response):
|
def process_response(request, response):
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
import django_extensions.db.fields
|
import django_extensions.db.fields
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ class TwitterCache(models.Model):
|
||||||
updated = ModificationDateTimeField()
|
updated = ModificationDateTimeField()
|
||||||
objects = TwitterCacheManager()
|
objects = TwitterCacheManager()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return u'Tweets from @' + self.account
|
return u'Tweets from @' + self.account
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ redirectpatterns = (
|
||||||
'https://wiki.mozilla.org/Websites/Directory', locale_prefix=False),
|
'https://wiki.mozilla.org/Websites/Directory', locale_prefix=False),
|
||||||
|
|
||||||
# bug 885856
|
# bug 885856
|
||||||
redirect(r'^projects/index\.(de|fr|hr|sq).html$', '/{}/firefox/',
|
redirect(r'^projects/index\.(de|fr|hr|sq)\.html$', '/{}/firefox/',
|
||||||
locale_prefix=False),
|
locale_prefix=False),
|
||||||
|
|
||||||
# bug 856075
|
# bug 856075
|
||||||
|
@ -99,14 +99,14 @@ redirectpatterns = (
|
||||||
redirect(r'^firefox/backtoschool/firstrun/?$', 'firefox.firstrun'),
|
redirect(r'^firefox/backtoschool/firstrun/?$', 'firefox.firstrun'),
|
||||||
|
|
||||||
# bug 824126, 837942
|
# bug 824126, 837942
|
||||||
redirect(r'^ports/qtmozilla(?:/|/index.html)?$', 'https://wiki.mozilla.org/Qt'),
|
redirect(r'^ports/qtmozilla(?:/|/index\.html)?$', 'https://wiki.mozilla.org/Qt'),
|
||||||
redirect(r'^ports/os2/?$', 'https://wiki.mozilla.org/Ports/os2'),
|
redirect(r'^ports/os2/?$', 'https://wiki.mozilla.org/Ports/os2'),
|
||||||
redirect(r'^ports(?P<path>.*)', 'http://www-archive.mozilla.org/ports{path}'),
|
redirect(r'^ports(?P<path>.*)', 'http://www-archive.mozilla.org/ports{path}'),
|
||||||
|
|
||||||
redirect(r'^b2g', 'https://support.mozilla.org/products/firefox-os'),
|
redirect(r'^b2g', 'https://support.mozilla.org/products/firefox-os'),
|
||||||
|
|
||||||
# Bug 781914
|
# Bug 781914
|
||||||
redirect(r'^contribute/areas.html$', 'mozorg.contribute.index'),
|
redirect(r'^contribute/areas\.html$', 'mozorg.contribute.index'),
|
||||||
redirect(r'^contribute/universityambassadors', 'https://campus.mozilla.community/'),
|
redirect(r'^contribute/universityambassadors', 'https://campus.mozilla.community/'),
|
||||||
|
|
||||||
# Bug 1144949
|
# Bug 1144949
|
||||||
|
@ -132,7 +132,7 @@ redirectpatterns = (
|
||||||
'https://marketplace.firefox.com/developers/'),
|
'https://marketplace.firefox.com/developers/'),
|
||||||
|
|
||||||
# Bug 815527 /m/privacy.html -> /privacy/firefox/
|
# Bug 815527 /m/privacy.html -> /privacy/firefox/
|
||||||
redirect(r'^m/privacy.html$', 'privacy.notices.firefox'),
|
redirect(r'^m/privacy\.html$', 'privacy.notices.firefox'),
|
||||||
|
|
||||||
# Bug 1109318 /privacy/you -> privacy/tips/
|
# Bug 1109318 /privacy/you -> privacy/tips/
|
||||||
# Bug 1238687 /privacy/tips -> teach/smarton/
|
# Bug 1238687 /privacy/tips -> teach/smarton/
|
||||||
|
@ -143,7 +143,7 @@ redirectpatterns = (
|
||||||
'mozorg.internet-health.privacy-security'),
|
'mozorg.internet-health.privacy-security'),
|
||||||
|
|
||||||
# Bug 821047 /about/mission.html -> /mission/
|
# Bug 821047 /about/mission.html -> /mission/
|
||||||
redirect(r'^about/mission.html$', '/mission/'),
|
redirect(r'^about/mission\.html$', '/mission/'),
|
||||||
|
|
||||||
# Bug 784411 /about/mission/ -> /mission/
|
# Bug 784411 /about/mission/ -> /mission/
|
||||||
redirect(r'^about/mission/?$', '/mission/'),
|
redirect(r'^about/mission/?$', '/mission/'),
|
||||||
|
@ -201,7 +201,7 @@ redirectpatterns = (
|
||||||
redirect(r'^dnt/?$', 'https://support.mozilla.org/kb/how-do-i-turn-do-not-track-feature'),
|
redirect(r'^dnt/?$', 'https://support.mozilla.org/kb/how-do-i-turn-do-not-track-feature'),
|
||||||
|
|
||||||
# bug 1205632
|
# bug 1205632
|
||||||
redirect(r'^js/language(?:/|/index.html)?$',
|
redirect(r'^js/language(?:/|/index\.html)?$',
|
||||||
'https://developer.mozilla.org/docs/Web/JavaScript/Language_Resources',
|
'https://developer.mozilla.org/docs/Web/JavaScript/Language_Resources',
|
||||||
locale_prefix=False),
|
locale_prefix=False),
|
||||||
redirect(r'^js/language/js20(/.*)?$', 'http://www.ecmascript-lang.org',
|
redirect(r'^js/language/js20(/.*)?$', 'http://www.ecmascript-lang.org',
|
||||||
|
@ -238,12 +238,12 @@ redirectpatterns = (
|
||||||
# bug 876810
|
# bug 876810
|
||||||
redirect(r'^hacking/commit-access-policy/?$',
|
redirect(r'^hacking/commit-access-policy/?$',
|
||||||
'mozorg.about.governance.policies.commit.access-policy'),
|
'mozorg.about.governance.policies.commit.access-policy'),
|
||||||
redirect(r'^hacking/committer(/|/faq.html)?$', 'mozorg.about.governance.policies.commit'),
|
redirect(r'^hacking/committer(/|/faq\.html)?$', 'mozorg.about.governance.policies.commit'),
|
||||||
redirect(r'^hacking/notification/?$', 'mozorg.about.governance.policies.commit'),
|
redirect(r'^hacking/notification/?$', 'mozorg.about.governance.policies.commit'),
|
||||||
redirect(r'^hacking/committer/committers-agreement\.(?P<ext>odt|pdf|txt)$',
|
redirect(r'^hacking/committer/committers-agreement\.(?P<ext>odt|pdf|txt)$',
|
||||||
'https://static.mozilla.com/foundation/documents/'
|
'https://static.mozilla.com/foundation/documents/'
|
||||||
'commit-access/committers-agreement.{ext}'),
|
'commit-access/committers-agreement.{ext}'),
|
||||||
redirect(r'^hacking/notification/acceptance-email.txt$',
|
redirect(r'^hacking/notification/acceptance-email\.txt$',
|
||||||
'https://static.mozilla.com/foundation/documents/commit-access/acceptance-email.txt'),
|
'https://static.mozilla.com/foundation/documents/commit-access/acceptance-email.txt'),
|
||||||
|
|
||||||
# bug 1165344
|
# bug 1165344
|
||||||
|
@ -275,33 +275,33 @@ redirectpatterns = (
|
||||||
redirect(r'^opportunities(?:/|/index\.html)?$', 'https://careers.mozilla.org/'),
|
redirect(r'^opportunities(?:/|/index\.html)?$', 'https://careers.mozilla.org/'),
|
||||||
|
|
||||||
# bug 818321
|
# bug 818321
|
||||||
redirect(r'^projects/security/tld-idn-policy-list.html$',
|
redirect(r'^projects/security/tld-idn-policy-list\.html$',
|
||||||
'/about/governance/policies/security-group/tld-idn/'),
|
'/about/governance/policies/security-group/tld-idn/'),
|
||||||
redirect(r'^projects/security/membership-policy.html$',
|
redirect(r'^projects/security/membership-policy\.html$',
|
||||||
'/about/governance/policies/security-group/membership/'),
|
'/about/governance/policies/security-group/membership/'),
|
||||||
redirect(r'^projects/security/secgrouplist.html$',
|
redirect(r'^projects/security/secgrouplist\.html$',
|
||||||
'/about/governance/policies/security-group/'),
|
'/about/governance/policies/security-group/'),
|
||||||
redirect(r'^projects/security/security-bugs-policy.html$',
|
redirect(r'^projects/security/security-bugs-policy\.html$',
|
||||||
'/about/governance/policies/security-group/bugs/'),
|
'/about/governance/policies/security-group/bugs/'),
|
||||||
|
|
||||||
# bug 818316, 1128579
|
# bug 818316, 1128579
|
||||||
redirect(r'^projects/security/certs(?:/(?:index.html)?)?$',
|
redirect(r'^projects/security/certs(?:/(?:index\.html)?)?$',
|
||||||
'/about/governance/policies/security-group/certs/'),
|
'/about/governance/policies/security-group/certs/'),
|
||||||
redirect(r'^projects/security/certs/included(?:/(?:index.html)?)?$',
|
redirect(r'^projects/security/certs/included(?:/(?:index\.html)?)?$',
|
||||||
'https://wiki.mozilla.org/CA:IncludedCAs'),
|
'https://wiki.mozilla.org/CA:IncludedCAs'),
|
||||||
redirect(r'^projects/security/certs/pending(?:/(?:index.html)?)?$',
|
redirect(r'^projects/security/certs/pending(?:/(?:index\.html)?)?$',
|
||||||
'https://wiki.mozilla.org/CA:PendingCAs'),
|
'https://wiki.mozilla.org/CA:PendingCAs'),
|
||||||
redirect(r'^projects/security/certs/policy(?:/(?:index.html)?)?$',
|
redirect(r'^projects/security/certs/policy(?:/(?:index\.html)?)?$',
|
||||||
'/about/governance/policies/security-group/certs/policy/'),
|
'/about/governance/policies/security-group/certs/policy/'),
|
||||||
redirect(r'^projects/security/certs/policy/EnforcementPolicy.html$',
|
redirect(r'^projects/security/certs/policy/EnforcementPolicy\.html$',
|
||||||
'/about/governance/policies/security-group/certs/policy/enforcement/'),
|
'/about/governance/policies/security-group/certs/policy/enforcement/'),
|
||||||
redirect(r'^projects/security/certs/policy/MaintenancePolicy.html$',
|
redirect(r'^projects/security/certs/policy/MaintenancePolicy\.html$',
|
||||||
'/about/governance/policies/security-group/certs/policy/maintenance/'),
|
'/about/governance/policies/security-group/certs/policy/maintenance/'),
|
||||||
redirect(r'^projects/security/certs/policy/InclusionPolicy.html$',
|
redirect(r'^projects/security/certs/policy/InclusionPolicy\.html$',
|
||||||
'/about/governance/policies/security-group/certs/policy/inclusion/'),
|
'/about/governance/policies/security-group/certs/policy/inclusion/'),
|
||||||
redirect(r'^about/governance/policies/security-group/certs/included(?:/(?:index.html)?)?$',
|
redirect(r'^about/governance/policies/security-group/certs/included(?:/(?:index\.html)?)?$',
|
||||||
'https://wiki.mozilla.org/CA:IncludedCAs'),
|
'https://wiki.mozilla.org/CA:IncludedCAs'),
|
||||||
redirect(r'^about/governance/policies/security-group/certs/pending(?:/(?:index.html)?)?$',
|
redirect(r'^about/governance/policies/security-group/certs/pending(?:/(?:index\.html)?)?$',
|
||||||
'https://wiki.mozilla.org/CA:PendingCAs'),
|
'https://wiki.mozilla.org/CA:PendingCAs'),
|
||||||
|
|
||||||
# bug 1068931
|
# bug 1068931
|
||||||
|
@ -309,11 +309,11 @@ redirectpatterns = (
|
||||||
|
|
||||||
# bug 887426
|
# bug 887426
|
||||||
redirect(r'^about/policies/?$', '/about/governance/policies/'),
|
redirect(r'^about/policies/?$', '/about/governance/policies/'),
|
||||||
redirect(r'^about/policies/participation.html$', '/about/governance/policies/participation/'),
|
redirect(r'^about/policies/participation\.html$', '/about/governance/policies/participation/'),
|
||||||
redirect(r'^about/policies/policies.html$', '/about/governance/policies/'),
|
redirect(r'^about/policies/policies\.html$', '/about/governance/policies/'),
|
||||||
|
|
||||||
# bug 882923
|
# bug 882923
|
||||||
redirect(r'^opt-out.html$', '/privacy/websites/#user-choices'),
|
redirect(r'^opt-out\.html$', '/privacy/websites/#user-choices'),
|
||||||
|
|
||||||
# bug 878039
|
# bug 878039
|
||||||
redirect(r'^access/?$', 'https://developer.mozilla.org/docs/Web/Accessibility'),
|
redirect(r'^access/?$', 'https://developer.mozilla.org/docs/Web/Accessibility'),
|
||||||
|
@ -363,7 +363,7 @@ redirectpatterns = (
|
||||||
'https://developer.mozilla.org/docs/Web/Accessibility/Implementing_MSAA_server'),
|
'https://developer.mozilla.org/docs/Web/Accessibility/Implementing_MSAA_server'),
|
||||||
redirect(r'^access/windows/zoomtext\.html$',
|
redirect(r'^access/windows/zoomtext\.html$',
|
||||||
'https://developer.mozilla.org/docs/Mozilla/Accessibility/ZoomText'),
|
'https://developer.mozilla.org/docs/Mozilla/Accessibility/ZoomText'),
|
||||||
redirect('^access/donate(\.html|/)?$', 'https://donate.mozilla.org/'),
|
redirect(r'^access/donate(\.html|/)?$', 'https://donate.mozilla.org/'),
|
||||||
|
|
||||||
# bug 1148187
|
# bug 1148187
|
||||||
redirect(r'^access/(?P<page>.+)$',
|
redirect(r'^access/(?P<page>.+)$',
|
||||||
|
@ -385,7 +385,7 @@ redirectpatterns = (
|
||||||
redirect(r'^MPL/boilerplate-1\.1/(.*)$',
|
redirect(r'^MPL/boilerplate-1\.1/(.*)$',
|
||||||
'http://website-archive.mozilla.org/www.mozilla.org/mpl/MPL/boilerplate-1.1/{}',
|
'http://website-archive.mozilla.org/www.mozilla.org/mpl/MPL/boilerplate-1.1/{}',
|
||||||
locale_prefix=False),
|
locale_prefix=False),
|
||||||
redirect(r'^MPL/missing.html$',
|
redirect(r'^MPL/missing\.html$',
|
||||||
'http://website-archive.mozilla.org/www.mozilla.org/mpl/MPL/missing.html',
|
'http://website-archive.mozilla.org/www.mozilla.org/mpl/MPL/missing.html',
|
||||||
locale_prefix=False),
|
locale_prefix=False),
|
||||||
|
|
||||||
|
@ -401,9 +401,9 @@ redirectpatterns = (
|
||||||
# bug 724682
|
# bug 724682
|
||||||
redirect(r'^projects/mathml/demo/texvsmml\.html$',
|
redirect(r'^projects/mathml/demo/texvsmml\.html$',
|
||||||
'https://developer.mozilla.org/docs/Mozilla_MathML_Project/MathML_Torture_Test'),
|
'https://developer.mozilla.org/docs/Mozilla_MathML_Project/MathML_Torture_Test'),
|
||||||
redirect(r'^projects/mathml/fonts(?:/(?:index.html)?)?$',
|
redirect(r'^projects/mathml/fonts(?:/(?:index\.html)?)?$',
|
||||||
'https://developer.mozilla.org/Mozilla_MathML_Project/Fonts'),
|
'https://developer.mozilla.org/Mozilla_MathML_Project/Fonts'),
|
||||||
redirect(r'^projects/mathml/screenshots(?:/(?:index.html)?)?$',
|
redirect(r'^projects/mathml/screenshots(?:/(?:index\.html)?)?$',
|
||||||
'https://developer.mozilla.org/Mozilla_MathML_Project/Screenshots'),
|
'https://developer.mozilla.org/Mozilla_MathML_Project/Screenshots'),
|
||||||
redirect(r'^projects/mathml/authoring\.html$',
|
redirect(r'^projects/mathml/authoring\.html$',
|
||||||
'https://developer.mozilla.org/en/Mozilla_MathML_Project/Authoring'),
|
'https://developer.mozilla.org/en/Mozilla_MathML_Project/Authoring'),
|
||||||
|
@ -458,7 +458,7 @@ redirectpatterns = (
|
||||||
'https://developer.mozilla.org/docs/Mozilla/Projects/Rhino/Download_Rhino'),
|
'https://developer.mozilla.org/docs/Mozilla/Projects/Rhino/Download_Rhino'),
|
||||||
redirect(r'^rhino/doc\.html$',
|
redirect(r'^rhino/doc\.html$',
|
||||||
'https://developer.mozilla.org/docs/Mozilla/Projects/Rhino/Documentation'),
|
'https://developer.mozilla.org/docs/Mozilla/Projects/Rhino/Documentation'),
|
||||||
redirect('^rhino/shell\.html$',
|
redirect(r'^rhino/shell\.html$',
|
||||||
'https://developer.mozilla.org/docs/Mozilla/Projects/Rhino/Shell'),
|
'https://developer.mozilla.org/docs/Mozilla/Projects/Rhino/Shell'),
|
||||||
redirect(r'^rhino/?', 'https://developer.mozilla.org/docs/Mozilla/Projects/Rhino'),
|
redirect(r'^rhino/?', 'https://developer.mozilla.org/docs/Mozilla/Projects/Rhino'),
|
||||||
|
|
||||||
|
@ -487,9 +487,9 @@ redirectpatterns = (
|
||||||
# (The links within the foundation pages have been updated, but there are
|
# (The links within the foundation pages have been updated, but there are
|
||||||
# probably many links to them from other pages and sites that need to keep
|
# probably many links to them from other pages and sites that need to keep
|
||||||
# working.)
|
# working.)
|
||||||
redirect(r'^foundation/documents/(?P<pdf>[^/]+).pdf$',
|
redirect(r'^foundation/documents/(?P<pdf>[^/]+)\.pdf$',
|
||||||
'https://static.mozilla.com/foundation/documents/{pdf}.pdf', re_flags='i'),
|
'https://static.mozilla.com/foundation/documents/{pdf}.pdf', re_flags='i'),
|
||||||
redirect(r'^foundation/donate_form.pdf$',
|
redirect(r'^foundation/donate_form\.pdf$',
|
||||||
'https://static.mozilla.com/foundation/documents/donate_form.pdf', re_flags='i'),
|
'https://static.mozilla.com/foundation/documents/donate_form.pdf', re_flags='i'),
|
||||||
|
|
||||||
# openwebfund/ and openwebfund/index.html redirect to another site. Careful because
|
# openwebfund/ and openwebfund/index.html redirect to another site. Careful because
|
||||||
|
@ -502,14 +502,14 @@ redirectpatterns = (
|
||||||
|
|
||||||
# FIXUPs for changing foo/bar.html to foo/bar/
|
# FIXUPs for changing foo/bar.html to foo/bar/
|
||||||
# Redirect foundation/foo.html to foundation/foo/, with a redirect for the nice search engines
|
# Redirect foundation/foo.html to foundation/foo/, with a redirect for the nice search engines
|
||||||
redirect(r'^foundation/(?P<page>about|careers|licensing|moco|mocosc).html$',
|
redirect(r'^foundation/(?P<page>about|careers|licensing|moco|mocosc)\.html$',
|
||||||
'/foundation/{page}/', re_flags='i'),
|
'/foundation/{page}/', re_flags='i'),
|
||||||
# Redirect foundation/anything/foo.html to foundation/anything/foo/,
|
# Redirect foundation/anything/foo.html to foundation/anything/foo/,
|
||||||
# with a redirect for the nice search engines
|
# with a redirect for the nice search engines
|
||||||
redirect(r'^foundation/documents/(?P<page>index|mozilla-200.-financial-faq)\.html$',
|
redirect(r'^foundation/documents/(?P<page>index|mozilla-200.-financial-faq)\.html$',
|
||||||
'/foundation/{page}/', re_flags='i'),
|
'/foundation/{page}/', re_flags='i'),
|
||||||
redirect(r'^foundation/(?P<page>(?:annualreport|documents|feed-icon-guidelines|'
|
redirect(r'^foundation/(?P<page>(?:annualreport|documents|feed-icon-guidelines|'
|
||||||
r'licensing|openwebfund|trademarks)/.*).html$', '/foundation/{page}/', re_flags='i'),
|
r'licensing|openwebfund|trademarks)/.*)\.html$', '/foundation/{page}/', re_flags='i'),
|
||||||
|
|
||||||
# bug 442671
|
# bug 442671
|
||||||
redirect(r'^foundation/trademarks/l10n-policy/?$', '/foundation/trademarks/', re_flags='i'),
|
redirect(r'^foundation/trademarks/l10n-policy/?$', '/foundation/trademarks/', re_flags='i'),
|
||||||
|
@ -568,7 +568,7 @@ redirectpatterns = (
|
||||||
redirect(r'^about/patents/?$', 'mozorg.about.policy.patents.index'),
|
redirect(r'^about/patents/?$', 'mozorg.about.policy.patents.index'),
|
||||||
redirect(r'^about/patents/guide/?$', 'mozorg.about.policy.patents.guide'),
|
redirect(r'^about/patents/guide/?$', 'mozorg.about.policy.patents.guide'),
|
||||||
redirect(r'^about/patents/license/?$', 'mozorg.about.policy.patents.license'),
|
redirect(r'^about/patents/license/?$', 'mozorg.about.policy.patents.license'),
|
||||||
redirect(r'^about/patents/license/1.0/?$', 'mozorg.about.policy.patents.license-1.0'),
|
redirect(r'^about/patents/license/1\.0/?$', 'mozorg.about.policy.patents.license-1.0'),
|
||||||
|
|
||||||
redirect(r'^projects/marketing(/.*)?$', 'https://wiki.mozilla.org/MarketingGuide'),
|
redirect(r'^projects/marketing(/.*)?$', 'https://wiki.mozilla.org/MarketingGuide'),
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
|
|
||||||
<!--BEGIN_LIST-->
|
<!--BEGIN_LIST-->
|
||||||
|
|
||||||
{% for title, forums_list in forums.ordered.iteritems() %}
|
{% for title, forums_list in forums.ordered.items() %}
|
||||||
|
|
||||||
<section id="{{ title|slugify }}" class="forum-group">
|
<section id="{{ title|slugify }}" class="forum-group">
|
||||||
<a href="#" class="top">{{ _('Back to top') }}</a>
|
<a href="#" class="top">{{ _('Back to top') }}</a>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
<p>We would like to thank our contributors, whose efforts over many years have made this software what it is.</p>
|
<p>We would like to thank our contributors, whose efforts over many years have made this software what it is.</p>
|
||||||
|
|
||||||
{% for letter, names in credits.ordered.iteritems() %}
|
{% for letter, names in credits.ordered.items() %}
|
||||||
<h2><a name="{{ letter }}">{{ letter }}</a></h2>
|
<h2><a name="{{ letter }}">{{ letter }}</a></h2>
|
||||||
<p>
|
<p>
|
||||||
{{ ',\n'.join(names) }}
|
{{ ',\n'.join(names) }}
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
import misc
|
from bedrock.mozorg.templatetags import misc, social_widgets
|
||||||
import social_widgets
|
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
from __future__ import unicode_literals, print_function
|
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
from os import path
|
from os import path
|
||||||
from os.path import splitext
|
from os.path import splitext
|
||||||
try:
|
import urllib.parse
|
||||||
import urlparse
|
|
||||||
except ImportError:
|
|
||||||
import urllib.parse as urlparse
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.staticfiles.finders import find as find_static
|
from django.contrib.staticfiles.finders import find as find_static
|
||||||
|
@ -173,7 +168,7 @@ def platform_img(ctx, url, optional_attributes=None):
|
||||||
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 = {}
|
img_attrs = {}
|
||||||
for platform, image in img_urls.iteritems():
|
for platform, image in img_urls.items():
|
||||||
if is_l10n:
|
if is_l10n:
|
||||||
image = l10n_img_file_name(ctx, image)
|
image = l10n_img_file_name(ctx, image)
|
||||||
else:
|
else:
|
||||||
|
@ -188,7 +183,7 @@ def platform_img(ctx, url, optional_attributes=None):
|
||||||
|
|
||||||
img_attrs.update(optional_attributes)
|
img_attrs.update(optional_attributes)
|
||||||
attrs = ' '.join('%s="%s"' % (attr, val)
|
attrs = ' '.join('%s="%s"' % (attr, val)
|
||||||
for attr, val in img_attrs.iteritems())
|
for attr, val in img_attrs.items())
|
||||||
|
|
||||||
# Don't download any image until the javascript sets it based on
|
# 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
|
# data-src so we can do platform detection. If no js, show the
|
||||||
|
@ -309,7 +304,7 @@ def video(ctx, *args, **kwargs):
|
||||||
if ext not in filetypes:
|
if ext not in filetypes:
|
||||||
continue
|
continue
|
||||||
videos[ext] = (v if 'prefix' not in kwargs else
|
videos[ext] = (v if 'prefix' not in kwargs else
|
||||||
urlparse.urljoin(kwargs['prefix'], v))
|
urllib.parse.urljoin(kwargs['prefix'], v))
|
||||||
|
|
||||||
if not videos:
|
if not videos:
|
||||||
return ''
|
return ''
|
||||||
|
@ -397,7 +392,8 @@ def donate_url(ctx, source=''):
|
||||||
donate_url_params = settings.DONATE_PARAMS.get(
|
donate_url_params = settings.DONATE_PARAMS.get(
|
||||||
locale, settings.DONATE_PARAMS['en-US'])
|
locale, settings.DONATE_PARAMS['en-US'])
|
||||||
|
|
||||||
return settings.DONATE_LINK.format(locale=locale, presets=donate_url_params['presets'],
|
return settings.DONATE_LINK.format(
|
||||||
|
locale=locale, presets=donate_url_params['presets'],
|
||||||
default=donate_url_params['default'], source=source,
|
default=donate_url_params['default'], source=source,
|
||||||
currency=donate_url_params['currency'])
|
currency=donate_url_params['currency'])
|
||||||
|
|
||||||
|
@ -530,7 +526,7 @@ def htmlattr(_list, **kwargs):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for tag in _list:
|
for tag in _list:
|
||||||
for attr, value in kwargs.iteritems():
|
for attr, value in kwargs.items():
|
||||||
tag[attr] = value
|
tag[attr] = value
|
||||||
|
|
||||||
return _list
|
return _list
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import urllib
|
import urllib.parse
|
||||||
|
|
||||||
from django_jinja import library
|
from django_jinja import library
|
||||||
|
|
||||||
|
@ -29,7 +27,7 @@ def format_tweet_body(tweet):
|
||||||
hash = hashtags['text']
|
hash = hashtags['text']
|
||||||
text = text.replace('#' + hash,
|
text = text.replace('#' + hash,
|
||||||
('<a href="https://twitter.com/search?q=%s&src=hash"'
|
('<a href="https://twitter.com/search?q=%s&src=hash"'
|
||||||
' class="hash">#%s</a>' % ('%23' + urllib.quote(hash.encode('utf8')),
|
' class="hash">#%s</a>' % ('%23' + urllib.parse.quote(hash.encode('utf8')),
|
||||||
hash)))
|
hash)))
|
||||||
|
|
||||||
# Mentions (@someone)
|
# Mentions (@someone)
|
||||||
|
@ -37,7 +35,7 @@ def format_tweet_body(tweet):
|
||||||
name = user['screen_name']
|
name = user['screen_name']
|
||||||
text = text.replace('@' + name,
|
text = text.replace('@' + name,
|
||||||
('<a href="https://twitter.com/%s" class="mention">@%s</a>'
|
('<a href="https://twitter.com/%s" class="mention">@%s</a>'
|
||||||
% (urllib.quote(name.encode('utf8')), name)))
|
% (urllib.parse.quote(name.encode('utf8')), name)))
|
||||||
|
|
||||||
# URLs
|
# URLs
|
||||||
for url in entities['urls']:
|
for url in entities['urls']:
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
|
@ -24,7 +22,7 @@ class TestCredits(TestCase):
|
||||||
Walter Sobchak,Sobchak
|
Walter Sobchak,Sobchak
|
||||||
Theodore Donald Kerabatsos,Kerabatsos
|
Theodore Donald Kerabatsos,Kerabatsos
|
||||||
Tantek Çelik,Çelik
|
Tantek Çelik,Çelik
|
||||||
""".encode('utf8')))
|
"""))
|
||||||
self.assertListEqual(self.credits_file.rows, [
|
self.assertListEqual(self.credits_file.rows, [
|
||||||
['Tantek Çelik', 'CELIK'],
|
['Tantek Çelik', 'CELIK'],
|
||||||
['The Dude', 'DUDE'],
|
['The Dude', 'DUDE'],
|
||||||
|
@ -63,7 +61,7 @@ class TestCredits(TestCase):
|
||||||
Walter Sobchak,Sobchak
|
Walter Sobchak,Sobchak
|
||||||
Theodore Donald Kerabatsos,Kerabatsos
|
Theodore Donald Kerabatsos,Kerabatsos
|
||||||
Tantek Çelik,Çelik
|
Tantek Çelik,Çelik
|
||||||
""".encode('utf8')))
|
"""))
|
||||||
good_names = OrderedDict()
|
good_names = OrderedDict()
|
||||||
good_names['C'] = ['Tantek Çelik']
|
good_names['C'] = ['Tantek Çelik']
|
||||||
good_names['D'] = ['The Dude']
|
good_names['D'] = ['The Dude']
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.conf import settings
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
import pytest
|
||||||
from django_jinja.backend import Jinja2
|
from django_jinja.backend import Jinja2
|
||||||
from jinja2 import Markup
|
from jinja2 import Markup
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
@ -205,20 +206,18 @@ class TestVideoTag(TestCase):
|
||||||
assert doc('video source').length == 3
|
assert doc('video source').length == 3
|
||||||
|
|
||||||
# Extensions in the right order?
|
# Extensions in the right order?
|
||||||
for i, ext in enumerate(('webm', 'ogv', 'mp4')):
|
extensions = [os.path.splitext(el.attrib['src'])[1] for el in doc('video source')]
|
||||||
assert doc('video source:eq(%s)' % i).attr('src').endswith(ext)
|
assert extensions == ['.webm', '.ogv', '.mp4']
|
||||||
|
|
||||||
def test_prefix(self):
|
def test_prefix(self):
|
||||||
# Prefix should be applied to all videos.
|
# Prefix should be applied to all videos.
|
||||||
doc = pq(self._render("{{ video('meh.mp4', 'meh.ogv', "
|
doc = pq(self._render(
|
||||||
"prefix='http://example.com/blah/') }}"))
|
"{{ video('meh.mp4', 'meh.ogv', prefix='http://example.com/blah/') }}")
|
||||||
expected = ('http://example.com/blah/meh.ogv',
|
)
|
||||||
'http://example.com/blah/meh.mp4')
|
assert [el.attrib['src'] for el in doc('video source')] == [
|
||||||
|
'http://example.com/blah/meh.ogv',
|
||||||
assert doc('video source').length == 2
|
'http://example.com/blah/meh.mp4',
|
||||||
|
]
|
||||||
for i in xrange(2):
|
|
||||||
assert doc('video source:eq(%s)' % i).attr('src') == expected[i]
|
|
||||||
|
|
||||||
def test_fileformats(self):
|
def test_fileformats(self):
|
||||||
# URLs ending in strange extensions are ignored.
|
# URLs ending in strange extensions are ignored.
|
||||||
|
@ -229,8 +228,8 @@ class TestVideoTag(TestCase):
|
||||||
|
|
||||||
assert doc('video source').length == 2
|
assert doc('video source').length == 2
|
||||||
|
|
||||||
for i, ext in enumerate(('webm', 'ogv')):
|
extensions = [os.path.splitext(el.attrib['src'])[1] for el in doc('video source')]
|
||||||
assert doc('video source:eq(%s)' % i).attr('src').endswith(ext)
|
assert extensions == ['.webm', '.ogv']
|
||||||
|
|
||||||
|
|
||||||
@override_settings(STATIC_URL='/media/')
|
@override_settings(STATIC_URL='/media/')
|
||||||
|
@ -344,8 +343,10 @@ class TestPressBlogUrl(TestCase):
|
||||||
assert self._render('oc') == 'https://blog.mozilla.org/press/'
|
assert self._render('oc') == 'https://blog.mozilla.org/press/'
|
||||||
|
|
||||||
|
|
||||||
@override_settings(DONATE_LINK=TEST_DONATE_LINK,
|
@override_settings(
|
||||||
DONATE_PARAMS=TEST_DONATE_PARAMS)
|
DONATE_LINK=TEST_DONATE_LINK,
|
||||||
|
DONATE_PARAMS=TEST_DONATE_PARAMS,
|
||||||
|
)
|
||||||
class TestDonateUrl(TestCase):
|
class TestDonateUrl(TestCase):
|
||||||
rf = RequestFactory()
|
rf = RequestFactory()
|
||||||
|
|
||||||
|
@ -639,22 +640,22 @@ def test_f_unicode():
|
||||||
assert s == u'\xe9 baz'
|
assert s == u'\xe9 baz'
|
||||||
|
|
||||||
|
|
||||||
def test_f_markup():
|
format_string = 'Hello <b>{0}</b>'
|
||||||
format_string = 'Hello <b>{0}</b>'
|
format_markup = Markup(format_string)
|
||||||
val_string = '<em>Steve</em>'
|
val_string = '<em>Steve</em>'
|
||||||
|
val_markup = Markup(val_string)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('f, v', [
|
||||||
|
(format_string, val_string),
|
||||||
|
(format_string, val_markup),
|
||||||
|
(format_markup, val_string),
|
||||||
|
(format_markup, val_markup),
|
||||||
|
])
|
||||||
|
def test_f_markup(f, v):
|
||||||
expect = 'Hello <b><em>Steve</em></b>'
|
expect = 'Hello <b><em>Steve</em></b>'
|
||||||
|
s = render('{{ fmt|f(val) }}', {'fmt': f, 'val': v})
|
||||||
def markup_render(f, v):
|
assert expect == s
|
||||||
return render('{{ fmt|f(val) }}', {'fmt': f, 'val': v})
|
|
||||||
|
|
||||||
assert markup_render(format_string, val_string) == expect
|
|
||||||
|
|
||||||
format_markup = Markup(format_string)
|
|
||||||
val_markup = Markup(val_string)
|
|
||||||
|
|
||||||
assert markup_render(format_string, val_markup) == expect
|
|
||||||
assert markup_render(format_markup, val_string) == expect
|
|
||||||
assert markup_render(format_markup, val_markup) == expect
|
|
||||||
|
|
||||||
|
|
||||||
def test_datetime():
|
def test_datetime():
|
||||||
|
@ -687,7 +688,7 @@ def test_ifeq():
|
||||||
|
|
||||||
def test_csrf():
|
def test_csrf():
|
||||||
s = render('{{ csrf() }}', {'csrf_token': 'fffuuu'})
|
s = render('{{ csrf() }}', {'csrf_token': 'fffuuu'})
|
||||||
csrf = "<input type='hidden' name='csrfmiddlewaretoken' value='fffuuu' />"
|
csrf = '<input type="hidden" name="csrfmiddlewaretoken" value="fffuuu">'
|
||||||
assert csrf in s
|
assert csrf in s
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,12 @@
|
||||||
import json
|
import json
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
import tweepy
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
import tweepy
|
from bedrock.mozorg.templatetags.social_widgets import (format_tweet_body,
|
||||||
|
format_tweet_timestamp)
|
||||||
from bedrock.mozorg.tests import TestCase
|
from bedrock.mozorg.tests import TestCase
|
||||||
from bedrock.mozorg.templatetags.social_widgets import * # noqa
|
|
||||||
|
|
||||||
|
|
||||||
TEST_FILES_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')
|
'test_files')
|
||||||
|
|
|
@ -26,7 +26,7 @@ class TestPageNode(TestCase):
|
||||||
"""
|
"""
|
||||||
child = PageNode('test', path='asdf')
|
child = PageNode('test', path='asdf')
|
||||||
PageRoot('test', path='blah', children=[
|
PageRoot('test', path='blah', children=[
|
||||||
PageNode('test', path='whoo', children=[child])
|
PageNode('test', path='whoo', children=[child])
|
||||||
])
|
])
|
||||||
assert child.full_path == 'blah/whoo/asdf'
|
assert child.full_path == 'blah/whoo/asdf'
|
||||||
|
|
||||||
|
@ -217,9 +217,4 @@ class TestPageRoot(TestCase):
|
||||||
# Mocking properties
|
# Mocking properties
|
||||||
page.__get__ = lambda mock, self, cls: self.display_name
|
page.__get__ = lambda mock, self, cls: self.display_name
|
||||||
|
|
||||||
args = root.as_urlpatterns()
|
assert root.as_urlpatterns() == ['root', 'child1', 'child2']
|
||||||
|
|
||||||
assert 'child1' in args
|
|
||||||
assert 'child2' in args
|
|
||||||
assert 'root' in args
|
|
||||||
assert 'parent' not in args
|
|
||||||
|
|
|
@ -41,9 +41,11 @@ class TestHostnameMiddleware(TestCase):
|
||||||
self.middleware.process_response(self.request, self.response)
|
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_CLASSES=(list(settings.MIDDLEWARE_CLASSES) +
|
@override_settings(
|
||||||
['bedrock.mozorg.middleware.HostnameMiddleware']),
|
MIDDLEWARE=(list(settings.MIDDLEWARE) + ['bedrock.mozorg.middleware.HostnameMiddleware']),
|
||||||
HOSTNAME='foobar', CLUSTER_NAME='el-dudarino')
|
HOSTNAME='foobar',
|
||||||
|
CLUSTER_NAME='el-dudarino',
|
||||||
|
)
|
||||||
def test_request(self):
|
def test_request(self):
|
||||||
response = self.client.get('/en-US/')
|
response = self.client.get('/en-US/')
|
||||||
self.assertEqual(response['X-Backend-Server'], 'foobar.el-dudarino')
|
self.assertEqual(response['X-Backend-Server'], 'foobar.el-dudarino')
|
||||||
|
|
|
@ -25,18 +25,18 @@ class TestViews(TestCase):
|
||||||
"""The download button should have the funnelcake ID."""
|
"""The download button should have the funnelcake ID."""
|
||||||
with self.activate('en-US'):
|
with self.activate('en-US'):
|
||||||
resp = self.client.get(reverse('mozorg.home'), {'f': '5'})
|
resp = self.client.get(reverse('mozorg.home'), {'f': '5'})
|
||||||
assert 'product=firefox-stub-f5&' in resp.content
|
assert b'product=firefox-stub-f5&' in resp.content
|
||||||
|
|
||||||
def test_download_button_bad_funnelcake(self):
|
def test_download_button_bad_funnelcake(self):
|
||||||
"""The download button should not have a bad funnelcake ID."""
|
"""The download button should not have a bad funnelcake ID."""
|
||||||
with self.activate('en-US'):
|
with self.activate('en-US'):
|
||||||
resp = self.client.get(reverse('mozorg.home'), {'f': '5dude'})
|
resp = self.client.get(reverse('mozorg.home'), {'f': '5dude'})
|
||||||
assert 'product=firefox-stub&' in resp.content
|
assert b'product=firefox-stub&' in resp.content
|
||||||
assert 'product=firefox-stub-f5dude&' not in resp.content
|
assert b'product=firefox-stub-f5dude&' not in resp.content
|
||||||
|
|
||||||
resp = self.client.get(reverse('mozorg.home'), {'f': '999999999'})
|
resp = self.client.get(reverse('mozorg.home'), {'f': '999999999'})
|
||||||
assert 'product=firefox-stub&' in resp.content
|
assert b'product=firefox-stub&' in resp.content
|
||||||
assert 'product=firefox-stub-f999999999&' not in resp.content
|
assert b'product=firefox-stub-f999999999&' not in resp.content
|
||||||
|
|
||||||
|
|
||||||
class TestRobots(TestCase):
|
class TestRobots(TestCase):
|
||||||
|
|
|
@ -12,9 +12,10 @@ from bedrock.mozorg.util import page
|
||||||
def mock_view(request):
|
def mock_view(request):
|
||||||
return HttpResponse('test')
|
return HttpResponse('test')
|
||||||
|
|
||||||
urlpatterns = (
|
|
||||||
|
urlpatterns = [
|
||||||
url(r'', include('%s.urls' % settings.PROJECT_MODULE)),
|
url(r'', include('%s.urls' % settings.PROJECT_MODULE)),
|
||||||
|
|
||||||
# Used by test_helper
|
# Used by test_helper
|
||||||
page('base', 'base-resp.html'),
|
page('base', 'base-resp.html'),
|
||||||
)
|
]
|
||||||
|
|
|
@ -296,7 +296,7 @@ urlpatterns = (
|
||||||
url(r'^oauth/fxa/error/$', views.oauth_fxa_error, name='mozorg.oauth.fxa-error'),
|
url(r'^oauth/fxa/error/$', views.oauth_fxa_error, name='mozorg.oauth.fxa-error'),
|
||||||
|
|
||||||
page('plugincheck', 'mozorg/plugincheck.html'),
|
page('plugincheck', 'mozorg/plugincheck.html'),
|
||||||
url(r'^robots.txt$', views.Robots.as_view(), name='robots.txt'),
|
url(r'^robots\.txt$', views.Robots.as_view(), name='robots.txt'),
|
||||||
url('^technology/$', views.TechnologyView.as_view(), name='mozorg.technology'),
|
url('^technology/$', views.TechnologyView.as_view(), name='mozorg.technology'),
|
||||||
page('technology/what-is-a-browser', 'mozorg/what-is-a-browser.html'),
|
page('technology/what-is-a-browser', 'mozorg/what-is-a-browser.html'),
|
||||||
page('technology/update-your-browser', 'mozorg/update-browser.html'),
|
page('technology/update-your-browser', 'mozorg/update-browser.html'),
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import render as django_render
|
from django.shortcuts import render as django_render
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
@ -35,16 +33,6 @@ FXA_CLIENTS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class HttpResponseJSON(HttpResponse):
|
|
||||||
def __init__(self, data, status=None, cors=False):
|
|
||||||
super(HttpResponseJSON, self).__init__(content=json.dumps(data),
|
|
||||||
content_type='application/json',
|
|
||||||
status=status)
|
|
||||||
|
|
||||||
if cors:
|
|
||||||
self['Access-Control-Allow-Origin'] = '*'
|
|
||||||
|
|
||||||
|
|
||||||
def page(name, tmpl, decorators=None, url_name=None, **kwargs):
|
def page(name, tmpl, decorators=None, url_name=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Define a bedrock page.
|
Define a bedrock page.
|
||||||
|
@ -200,7 +188,7 @@ def get_fxa_oauth_token(code):
|
||||||
try:
|
try:
|
||||||
token_resp = oauthClient.trade_code(code, client_id=settings.FXA_OAUTH_CLIENT_ID, client_secret=settings.FXA_OAUTH_CLIENT_SECRET)
|
token_resp = oauthClient.trade_code(code, client_id=settings.FXA_OAUTH_CLIENT_ID, client_secret=settings.FXA_OAUTH_CLIENT_SECRET)
|
||||||
token = token_resp['access_token']
|
token = token_resp['access_token']
|
||||||
except:
|
except Exception:
|
||||||
token = None
|
token = None
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
@ -212,7 +200,7 @@ def get_fxa_profile_email(token):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
email = profileClient.get_email(token)
|
email = profileClient.get_email(token)
|
||||||
except:
|
except Exception:
|
||||||
email = None
|
email = None
|
||||||
|
|
||||||
return email
|
return email
|
||||||
|
@ -229,5 +217,5 @@ def fxa_concert_rsvp(email, isFx):
|
||||||
try:
|
try:
|
||||||
basket.request('post', 'fxa-concerts-rsvp', data=data)
|
basket.request('post', 'fxa-concerts-rsvp', data=data)
|
||||||
return True
|
return True
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -4,15 +4,16 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from commonware.decorators import xframe_allow
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.urlresolvers import reverse
|
from django.http import Http404, HttpResponseRedirect, JsonResponse
|
||||||
from django.http import Http404, HttpResponseRedirect
|
|
||||||
from django.shortcuts import render as django_render
|
from django.shortcuts import render as django_render
|
||||||
|
from django.urls import reverse
|
||||||
from django.views.decorators.cache import cache_page, never_cache
|
from django.views.decorators.cache import cache_page, never_cache
|
||||||
from django.views.decorators.http import require_safe
|
from django.views.decorators.http import require_safe
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
from lib import l10n_utils
|
||||||
from commonware.decorators import xframe_allow
|
from lib.l10n_utils.dotlang import lang_file_is_active
|
||||||
|
|
||||||
from bedrock.base.waffle import switch
|
from bedrock.base.waffle import switch
|
||||||
from bedrock.contentcards.models import get_page_content_cards
|
from bedrock.contentcards.models import get_page_content_cards
|
||||||
|
@ -22,13 +23,10 @@ from bedrock.mozorg.models import ContributorActivity
|
||||||
from bedrock.mozorg.util import (
|
from bedrock.mozorg.util import (
|
||||||
fxa_concert_rsvp,
|
fxa_concert_rsvp,
|
||||||
get_fxa_oauth_token,
|
get_fxa_oauth_token,
|
||||||
get_fxa_profile_email,
|
get_fxa_profile_email
|
||||||
HttpResponseJSON
|
|
||||||
)
|
)
|
||||||
from bedrock.pocketfeed.models import PocketArticle
|
from bedrock.pocketfeed.models import PocketArticle
|
||||||
from bedrock.wordpress.views import BlogPostsView
|
from bedrock.wordpress.views import BlogPostsView
|
||||||
from lib import l10n_utils
|
|
||||||
from lib.l10n_utils.dotlang import lang_file_is_active
|
|
||||||
|
|
||||||
credits_file = CreditsFile('credits')
|
credits_file = CreditsFile('credits')
|
||||||
forums_file = ForumsFile('forums')
|
forums_file = ForumsFile('forums')
|
||||||
|
@ -60,7 +58,9 @@ def mozid_data_view(request, source_name):
|
||||||
'totalactive': activity['total__sum'],
|
'totalactive': activity['total__sum'],
|
||||||
'new': activity['new__sum']} for activity in qs]
|
'new': activity['new__sum']} for activity in qs]
|
||||||
|
|
||||||
return HttpResponseJSON(data, cors=True)
|
response = JsonResponse(data, safe=False)
|
||||||
|
response['Access-Control-Allow-Origin'] = '*'
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@xframe_allow
|
@xframe_allow
|
||||||
|
|
|
@ -48,7 +48,7 @@ def get_lang_choices(newsletters=None):
|
||||||
lang_name = product_details.languages[lang]['native']
|
lang_name = product_details.languages[lang]['native']
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
locale = [loc for loc in product_details.languages.keys()
|
locale = [loc for loc in product_details.languages
|
||||||
if loc.startswith(lang)][0]
|
if loc.startswith(lang)][0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
continue
|
continue
|
||||||
|
@ -120,7 +120,7 @@ class CountrySelectForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, locale, *args, **kwargs):
|
def __init__(self, locale, *args, **kwargs):
|
||||||
regions = product_details.get_regions(locale)
|
regions = product_details.get_regions(locale)
|
||||||
regions = sorted(regions.iteritems(), key=itemgetter(1))
|
regions = sorted(iter(regions.items()), key=itemgetter(1))
|
||||||
super(CountrySelectForm, self).__init__(*args, **kwargs)
|
super(CountrySelectForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['country'].choices = regions
|
self.fields['country'].choices = regions
|
||||||
|
|
||||||
|
@ -149,7 +149,7 @@ class ManageSubscriptionsForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, locale, *args, **kwargs):
|
def __init__(self, locale, *args, **kwargs):
|
||||||
regions = product_details.get_regions(locale)
|
regions = product_details.get_regions(locale)
|
||||||
regions = sorted(regions.iteritems(), key=itemgetter(1))
|
regions = sorted(iter(regions.items()), key=itemgetter(1))
|
||||||
lang_choices = get_lang_choices()
|
lang_choices = get_lang_choices()
|
||||||
languages = [x[0] for x in lang_choices]
|
languages = [x[0] for x in lang_choices]
|
||||||
|
|
||||||
|
@ -249,7 +249,7 @@ class NewsletterFooterForm(forms.Form):
|
||||||
# out which languages to list in the form.
|
# out which languages to list in the form.
|
||||||
def __init__(self, newsletters, locale, data=None, *args, **kwargs):
|
def __init__(self, newsletters, locale, data=None, *args, **kwargs):
|
||||||
regions = product_details.get_regions(locale)
|
regions = product_details.get_regions(locale)
|
||||||
regions = sorted(regions.iteritems(), key=itemgetter(1))
|
regions = sorted(iter(regions.items()), key=itemgetter(1))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
newsletters = validate_newsletters(newsletters)
|
newsletters = validate_newsletters(newsletters)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
import basket
|
import basket
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django_extensions.db.fields.json
|
import django_extensions.db.fields.json
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ class NewsletterManager(models.Manager):
|
||||||
|
|
||||||
self.all().delete()
|
self.all().delete()
|
||||||
count = 0
|
count = 0
|
||||||
for slug, data in new_data.iteritems():
|
for slug, data in new_data.items():
|
||||||
self.create(
|
self.create(
|
||||||
slug=slug,
|
slug=slug,
|
||||||
data=data,
|
data=data,
|
||||||
|
@ -40,5 +40,5 @@ class Newsletter(models.Model):
|
||||||
|
|
||||||
objects = NewsletterManager()
|
objects = NewsletterManager()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return self.slug
|
return self.slug
|
||||||
|
|
|
@ -3,8 +3,8 @@ from bedrock.redirects.util import redirect
|
||||||
|
|
||||||
redirectpatterns = (
|
redirectpatterns = (
|
||||||
# bug 926629
|
# bug 926629
|
||||||
redirect(r'^newsletter/about_mobile(?:/(?:index.html)?)?$', 'newsletter.subscribe'),
|
redirect(r'^newsletter/about_mobile(?:/(?:index\.html)?)?$', 'newsletter.subscribe'),
|
||||||
redirect(r'^newsletter/about_mozilla(?:/(?:index.html)?)?$', 'mozorg.contribute.index'),
|
redirect(r'^newsletter/about_mozilla(?:/(?:index\.html)?)?$', 'mozorg.contribute.index'),
|
||||||
redirect(r'^newsletter/new(?:/(?:index.html)?)?$', 'newsletter.subscribe'),
|
redirect(r'^newsletter/new(?:/(?:index\.html)?)?$', 'newsletter.subscribe'),
|
||||||
redirect(r'^newsletter/ios(?:/(?:index.html)?)?$', 'firefox.mobile'),
|
redirect(r'^newsletter/ios(?:/(?:index\.html)?)?$', 'firefox.mobile'),
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,7 +30,7 @@ class TestViews(TestCase):
|
||||||
|
|
||||||
@patch('bedrock.newsletter.views.l10n_utils.render')
|
@patch('bedrock.newsletter.views.l10n_utils.render')
|
||||||
def test_updated_allows_good_tokens(self, mock_render):
|
def test_updated_allows_good_tokens(self, mock_render):
|
||||||
token = unicode(uuid.uuid4())
|
token = str(uuid.uuid4())
|
||||||
req = self.rf.get('/', {'token': token, 'unsub': 1})
|
req = self.rf.get('/', {'token': token, 'unsub': 1})
|
||||||
updated(req)
|
updated(req)
|
||||||
self.assertEqual(mock_render.call_args[0][2]['token'], token)
|
self.assertEqual(mock_render.call_args[0][2]['token'], token)
|
||||||
|
@ -53,7 +53,7 @@ class TestViews(TestCase):
|
||||||
@patch('basket.base.request')
|
@patch('basket.base.request')
|
||||||
class TestExistingNewsletterView(TestCase):
|
class TestExistingNewsletterView(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.token = unicode(uuid.uuid4())
|
self.token = str(uuid.uuid4())
|
||||||
self.user = {
|
self.user = {
|
||||||
'newsletters': [u'mozilla-and-you'],
|
'newsletters': [u'mozilla-and-you'],
|
||||||
'token': self.token,
|
'token': self.token,
|
||||||
|
@ -129,7 +129,7 @@ class TestExistingNewsletterView(TestCase):
|
||||||
# or they are marked 'show' and 'active' in the settings
|
# or they are marked 'show' and 'active' in the settings
|
||||||
get_newsletters.return_value = newsletters
|
get_newsletters.return_value = newsletters
|
||||||
# Find a newsletter without 'show' and subscribe the user to it
|
# Find a newsletter without 'show' and subscribe the user to it
|
||||||
for newsletter, data in newsletters.iteritems():
|
for newsletter, data in newsletters.items():
|
||||||
if not data.get('show', False):
|
if not data.get('show', False):
|
||||||
self.user['newsletters'] = [newsletter]
|
self.user['newsletters'] = [newsletter]
|
||||||
break
|
break
|
||||||
|
@ -148,10 +148,10 @@ class TestExistingNewsletterView(TestCase):
|
||||||
|
|
||||||
shown = set([form.initial['newsletter'] for form in forms])
|
shown = set([form.initial['newsletter'] for form in forms])
|
||||||
inactive = set([newsletter for newsletter, data
|
inactive = set([newsletter for newsletter, data
|
||||||
in newsletters.iteritems()
|
in newsletters.items()
|
||||||
if not data.get('active', False)])
|
if not data.get('active', False)])
|
||||||
to_show = set([newsletter for newsletter, data
|
to_show = set([newsletter for newsletter, data
|
||||||
in newsletters.iteritems()
|
in newsletters.items()
|
||||||
if data.get('show', False)]) - inactive
|
if data.get('show', False)]) - inactive
|
||||||
subscribed = set(self.user['newsletters'])
|
subscribed = set(self.user['newsletters'])
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ class TestExistingNewsletterView(TestCase):
|
||||||
|
|
||||||
def test_get_user_not_found(self, mock_basket_request):
|
def test_get_user_not_found(self, mock_basket_request):
|
||||||
# Token in URL but not a valid token - should redirect to recovery
|
# Token in URL but not a valid token - should redirect to recovery
|
||||||
rand_token = unicode(uuid.uuid4())
|
rand_token = str(uuid.uuid4())
|
||||||
url = reverse('newsletter.existing.token', args=(rand_token,))
|
url = reverse('newsletter.existing.token', args=(rand_token,))
|
||||||
with patch.multiple('basket',
|
with patch.multiple('basket',
|
||||||
request=DEFAULT) as basket_patches:
|
request=DEFAULT) as basket_patches:
|
||||||
|
@ -203,7 +203,7 @@ class TestExistingNewsletterView(TestCase):
|
||||||
def test_post_user_not_found(self, mock_basket_request):
|
def test_post_user_not_found(self, mock_basket_request):
|
||||||
# User submits form and passed token, but no user was found
|
# User submits form and passed token, but no user was found
|
||||||
# Should issue message and redirect to recovery
|
# Should issue message and redirect to recovery
|
||||||
rand_token = unicode(uuid.uuid4())
|
rand_token = str(uuid.uuid4())
|
||||||
url = reverse('newsletter.existing.token', args=(rand_token,))
|
url = reverse('newsletter.existing.token', args=(rand_token,))
|
||||||
with patch.multiple('basket',
|
with patch.multiple('basket',
|
||||||
update_user=DEFAULT,
|
update_user=DEFAULT,
|
||||||
|
@ -250,10 +250,9 @@ class TestExistingNewsletterView(TestCase):
|
||||||
# Should have called update_user with subscription list
|
# Should have called update_user with subscription list
|
||||||
self.assertEqual(1, basket_patches['update_user'].call_count)
|
self.assertEqual(1, basket_patches['update_user'].call_count)
|
||||||
kwargs = basket_patches['update_user'].call_args[1]
|
kwargs = basket_patches['update_user'].call_args[1]
|
||||||
self.assertEqual(
|
self.assertEqual(set(kwargs), set(['newsletters', 'lang']))
|
||||||
{'newsletters': u'mozilla-and-you,firefox-tips', 'lang': u'en'},
|
self.assertEqual(kwargs['lang'], 'en')
|
||||||
kwargs
|
self.assertEqual(set(kwargs['newsletters'].split(',')), set(['mozilla-and-you', 'firefox-tips']))
|
||||||
)
|
|
||||||
# Should not have called unsubscribe
|
# 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
|
# Should not have called subscribe
|
||||||
|
@ -266,7 +265,7 @@ class TestExistingNewsletterView(TestCase):
|
||||||
def test_unsubscribing(self, get_newsletters, mock_basket_request):
|
def test_unsubscribing(self, get_newsletters, mock_basket_request):
|
||||||
get_newsletters.return_value = newsletters
|
get_newsletters.return_value = newsletters
|
||||||
# They unsubscribe from the one newsletter they're subscribed to
|
# They unsubscribe from the one newsletter they're subscribed to
|
||||||
self.data['form-0-subscribed_radio'] = u'false'
|
self.data['form-0-subscribed_radio'] = u'False'
|
||||||
url = reverse('newsletter.existing.token', args=(self.token,))
|
url = reverse('newsletter.existing.token', args=(self.token,))
|
||||||
with patch.multiple('basket',
|
with patch.multiple('basket',
|
||||||
update_user=DEFAULT,
|
update_user=DEFAULT,
|
||||||
|
@ -413,7 +412,7 @@ class TestExistingNewsletterView(TestCase):
|
||||||
|
|
||||||
class TestConfirmView(TestCase):
|
class TestConfirmView(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.token = unicode(uuid.uuid4())
|
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):
|
def test_normal(self):
|
||||||
|
@ -469,7 +468,7 @@ class TestConfirmView(TestCase):
|
||||||
|
|
||||||
class TestSetCountryView(TestCase):
|
class TestSetCountryView(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.token = unicode(uuid.uuid4())
|
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):
|
def test_normal_submit(self):
|
||||||
|
@ -509,8 +508,8 @@ class TestRecoveryView(TestCase):
|
||||||
def test_unknown_email(self, mock_basket):
|
def test_unknown_email(self, mock_basket):
|
||||||
"""Unknown email addresses give helpful error message"""
|
"""Unknown email addresses give helpful error message"""
|
||||||
data = {'email': 'unknown@example.com'}
|
data = {'email': 'unknown@example.com'}
|
||||||
mock_basket.side_effect = basket.BasketException(status_code=404,
|
mock_basket.side_effect = basket.BasketException(
|
||||||
code=basket.errors.BASKET_UNKNOWN_EMAIL)
|
status_code=404, code=basket.errors.BASKET_UNKNOWN_EMAIL)
|
||||||
rsp = self.client.post(self.url, data)
|
rsp = self.client.post(self.url, data)
|
||||||
self.assertTrue(mock_basket.called)
|
self.assertTrue(mock_basket.called)
|
||||||
self.assertEqual(200, rsp.status_code)
|
self.assertEqual(200, rsp.status_code)
|
||||||
|
@ -652,7 +651,7 @@ class TestNewsletterSubscribe(TestCase):
|
||||||
resp = self.ajax_request(data)
|
resp = self.ajax_request(data)
|
||||||
resp_data = json.loads(resp.content)
|
resp_data = json.loads(resp.content)
|
||||||
self.assertFalse(resp_data['success'])
|
self.assertFalse(resp_data['success'])
|
||||||
self.assertEqual(resp_data['errors'][0], unicode(invalid_email_address))
|
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):
|
def test_returns_ajax_basket_error(self, subscribe_mock):
|
||||||
|
@ -668,7 +667,7 @@ class TestNewsletterSubscribe(TestCase):
|
||||||
resp = self.ajax_request(data)
|
resp = self.ajax_request(data)
|
||||||
resp_data = json.loads(resp.content)
|
resp_data = json.loads(resp.content)
|
||||||
self.assertFalse(resp_data['success'])
|
self.assertFalse(resp_data['success'])
|
||||||
self.assertEqual(resp_data['errors'][0], unicode(general_error))
|
self.assertEqual(resp_data['errors'][0], str(general_error))
|
||||||
|
|
||||||
def test_shows_normal_form(self):
|
def test_shows_normal_form(self):
|
||||||
"""A normal GET should show the form."""
|
"""A normal GET should show the form."""
|
||||||
|
|
|
@ -19,9 +19,9 @@ def get_languages_for_newsletters(newsletters=None):
|
||||||
"""
|
"""
|
||||||
all_newsletters = get_newsletters()
|
all_newsletters = get_newsletters()
|
||||||
if newsletters is None:
|
if newsletters is None:
|
||||||
newsletters = all_newsletters.values()
|
newsletters = list(all_newsletters.values())
|
||||||
else:
|
else:
|
||||||
if isinstance(newsletters, basestring):
|
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]
|
newsletters = [all_newsletters.get(nl, {}) for nl in newsletters]
|
||||||
|
|
||||||
|
|
|
@ -8,34 +8,30 @@ from cgi import escape
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.forms.formsets import formset_factory
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.views.decorators.cache import never_cache
|
|
||||||
|
|
||||||
import basket
|
import basket
|
||||||
import basket.errors
|
import basket.errors
|
||||||
import commonware.log
|
import commonware.log
|
||||||
from jinja2 import Markup
|
|
||||||
|
|
||||||
import lib.l10n_utils as l10n_utils
|
import lib.l10n_utils as l10n_utils
|
||||||
import requests
|
import requests
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.forms.formsets import formset_factory
|
||||||
|
from django.http import HttpResponseRedirect, JsonResponse
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.views.decorators.cache import never_cache
|
||||||
|
from jinja2 import Markup
|
||||||
|
from lib.l10n_utils.dotlang import _, _lazy
|
||||||
|
|
||||||
from bedrock.base import waffle
|
from bedrock.base import waffle
|
||||||
from lib.l10n_utils.dotlang import _, _lazy
|
|
||||||
from bedrock.base.urlresolvers import reverse
|
from bedrock.base.urlresolvers import reverse
|
||||||
|
|
||||||
from .forms import (CountrySelectForm, EmailForm, ManageSubscriptionsForm,
|
|
||||||
NewsletterForm, NewsletterFooterForm)
|
|
||||||
# Cannot use short "from . import utils" because we need to mock
|
# Cannot use short "from . import utils" because we need to mock
|
||||||
# utils.get_newsletters in our tests
|
# utils.get_newsletters in our tests
|
||||||
from bedrock.base.views import get_geo_from_request
|
from bedrock.base.views import get_geo_from_request
|
||||||
from bedrock.mozorg.util import HttpResponseJSON
|
|
||||||
from bedrock.newsletter import utils
|
from bedrock.newsletter import utils
|
||||||
|
|
||||||
|
from .forms import (CountrySelectForm, EmailForm, ManageSubscriptionsForm,
|
||||||
|
NewsletterFooterForm, NewsletterForm)
|
||||||
|
|
||||||
log = commonware.log.getLogger('b.newsletter')
|
log = commonware.log.getLogger('b.newsletter')
|
||||||
|
|
||||||
|
@ -346,7 +342,7 @@ def existing(request, token=None):
|
||||||
# Figure out which newsletters to display, and whether to show them
|
# Figure out which newsletters to display, and whether to show them
|
||||||
# as already subscribed.
|
# as already subscribed.
|
||||||
initial = []
|
initial = []
|
||||||
for newsletter, data in newsletter_data.iteritems():
|
for newsletter, data in newsletter_data.items():
|
||||||
# Only show a newsletter if it has ['active'] == True and
|
# Only show a newsletter if it has ['active'] == True and
|
||||||
# ['show'] == True or the user is already subscribed
|
# ['show'] == True or the user is already subscribed
|
||||||
if not data.get('active', False):
|
if not data.get('active', False):
|
||||||
|
@ -488,7 +484,7 @@ def existing(request, token=None):
|
||||||
# and each value is the list of newsletter keys that are available in
|
# and each value is the list of newsletter keys that are available in
|
||||||
# that language code.
|
# that language code.
|
||||||
newsletter_languages = defaultdict(list)
|
newsletter_languages = defaultdict(list)
|
||||||
for newsletter, data in newsletter_data.iteritems():
|
for newsletter, data in newsletter_data.items():
|
||||||
for lang in data['languages']:
|
for lang in data['languages']:
|
||||||
newsletter_languages[lang].append(newsletter)
|
newsletter_languages[lang].append(newsletter)
|
||||||
newsletter_languages = mark_safe(json.dumps(newsletter_languages))
|
newsletter_languages = mark_safe(json.dumps(newsletter_languages))
|
||||||
|
@ -567,7 +563,7 @@ def updated(request):
|
||||||
# so we can read them. (Well, except for the free-form reason.)
|
# so we can read them. (Well, except for the free-form reason.)
|
||||||
for i, reason in enumerate(REASONS):
|
for i, reason in enumerate(REASONS):
|
||||||
if _post_or_get(request, 'reason%d' % i):
|
if _post_or_get(request, 'reason%d' % i):
|
||||||
reasons.append(unicode(reason))
|
reasons.append(str(reason))
|
||||||
if _post_or_get(request, 'reason-text-p'):
|
if _post_or_get(request, 'reason-text-p'):
|
||||||
reasons.append(_post_or_get(request, 'reason-text', ''))
|
reasons.append(_post_or_get(request, 'reason-text', ''))
|
||||||
|
|
||||||
|
@ -663,11 +659,11 @@ def newsletter_subscribe(request):
|
||||||
**kwargs)
|
**kwargs)
|
||||||
except basket.BasketException as e:
|
except basket.BasketException as e:
|
||||||
if e.code == basket.errors.BASKET_INVALID_EMAIL:
|
if e.code == basket.errors.BASKET_INVALID_EMAIL:
|
||||||
errors.append(unicode(invalid_email_address))
|
errors.append(str(invalid_email_address))
|
||||||
else:
|
else:
|
||||||
log.exception("Error subscribing %s to newsletter %s" %
|
log.exception("Error subscribing %s to newsletter %s" %
|
||||||
(data['email'], data['newsletters']))
|
(data['email'], data['newsletters']))
|
||||||
errors.append(unicode(general_error))
|
errors.append(str(general_error))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if 'email' in form.errors:
|
if 'email' in form.errors:
|
||||||
|
@ -679,7 +675,7 @@ def newsletter_subscribe(request):
|
||||||
errors.extend(form.errors[fieldname])
|
errors.extend(form.errors[fieldname])
|
||||||
|
|
||||||
# form error messages may contain unsanitized user input
|
# form error messages may contain unsanitized user input
|
||||||
errors = map(escape, errors)
|
errors = list(map(escape, errors))
|
||||||
|
|
||||||
if request.is_ajax():
|
if request.is_ajax():
|
||||||
# return JSON
|
# return JSON
|
||||||
|
@ -691,7 +687,7 @@ def newsletter_subscribe(request):
|
||||||
else:
|
else:
|
||||||
resp = {'success': True}
|
resp = {'success': True}
|
||||||
|
|
||||||
return HttpResponseJSON(resp)
|
return JsonResponse(resp)
|
||||||
else:
|
else:
|
||||||
ctx = {'newsletter_form': form}
|
ctx = {'newsletter_form': form}
|
||||||
if not errors:
|
if not errors:
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import print_function, unicode_literals
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from __future__ import print_function, unicode_literals
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.utils import DatabaseError
|
from django.db.utils import DatabaseError
|
||||||
|
|
||||||
|
@ -62,7 +60,7 @@ class PocketArticleManager(models.Manager):
|
||||||
try:
|
try:
|
||||||
if obj:
|
if obj:
|
||||||
if obj.time_shared != article['time_shared']:
|
if obj.time_shared != article['time_shared']:
|
||||||
for key, value in article.iteritems():
|
for key, value in article.items():
|
||||||
setattr(obj, key, value)
|
setattr(obj, key, value)
|
||||||
obj.save()
|
obj.save()
|
||||||
update_count += 1
|
update_count += 1
|
||||||
|
@ -97,7 +95,7 @@ class PocketArticle(models.Model):
|
||||||
get_latest_by = 'time_shared'
|
get_latest_by = 'time_shared'
|
||||||
ordering = ['-time_shared']
|
ordering = ['-time_shared']
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -20,9 +20,8 @@ def test_get_articles_data(req_mock):
|
||||||
|
|
||||||
api.get_articles_data(count=7)
|
api.get_articles_data(count=7)
|
||||||
|
|
||||||
req_mock.post.assert_called_once_with('test_url',
|
req_mock.post.assert_called_once_with(
|
||||||
json=expected_payload,
|
'test_url', json=expected_payload, timeout=5)
|
||||||
timeout=5)
|
|
||||||
|
|
||||||
|
|
||||||
@patch.object(api, 'requests')
|
@patch.object(api, 'requests')
|
||||||
|
|
|
@ -174,7 +174,7 @@ redirectpatterns = (
|
||||||
redirect(r'^press/mozilla-foundation\.html$',
|
redirect(r'^press/mozilla-foundation\.html$',
|
||||||
'https://blog.mozilla.org/press/2003/07/mozilla-org-announces-launch-of-the-'
|
'https://blog.mozilla.org/press/2003/07/mozilla-org-announces-launch-of-the-'
|
||||||
'mozilla-foundation-to-lead-open-source-browser-efforts/'),
|
'mozilla-foundation-to-lead-open-source-browser-efforts/'),
|
||||||
redirect(r'^press/mozilla1.0\.html$',
|
redirect(r'^press/mozilla1\.0\.html$',
|
||||||
'https://blog.mozilla.org/press/2002/06/mozilla-org-launches-mozilla-1-0/'),
|
'https://blog.mozilla.org/press/2002/06/mozilla-org-launches-mozilla-1-0/'),
|
||||||
redirect(r'^press/open-source-security\.html$',
|
redirect(r'^press/open-source-security\.html$',
|
||||||
'https://blog.mozilla.org/press/2000/01/open-source-development-of-security-products-'
|
'https://blog.mozilla.org/press/2000/01/open-source-development-of-security-products-'
|
||||||
|
|
|
@ -66,7 +66,7 @@ class TestPressInquiry(TestCase):
|
||||||
response = self.view(request)
|
response = self.view(request)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.assertIn('Please enter your name.', response.content)
|
self.assertIn(b'Please enter your name.', response.content)
|
||||||
|
|
||||||
def test_view_post_honeypot(self):
|
def test_view_post_honeypot(self):
|
||||||
"""
|
"""
|
||||||
|
@ -84,7 +84,7 @@ class TestPressInquiry(TestCase):
|
||||||
response = self.view(request)
|
response = self.view(request)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.assertIn('An error has occurred', response.content)
|
self.assertIn(b'An error has occurred', response.content)
|
||||||
|
|
||||||
def test_form_valid_data(self):
|
def test_form_valid_data(self):
|
||||||
"""
|
"""
|
||||||
|
@ -199,7 +199,7 @@ class TestSpeakerRequest(TestCase):
|
||||||
response = self.view(request)
|
response = self.view(request)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.assertIn('Please enter a URL', response.content)
|
self.assertIn(b'Please enter a URL', response.content)
|
||||||
|
|
||||||
def test_view_post_honeypot(self):
|
def test_view_post_honeypot(self):
|
||||||
"""
|
"""
|
||||||
|
@ -217,7 +217,7 @@ class TestSpeakerRequest(TestCase):
|
||||||
response = self.view(request)
|
response = self.view(request)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
self.assertIn('An error has occurred', response.content)
|
self.assertIn(b'An error has occurred', response.content)
|
||||||
|
|
||||||
def test_form_valid_data(self):
|
def test_form_valid_data(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
from django.core.urlresolvers import Resolver404
|
from django.urls import Resolver404
|
||||||
|
|
||||||
from .util import get_resolver
|
from .util import get_resolver
|
||||||
|
|
||||||
|
|
||||||
class RedirectsMiddleware(object):
|
class RedirectsMiddleware:
|
||||||
def __init__(self, resolver=None):
|
def __init__(self, get_response=None, resolver=None):
|
||||||
|
self.get_response = get_response
|
||||||
self.resolver = resolver or get_resolver()
|
self.resolver = resolver or get_resolver()
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.process_request(request)
|
||||||
|
if response:
|
||||||
|
return response
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
try:
|
try:
|
||||||
resolver_match = self.resolver.resolve(request.path_info)
|
resolver_match = self.resolver.resolve(request.path_info)
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -13,7 +13,7 @@ patterns = [
|
||||||
redirect(r'^dude/already/10th/', '/far/out/'),
|
redirect(r'^dude/already/10th/', '/far/out/'),
|
||||||
redirect(r'^walter/prior/restraint/', '/finishes/coffee/'),
|
redirect(r'^walter/prior/restraint/', '/finishes/coffee/'),
|
||||||
]
|
]
|
||||||
middleware = RedirectsMiddleware(get_resolver(patterns))
|
middleware = RedirectsMiddleware(resolver=get_resolver(patterns))
|
||||||
|
|
||||||
|
|
||||||
class TestRedirectsMiddleware(TestCase):
|
class TestRedirectsMiddleware(TestCase):
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
from urlparse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
from django.conf.urls import RegexURLPattern
|
from django.urls import URLPattern
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
|
||||||
|
@ -88,12 +88,12 @@ class TestNoRedirectUrlPattern(TestCase):
|
||||||
no_redirect(r'^iam/the/walrus/$'),
|
no_redirect(r'^iam/the/walrus/$'),
|
||||||
redirect(r'^iam/the/.*/$', '/coo/coo/cachoo/'),
|
redirect(r'^iam/the/.*/$', '/coo/coo/cachoo/'),
|
||||||
])
|
])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
||||||
self.assertIsNone(resp)
|
self.assertIsNone(resp)
|
||||||
|
|
||||||
# including locale
|
# including locale
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/pt-BR/iam/the/walrus/'))
|
resp = middleware.process_request(self.rf.get('/pt-BR/iam/the/walrus/'))
|
||||||
self.assertIsNone(resp)
|
self.assertIsNone(resp)
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ class TestNoRedirectUrlPattern(TestCase):
|
||||||
redirect(r'^iam/the/walrus/$', '/coo/coo/cachoo/'),
|
redirect(r'^iam/the/walrus/$', '/coo/coo/cachoo/'),
|
||||||
no_redirect(r'^iam/the/walrus/$', re_flags='i'),
|
no_redirect(r'^iam/the/walrus/$', re_flags='i'),
|
||||||
])
|
])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/IAm/The/Walrus/'))
|
resp = middleware.process_request(self.rf.get('/IAm/The/Walrus/'))
|
||||||
self.assertIsNone(resp)
|
self.assertIsNone(resp)
|
||||||
|
|
||||||
|
@ -129,10 +129,10 @@ class TestRedirectUrlPattern(TestCase):
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
"""
|
"""
|
||||||
Should return a RegexURLPattern with a matching name attribute
|
Should return a URLPattern with a matching name attribute
|
||||||
"""
|
"""
|
||||||
url_pattern = redirect(r'^the/dude$', 'abides', name='Lebowski')
|
url_pattern = redirect(r'^the/dude$', 'abides', name='Lebowski')
|
||||||
assert isinstance(url_pattern, RegexURLPattern)
|
assert isinstance(url_pattern, URLPattern)
|
||||||
assert url_pattern.name == 'Lebowski'
|
assert url_pattern.name == 'Lebowski'
|
||||||
|
|
||||||
def test_no_query(self):
|
def test_no_query(self):
|
||||||
|
@ -292,7 +292,7 @@ class TestRedirectUrlPattern(TestCase):
|
||||||
Should be able to capture info from URL and use in redirection.
|
Should be able to capture info from URL and use in redirection.
|
||||||
"""
|
"""
|
||||||
resolver = get_resolver([redirect(r'^iam/the/(?P<name>.+)/$', '/donnie/the/{name}/')])
|
resolver = get_resolver([redirect(r'^iam/the/(?P<name>.+)/$', '/donnie/the/{name}/')])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
||||||
assert resp.status_code == 301
|
assert resp.status_code == 301
|
||||||
assert resp['Location'] == '/donnie/the/walrus/'
|
assert resp['Location'] == '/donnie/the/walrus/'
|
||||||
|
@ -303,7 +303,7 @@ class TestRedirectUrlPattern(TestCase):
|
||||||
"""
|
"""
|
||||||
resolver = get_resolver([redirect(r'^iam/the/(?P<name>.+)/$',
|
resolver = get_resolver([redirect(r'^iam/the/(?P<name>.+)/$',
|
||||||
'/donnie/the/{name}/')])
|
'/donnie/the/{name}/')])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/pt-BR/iam/the/walrus/'))
|
resp = middleware.process_request(self.rf.get('/pt-BR/iam/the/walrus/'))
|
||||||
assert resp.status_code == 301
|
assert resp.status_code == 301
|
||||||
assert resp['Location'] == '/pt-BR/donnie/the/walrus/'
|
assert resp['Location'] == '/pt-BR/donnie/the/walrus/'
|
||||||
|
@ -314,7 +314,7 @@ class TestRedirectUrlPattern(TestCase):
|
||||||
"""
|
"""
|
||||||
resolver = get_resolver([redirect(r'^iam/the/(?P<name>.+)/$',
|
resolver = get_resolver([redirect(r'^iam/the/(?P<name>.+)/$',
|
||||||
'/donnie/the/{name}/')])
|
'/donnie/the/{name}/')])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
||||||
assert resp.status_code == 301
|
assert resp.status_code == 301
|
||||||
assert resp['Location'] == '/donnie/the/walrus/'
|
assert resp['Location'] == '/donnie/the/walrus/'
|
||||||
|
@ -325,7 +325,7 @@ class TestRedirectUrlPattern(TestCase):
|
||||||
"""
|
"""
|
||||||
resolver = get_resolver([redirect(r'^iam/the/(?P<name>.+)/$',
|
resolver = get_resolver([redirect(r'^iam/the/(?P<name>.+)/$',
|
||||||
'/donnie/the/{name}/', prepend_locale=False)])
|
'/donnie/the/{name}/', prepend_locale=False)])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/zh-TW/iam/the/walrus/'))
|
resp = middleware.process_request(self.rf.get('/zh-TW/iam/the/walrus/'))
|
||||||
assert resp.status_code == 301
|
assert resp.status_code == 301
|
||||||
assert resp['Location'] == '/donnie/the/walrus/'
|
assert resp['Location'] == '/donnie/the/walrus/'
|
||||||
|
@ -340,7 +340,7 @@ class TestRedirectUrlPattern(TestCase):
|
||||||
"""
|
"""
|
||||||
resolver = get_resolver([redirect(r'^iam/the/(.+)/$', '/donnie/the/{}/',
|
resolver = get_resolver([redirect(r'^iam/the/(.+)/$', '/donnie/the/{}/',
|
||||||
locale_prefix=False)])
|
locale_prefix=False)])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
||||||
assert resp.status_code == 301
|
assert resp.status_code == 301
|
||||||
assert resp['Location'] == '/donnie/the/walrus/'
|
assert resp['Location'] == '/donnie/the/walrus/'
|
||||||
|
@ -351,7 +351,7 @@ class TestRedirectUrlPattern(TestCase):
|
||||||
"""
|
"""
|
||||||
resolver = get_resolver([redirect(r'^iam/the(/.+)?/$', '/donnie/the{}/',
|
resolver = get_resolver([redirect(r'^iam/the(/.+)?/$', '/donnie/the{}/',
|
||||||
locale_prefix=False)])
|
locale_prefix=False)])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/iam/the/'))
|
resp = middleware.process_request(self.rf.get('/iam/the/'))
|
||||||
assert resp.status_code == 301
|
assert resp.status_code == 301
|
||||||
assert resp['Location'] == '/donnie/the/'
|
assert resp['Location'] == '/donnie/the/'
|
||||||
|
@ -364,7 +364,7 @@ class TestRedirectUrlPattern(TestCase):
|
||||||
redirect(r'^iam/the/walrus/$', '/coo/coo/cachoo/'),
|
redirect(r'^iam/the/walrus/$', '/coo/coo/cachoo/'),
|
||||||
redirect(r'^iam/the/walrus/$', '/dammit/donnie/', re_flags='i'),
|
redirect(r'^iam/the/walrus/$', '/dammit/donnie/', re_flags='i'),
|
||||||
])
|
])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/IAm/The/Walrus/'))
|
resp = middleware.process_request(self.rf.get('/IAm/The/Walrus/'))
|
||||||
assert resp.status_code == 301
|
assert resp.status_code == 301
|
||||||
assert resp['Location'] == '/dammit/donnie/'
|
assert resp['Location'] == '/dammit/donnie/'
|
||||||
|
@ -391,7 +391,7 @@ class TestRedirectUrlPattern(TestCase):
|
||||||
"""
|
"""
|
||||||
resolver = get_resolver([redirect(r'^editor/(?P<page>.*)$',
|
resolver = get_resolver([redirect(r'^editor/(?P<page>.*)$',
|
||||||
'http://www-archive.mozilla.org/editor/{page}')])
|
'http://www-archive.mozilla.org/editor/{page}')])
|
||||||
middleware = RedirectsMiddleware(resolver)
|
middleware = RedirectsMiddleware(resolver=resolver)
|
||||||
resp = middleware.process_request(self.rf.get('/editor/midasdemo/securityprefs.html'
|
resp = middleware.process_request(self.rf.get('/editor/midasdemo/securityprefs.html'
|
||||||
'%3C/span%3E%3C/a%3E%C2%A0'))
|
'%3C/span%3E%3C/a%3E%C2%A0'))
|
||||||
assert resp.status_code == 301
|
assert resp.status_code == 301
|
||||||
|
|
|
@ -3,21 +3,20 @@
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from urllib import urlencode
|
from urllib.parse import parse_qs, urlencode
|
||||||
from urlparse import parse_qs
|
|
||||||
|
|
||||||
from django.core.urlresolvers import NoReverseMatch, RegexURLResolver, reverse
|
import commonware.log
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.http import HttpResponsePermanentRedirect, HttpResponseRedirect, HttpResponseGone
|
from django.http import (HttpResponseGone, HttpResponsePermanentRedirect,
|
||||||
|
HttpResponseRedirect)
|
||||||
|
from django.urls import NoReverseMatch, URLResolver, reverse
|
||||||
|
from django.urls.resolvers import RegexPattern
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.html import strip_tags
|
from django.utils.html import strip_tags
|
||||||
from django.views.decorators.vary import vary_on_headers
|
from django.views.decorators.vary import vary_on_headers
|
||||||
|
|
||||||
import commonware.log
|
|
||||||
|
|
||||||
from bedrock.mozorg.decorators import cache_control_expires
|
from bedrock.mozorg.decorators import cache_control_expires
|
||||||
|
|
||||||
|
|
||||||
log = commonware.log.getLogger('redirects.util')
|
log = commonware.log.getLogger('redirects.util')
|
||||||
LOCALE_RE = r'^(?P<locale>\w{2,3}(?:-\w{2})?/)?'
|
LOCALE_RE = r'^(?P<locale>\w{2,3}(?:-\w{2})?/)?'
|
||||||
HTTP_RE = re.compile(r'^https?://', re.IGNORECASE)
|
HTTP_RE = re.compile(r'^https?://', re.IGNORECASE)
|
||||||
|
@ -31,7 +30,8 @@ def register(patterns):
|
||||||
|
|
||||||
|
|
||||||
def get_resolver(patterns=None):
|
def get_resolver(patterns=None):
|
||||||
return RegexURLResolver(r'^/', patterns or redirectpatterns)
|
patterns = patterns or redirectpatterns
|
||||||
|
return URLResolver(RegexPattern(r'^/'), patterns)
|
||||||
|
|
||||||
|
|
||||||
def header_redirector(header_name, regex, match_dest, nomatch_dest, case_sensitive=False):
|
def header_redirector(header_name, regex, match_dest, nomatch_dest, case_sensitive=False):
|
||||||
|
@ -166,7 +166,7 @@ def redirect(pattern, to, permanent=True, locale_prefix=True, anchor=None, name=
|
||||||
view_decorators.append(cache_control_expires(cache_timeout))
|
view_decorators.append(cache_control_expires(cache_timeout))
|
||||||
|
|
||||||
if vary:
|
if vary:
|
||||||
if isinstance(vary, basestring):
|
if isinstance(vary, str):
|
||||||
vary = [vary]
|
vary = [vary]
|
||||||
view_decorators.append(vary_on_headers(*vary))
|
view_decorators.append(vary_on_headers(*vary))
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
# Adapted from django-mozilla-product-details
|
# Adapted from django-mozilla-product-details
|
||||||
version_re = (r"\d+" # major (x in x.y)
|
version_re = (r"\d+" # major (x in x.y)
|
||||||
"\.\d+" # minor1 (y in x.y)
|
r"\.\d+" # minor1 (y in x.y)
|
||||||
"\.?(?:\d+)?" # minor2 (z in x.y.z)
|
r"\.?(?:\d+)?" # minor2 (z in x.y.z)
|
||||||
"\.?(?:\d+)?" # minor3 (w in x.y.z.w)
|
r"\.?(?:\d+)?" # minor3 (w in x.y.z.w)
|
||||||
"(?:a|b(?:eta)?)?" # alpha/beta
|
r"(?:a|b(?:eta)?)?" # alpha/beta
|
||||||
"(?:\d*)" # alpha/beta version
|
r"(?:\d*)" # alpha/beta version
|
||||||
"(?:pre)?" # pre release
|
r"(?:pre)?" # pre release
|
||||||
"(?:\d)?" # pre release version
|
r"(?:\d)?" # pre release version
|
||||||
"(?:esr)?") # extended support release
|
r"(?:esr)?") # extended support release
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import bedrock.releasenotes.models
|
import bedrock.releasenotes.models
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,11 @@ from bedrock.releasenotes.utils import memoize
|
||||||
LONG_RN_CACHE_TIMEOUT = 7200 # 2 hours
|
LONG_RN_CACHE_TIMEOUT = 7200 # 2 hours
|
||||||
cache = caches['release-notes']
|
cache = caches['release-notes']
|
||||||
markdowner = markdown.Markdown(extensions=[
|
markdowner = markdown.Markdown(extensions=[
|
||||||
'tables', 'codehilite', 'fenced_code', 'toc', 'nl2br'
|
'markdown.extensions.tables',
|
||||||
|
'markdown.extensions.codehilite',
|
||||||
|
'markdown.extensions.fenced_code',
|
||||||
|
'markdown.extensions.toc',
|
||||||
|
'markdown.extensions.nl2br',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +59,7 @@ FIELD_PROCESSORS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RNModel(object):
|
class RNModel:
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
if not hasattr(self, key):
|
if not hasattr(self, key):
|
||||||
|
@ -171,7 +175,7 @@ class ProductRelease(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-release_date']
|
ordering = ['-release_date']
|
||||||
|
|
||||||
def __unicode__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
@ -180,7 +184,7 @@ class ProductRelease(models.Model):
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def major_version_int(self):
|
def major_version_int(self):
|
||||||
return self.version_obj.major
|
return self.version_obj.major or 0
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def version_obj(self):
|
def version_obj(self):
|
||||||
|
|
|
@ -19,7 +19,6 @@ from bedrock.releasenotes.models import ProductRelease
|
||||||
|
|
||||||
TESTS_PATH = Path(__file__).parent
|
TESTS_PATH = Path(__file__).parent
|
||||||
DATA_PATH = str(TESTS_PATH.joinpath('data'))
|
DATA_PATH = str(TESTS_PATH.joinpath('data'))
|
||||||
firefox_desktop = FirefoxDesktop(json_dir=DATA_PATH)
|
|
||||||
RELEASES_PATH = str(TESTS_PATH)
|
RELEASES_PATH = str(TESTS_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,27 +255,28 @@ class TestReleaseNotesIndex(TestCase):
|
||||||
self.pd_cache.clear()
|
self.pd_cache.clear()
|
||||||
|
|
||||||
@patch('bedrock.releasenotes.views.l10n_utils.render')
|
@patch('bedrock.releasenotes.views.l10n_utils.render')
|
||||||
@patch('bedrock.releasenotes.views.firefox_desktop', firefox_desktop)
|
|
||||||
def test_relnotes_index_firefox(self, render_mock):
|
def test_relnotes_index_firefox(self, render_mock):
|
||||||
render_mock().render.return_value = HttpResponse('')
|
firefox_desktop = FirefoxDesktop(json_dir=DATA_PATH)
|
||||||
with self.activate('en-US'):
|
with patch('bedrock.releasenotes.views.firefox_desktop', firefox_desktop):
|
||||||
self.client.get(reverse('firefox.releases.index'))
|
render_mock().render.return_value = HttpResponse('')
|
||||||
releases = render_mock.call_args[0][2]['releases']
|
with self.activate('en-US'):
|
||||||
assert len(releases) == len(firefox_desktop.firefox_history_major_releases)
|
self.client.get(reverse('firefox.releases.index'))
|
||||||
assert releases[0][0] == 36.0
|
releases = render_mock.call_args[0][2]['releases']
|
||||||
assert releases[0][1]['major'] == '36.0'
|
assert len(releases) == len(firefox_desktop.firefox_history_major_releases)
|
||||||
assert releases[0][1]['minor'] == []
|
assert releases[0][0] == 36.0
|
||||||
assert releases[3][0] == 33.1
|
assert releases[0][1]['major'] == '36.0'
|
||||||
assert releases[3][1]['major'] == '33.1'
|
assert releases[0][1]['minor'] == []
|
||||||
assert releases[3][1]['minor'] == ['33.1.1']
|
assert releases[3][0] == 33.1
|
||||||
assert releases[4][0] == 33.0
|
assert releases[3][1]['major'] == '33.1'
|
||||||
assert releases[4][1]['major'] == '33.0'
|
assert releases[3][1]['minor'] == ['33.1.1']
|
||||||
assert releases[4][1]['minor'] == ['33.0.1', '33.0.2', '33.0.3']
|
assert releases[4][0] == 33.0
|
||||||
assert releases[6][0] == 31.0
|
assert releases[4][1]['major'] == '33.0'
|
||||||
assert releases[6][1]['major'] == '31.0'
|
assert releases[4][1]['minor'] == ['33.0.1', '33.0.2', '33.0.3']
|
||||||
assert (
|
assert releases[6][0] == 31.0
|
||||||
releases[6][1]['minor'] ==
|
assert releases[6][1]['major'] == '31.0'
|
||||||
['31.1.0', '31.1.1', '31.2.0', '31.3.0', '31.4.0', '31.5.0'])
|
assert (
|
||||||
|
releases[6][1]['minor'] ==
|
||||||
|
['31.1.0', '31.1.1', '31.2.0', '31.3.0', '31.4.0', '31.5.0'])
|
||||||
|
|
||||||
|
|
||||||
class TestNotesRedirects(TestCase):
|
class TestNotesRedirects(TestCase):
|
||||||
|
|
|
@ -23,6 +23,7 @@ SUPPORT_URLS = {
|
||||||
|
|
||||||
def release_notes_template(channel, product, version=None):
|
def release_notes_template(channel, product, version=None):
|
||||||
channel = channel or 'release'
|
channel = channel or 'release'
|
||||||
|
version = version or 0
|
||||||
if product == 'Firefox' and channel == 'Aurora' and version >= 35:
|
if product == 'Firefox' and channel == 'Aurora' and version >= 35:
|
||||||
return 'firefox/releases/dev-browser-notes.html'
|
return 'firefox/releases/dev-browser-notes.html'
|
||||||
|
|
||||||
|
@ -141,8 +142,9 @@ def releases_index(request, product):
|
||||||
# Starting with Firefox 10, ESR had been offered every 7 major releases, but
|
# Starting with Firefox 10, ESR had been offered every 7 major releases, but
|
||||||
# Firefox 59 wasn't ESR. Firefox 60 became the next ESR instead, and since
|
# Firefox 59 wasn't ESR. Firefox 60 became the next ESR instead, and since
|
||||||
# then ESR is offered every 8 major releases.
|
# then ESR is offered every 8 major releases.
|
||||||
esr_major_versions = (range(10, 59, 7) +
|
esr_major_versions = (
|
||||||
range(60, int(firefox_desktop.latest_version().split('.')[0]), 8))
|
list(range(10, 59, 7)) +
|
||||||
|
list(range(60, int(firefox_desktop.latest_version().split('.')[0]), 8)))
|
||||||
|
|
||||||
if product == 'Firefox':
|
if product == 'Firefox':
|
||||||
major_releases = firefox_desktop.firefox_history_major_releases
|
major_releases = firefox_desktop.firefox_history_major_releases
|
||||||
|
@ -158,9 +160,8 @@ def releases_index(request, product):
|
||||||
major_pattern = r'^' + re.escape(converter % round(major_version, 1))
|
major_pattern = r'^' + re.escape(converter % round(major_version, 1))
|
||||||
releases[major_version] = {
|
releases[major_version] = {
|
||||||
'major': release,
|
'major': release,
|
||||||
'minor': sorted(filter(lambda x: re.findall(major_pattern, x),
|
'minor': sorted([x for x in minor_releases if re.findall(major_pattern, x)],
|
||||||
minor_releases),
|
key=lambda x: [int(y) for y in x.split('.')])
|
||||||
key=lambda x: map(lambda y: int(y), x.split('.')))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return l10n_utils.render(
|
return l10n_utils.render(
|
||||||
|
@ -175,8 +176,7 @@ def nightly_feed(request):
|
||||||
releases = get_releases_or_404('firefox', 'nightly', 5)
|
releases = get_releases_or_404('firefox', 'nightly', 5)
|
||||||
|
|
||||||
for release in releases:
|
for release in releases:
|
||||||
link = reverse('firefox.desktop.releasenotes',
|
link = reverse('firefox.desktop.releasenotes', args=(release.version, 'release'))
|
||||||
args=(release.version, 'release'))
|
|
||||||
|
|
||||||
for note in release.notes:
|
for note in release.notes:
|
||||||
if note.id in notes:
|
if note.id in notes:
|
||||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче