зеркало из 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
|
||||
.env
|
||||
**/__pycache__
|
||||
**/*.pyc
|
||||
|
|
|
@ -23,7 +23,7 @@ RUN gulp build --production
|
|||
########
|
||||
# Python dependencies builder
|
||||
#
|
||||
FROM python:2-stretch AS python-builder
|
||||
FROM python:3-slim AS python-builder
|
||||
|
||||
WORKDIR /app
|
||||
ENV LANG=C.UTF-8
|
||||
|
@ -34,6 +34,7 @@ ENV PATH="/venv/bin:$PATH"
|
|||
COPY docker/bin/apt-install /usr/local/bin/
|
||||
RUN apt-install gettext build-essential libxml2-dev libxslt1-dev libxslt1.1
|
||||
|
||||
RUN pip install virtualenv
|
||||
RUN virtualenv /venv
|
||||
|
||||
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
|
||||
#
|
||||
FROM python:2-slim-stretch AS app-base
|
||||
FROM python:3-slim AS app-base
|
||||
|
||||
# Extra python env
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
|
|
2
Makefile
2
Makefile
|
@ -79,7 +79,7 @@ clean:
|
|||
git clean -f
|
||||
|
||||
lint: .docker-build-pull
|
||||
${DC} run test flake8 bedrock lib tests
|
||||
${DC} run test flake8
|
||||
${DC} run assets gulp js:lint css:lint
|
||||
|
||||
test: .docker-build-pull
|
||||
|
|
|
@ -12,7 +12,7 @@ class SimpleDictCache(LocMemCache):
|
|||
def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
with self._lock.writer():
|
||||
with self._lock:
|
||||
if self._has_expired(key):
|
||||
self._set(key, value, timeout)
|
||||
return True
|
||||
|
@ -22,13 +22,13 @@ class SimpleDictCache(LocMemCache):
|
|||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
value = default
|
||||
with self._lock.reader():
|
||||
with self._lock:
|
||||
if not self._has_expired(key):
|
||||
value = self._cache[key]
|
||||
if value is not default:
|
||||
return value
|
||||
|
||||
with self._lock.writer():
|
||||
with self._lock:
|
||||
try:
|
||||
del self._cache[key]
|
||||
del self._expire_info[key]
|
||||
|
@ -39,7 +39,7 @@ class SimpleDictCache(LocMemCache):
|
|||
def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
|
||||
key = self.make_key(key, version=version)
|
||||
self.validate_key(key)
|
||||
with self._lock.writer():
|
||||
with self._lock:
|
||||
self._set(key, value, timeout)
|
||||
|
||||
def incr(self, key, delta=1, version=None):
|
||||
|
@ -48,6 +48,6 @@ class SimpleDictCache(LocMemCache):
|
|||
raise ValueError("Key '%s' not found" % key)
|
||||
new_value = value + delta
|
||||
key = self.make_key(key, version=version)
|
||||
with self._lock.writer():
|
||||
with self._lock:
|
||||
self._cache[key] = new_value
|
||||
return new_value
|
||||
|
|
|
@ -91,7 +91,7 @@ for key, value in settings.LOGGING.items():
|
|||
cfg[key] = value
|
||||
|
||||
# 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:
|
||||
logger['handlers'] = ['syslog' if use_syslog else 'console']
|
||||
if 'level' not in logger:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
|
@ -27,7 +26,7 @@ def refresh_db_values():
|
|||
|
||||
ConfigValue.objects.all().delete()
|
||||
count = 0
|
||||
for name, value in values.iteritems():
|
||||
for name, value in values.items():
|
||||
if value:
|
||||
ConfigValue.objects.create(name=name, value=value)
|
||||
count += 1
|
||||
|
|
|
@ -5,30 +5,41 @@ This is django-localeurl, but with mozilla style capital letters in
|
|||
the locale codes.
|
||||
"""
|
||||
import base64
|
||||
import urllib
|
||||
import urllib.parse
|
||||
from urllib.parse import unquote
|
||||
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.core.exceptions import MiddlewareNotUsed
|
||||
from django.http import HttpResponsePermanentRedirect, HttpResponse
|
||||
from django.utils.encoding import force_text
|
||||
from django.http import HttpResponse, HttpResponsePermanentRedirect
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
from . import urlresolvers
|
||||
from lib.l10n_utils import translation
|
||||
|
||||
from . import urlresolvers
|
||||
|
||||
class LocaleURLMiddleware(object):
|
||||
|
||||
class LocaleURLMiddleware:
|
||||
"""
|
||||
1. Search for the locale.
|
||||
2. Save it in the request.
|
||||
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:
|
||||
warn("USE_I18N or USE_L10N is False but LocaleURLMiddleware is "
|
||||
"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):
|
||||
prefixer = urlresolvers.Prefixer(request)
|
||||
|
@ -37,11 +48,11 @@ class LocaleURLMiddleware(object):
|
|||
|
||||
if full_path != request.path:
|
||||
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:
|
||||
full_path = '?'.join(
|
||||
[full_path, force_text(query_string, errors='ignore')])
|
||||
[full_path, unquote(query_string, errors='ignore')])
|
||||
|
||||
response = HttpResponsePermanentRedirect(full_path)
|
||||
|
||||
|
@ -58,14 +69,21 @@ class LocaleURLMiddleware(object):
|
|||
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.
|
||||
Set the BASIC_AUTH_CREDS environment variable to enable.
|
||||
"""
|
||||
def __init__(self):
|
||||
def __init__(self, get_response=None):
|
||||
if not settings.BASIC_AUTH_CREDS:
|
||||
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):
|
||||
required_auth = settings.BASIC_AUTH_CREDS
|
||||
|
@ -85,3 +103,11 @@ class BasicAuthMiddleware(object):
|
|||
realm = settings.APP_NAME or 'bedrock-demo'
|
||||
response['WWW-Authenticate'] = 'Basic realm="{}"'.format(realm)
|
||||
return response
|
||||
|
||||
|
||||
class RobotsTagHeader(OldRobotsTagHeader, MiddlewareMixin):
|
||||
pass
|
||||
|
||||
|
||||
class FrameOptionsHeader(OldFrameOptionsHeader, MiddlewareMixin):
|
||||
pass
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ class ConfigValue(models.Model):
|
|||
class Meta:
|
||||
app_label = 'base'
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return '%s=%s' % (self.name, self.value)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import datetime
|
||||
import urllib
|
||||
import urlparse
|
||||
import urllib.parse
|
||||
|
||||
from django.conf import settings
|
||||
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
|
||||
names, which will be replaced.
|
||||
"""
|
||||
url = urlparse.urlparse(url_)
|
||||
url = urllib.parse.urlparse(url_)
|
||||
fragment = hash if hash is not None else url.fragment
|
||||
|
||||
# Use dict(parse_qsl) so we don't get lists of values.
|
||||
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_string = _urlencode([(k, v) for k, v in query_dict.items()
|
||||
if v is not None])
|
||||
new = urlparse.ParseResult(url.scheme, url.netloc, url.path, url.params,
|
||||
query_string, fragment)
|
||||
new = urllib.parse.ParseResult(
|
||||
url.scheme, url.netloc, url.path, url.params, query_string, fragment)
|
||||
return new.geturl()
|
||||
|
||||
|
||||
def _urlencode(items):
|
||||
"""A Unicode-safe URLencoder."""
|
||||
try:
|
||||
return urllib.urlencode(items)
|
||||
return urllib.parse.urlencode(items)
|
||||
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
|
||||
def mailtoencode(txt):
|
||||
"""Url encode a string using %20 for spaces."""
|
||||
if isinstance(txt, unicode):
|
||||
if isinstance(txt, str):
|
||||
txt = txt.encode('utf-8')
|
||||
return urllib.quote(txt)
|
||||
return urllib.parse.quote(txt)
|
||||
|
||||
|
||||
@library.filter
|
||||
def urlencode(txt):
|
||||
"""Url encode a string using + for spaces."""
|
||||
if isinstance(txt, unicode):
|
||||
if isinstance(txt, str):
|
||||
txt = txt.encode('utf-8')
|
||||
return urllib.quote_plus(txt)
|
||||
return urllib.parse.quote_plus(txt)
|
||||
|
||||
|
||||
@library.global_function
|
||||
|
|
|
@ -82,8 +82,8 @@ class AcceptedLocalesTest(TestCase):
|
|||
# simulate the successful result of the DEV_LANGUAGES list
|
||||
# comprehension defined in settings.
|
||||
settings.DEV_LANGUAGES = ['en-US', 'fr']
|
||||
assert settings.LANGUAGE_URL_MAP == {'en-us': 'en-US', 'fr': 'fr'}, \
|
||||
('DEV is True, but DEV_LANGUAGES are not used to define the '
|
||||
assert settings.LANGUAGE_URL_MAP == {'en-us': 'en-US', 'fr': 'fr'}, (
|
||||
'DEV is True, but DEV_LANGUAGES are not used to define the '
|
||||
'allowed locales.')
|
||||
|
||||
def test_prod_languages(self):
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from urllib.parse import urlencode
|
||||
|
||||
from django.test import TestCase, RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
@ -15,7 +17,7 @@ class TestLocaleURLMiddleware(TestCase):
|
|||
"""Should redirect to lang prefixed url."""
|
||||
path = '/the/dude/'
|
||||
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)
|
||||
|
||||
@override_settings(DEV_LANGUAGES=('es', 'fr'),
|
||||
|
@ -24,17 +26,18 @@ class TestLocaleURLMiddleware(TestCase):
|
|||
"""Should redirect to default lang if not in settings."""
|
||||
path = '/the/dude/'
|
||||
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)
|
||||
|
||||
@override_settings(DEV_LANGUAGES=('de', 'fr'))
|
||||
def test_redirects_to_correct_language_despite_unicode_errors(self):
|
||||
"""Should redirect to lang prefixed url, stripping invalid chars."""
|
||||
path = '/the/dude/'
|
||||
corrupt_querystring = '?a\xa4\x91b\xa4\x91i\xc0de=s'
|
||||
corrupt_querystring = '?' + urlencode(
|
||||
{b'a\xa4\x91b\xa4\x91i\xc0de': 's'})
|
||||
corrected_querystring = '?abide=s'
|
||||
req = self.rf.get(path + corrupt_querystring,
|
||||
HTTP_ACCEPT_LANGUAGE='de')
|
||||
resp = LocaleURLMiddleware().process_request(req)
|
||||
resp = self.middleware.process_request(req)
|
||||
self.assertEqual(resp['Location'],
|
||||
'/de' + path + corrected_querystring)
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
# and instead uses a simple dict for in-memory storage. These were adapted
|
||||
# from the cache backend tests in Django itself.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import time
|
||||
import warnings
|
||||
|
@ -81,7 +79,7 @@ class SimpleDictCacheTests(TestCase):
|
|||
cache.set('somekey', 'value')
|
||||
|
||||
# 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')
|
||||
|
||||
|
@ -120,10 +118,10 @@ class SimpleDictCacheTests(TestCase):
|
|||
def test_has_key(self):
|
||||
# The cache can be inspected for cache keys
|
||||
cache.set("hello1", "goodbye1")
|
||||
self.assertEqual(cache.has_key("hello1"), True) # noqa
|
||||
self.assertEqual(cache.has_key("goodbye1"), False) # noqa
|
||||
self.assertEqual("hello1" in cache, True) # noqa
|
||||
self.assertEqual("goodbye1" in cache, False) # noqa
|
||||
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):
|
||||
# The in operator can be used to inspect cache contents
|
||||
|
@ -180,7 +178,7 @@ class SimpleDictCacheTests(TestCase):
|
|||
|
||||
cache.add("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):
|
||||
# Unicode values can be cached
|
||||
|
@ -191,21 +189,21 @@ class SimpleDictCacheTests(TestCase):
|
|||
'ascii2': {'x': 1}
|
||||
}
|
||||
# Test `set`
|
||||
for (key, value) in stuff.items():
|
||||
for key, value in stuff.items():
|
||||
cache.set(key, value)
|
||||
self.assertEqual(cache.get(key), value)
|
||||
|
||||
# Test `add`
|
||||
for (key, value) in stuff.items():
|
||||
for key, value in stuff.items():
|
||||
cache.delete(key)
|
||||
cache.add(key, value)
|
||||
self.assertEqual(cache.get(key), value)
|
||||
|
||||
# Test `set_many`
|
||||
for (key, value) in stuff.items():
|
||||
for key, value in stuff.items():
|
||||
cache.delete(key)
|
||||
cache.set_many(stuff)
|
||||
for (key, value) in stuff.items():
|
||||
for key, value in stuff.items():
|
||||
self.assertEqual(cache.get(key), value)
|
||||
|
||||
def test_binary_string(self):
|
||||
|
@ -324,7 +322,7 @@ class SimpleDictCacheTests(TestCase):
|
|||
count = 0
|
||||
# Count how many keys are left in the cache.
|
||||
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
|
||||
self.assertEqual(count, final_count)
|
||||
|
||||
|
@ -451,11 +449,11 @@ class SimpleDictCacheTests(TestCase):
|
|||
cache.set('answer1', 42)
|
||||
|
||||
# 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.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.assertFalse(caches['v2'].has_key('answer1', version=2)) # noqa
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.conf.urls import url
|
||||
from django.urls import re_path
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
|
@ -26,11 +26,11 @@ def test_split_path(path, result):
|
|||
|
||||
# Test 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):
|
||||
self.fix = fix
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from threading import local
|
||||
|
||||
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.functional import lazy
|
||||
from django.utils.translation.trans_real import parse_accept_lang_header
|
||||
|
@ -45,7 +45,7 @@ def _get_language_map():
|
|||
:return: dict
|
||||
"""
|
||||
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
|
||||
# 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
|
||||
|
@ -86,7 +86,7 @@ def split_path(path_):
|
|||
return '', path
|
||||
|
||||
|
||||
class Prefixer(object):
|
||||
class Prefixer:
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
split = split_path(request.path_info)
|
||||
|
|
|
@ -5,19 +5,17 @@ from datetime import datetime
|
|||
from os import getenv
|
||||
from time import time
|
||||
|
||||
import timeago
|
||||
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.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST, require_safe
|
||||
|
||||
import timeago
|
||||
from lib import l10n_utils
|
||||
from raven.contrib.django.models import client
|
||||
|
||||
from bedrock.mozorg.util import HttpResponseJSON
|
||||
from bedrock.utils import git
|
||||
from lib import l10n_utils
|
||||
|
||||
|
||||
def get_geo_from_request(request):
|
||||
|
@ -46,7 +44,7 @@ def geolocate(request):
|
|||
"""
|
||||
country_code = get_geo_from_request(request)
|
||||
if country_code is None:
|
||||
return HttpResponseJSON({
|
||||
return JsonResponse({
|
||||
"error": {
|
||||
"errors": [{
|
||||
"domain": "geolocation",
|
||||
|
@ -58,7 +56,7 @@ def geolocate(request):
|
|||
}
|
||||
}, status=404)
|
||||
|
||||
return HttpResponseJSON({
|
||||
return JsonResponse({
|
||||
'country_code': country_code,
|
||||
})
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django_extensions.db.fields.json
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ class ContentCard(models.Model):
|
|||
class Meta:
|
||||
ordering = ('id',)
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return '{} ({})'.format(self.card_name, self.locale)
|
||||
|
||||
@property
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from django.conf.urls import url
|
||||
from bedrock.mozorg.util import page
|
||||
|
||||
import views
|
||||
from bedrock.etc import views
|
||||
|
||||
|
||||
urlpatterns = (
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
# 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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from datetime import datetime
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
from django.utils.encoding import force_text
|
||||
from icalendar import Calendar
|
||||
from pytz import timezone
|
||||
|
||||
|
@ -128,7 +128,7 @@ class Event(models.Model):
|
|||
class Meta:
|
||||
ordering = ('start_time',)
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
|
@ -142,13 +142,14 @@ class Event(models.Model):
|
|||
return u'<abbr>{0:%b}</abbr>'.format(self.start_time)
|
||||
|
||||
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:
|
||||
value = ical_event.decoded(ical_prop)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(value, basestring):
|
||||
value = force_text(value, strings_only=True)
|
||||
if isinstance(value, str):
|
||||
value = value.strip()
|
||||
setattr(self, field, value)
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class TestFutureQuerySet(TestCase):
|
|||
"""
|
||||
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
|
||||
|
||||
@override_settings(USE_TZ=False)
|
||||
|
@ -82,7 +82,7 @@ class TestFutureQuerySet(TestCase):
|
|||
"""
|
||||
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
|
||||
|
||||
|
||||
|
@ -95,7 +95,7 @@ class TestQuerySets(TestCase):
|
|||
self.mock_datetime = datetime_patcher.start()
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -6,10 +6,7 @@ import codecs
|
|||
import logging
|
||||
import os.path
|
||||
from time import mktime
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
from io import StringIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
|
@ -19,7 +16,7 @@ from django.utils.http import http_date
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExternalFile(object):
|
||||
class ExternalFile:
|
||||
def __init__(self, file_id):
|
||||
try:
|
||||
fileinfo = settings.EXTERNAL_FILES[file_id]
|
||||
|
@ -87,7 +84,7 @@ class ExternalFile(object):
|
|||
return self.validate_content(content)
|
||||
|
||||
def read(self):
|
||||
return self.file_object.content.encode('utf-8')
|
||||
return self.file_object.content
|
||||
|
||||
def readlines(self):
|
||||
return StringIO(self.read()).readlines()
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils.module_loading import import_string
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import re
|
||||
from collections import OrderedDict
|
||||
from operator import itemgetter
|
||||
from urllib import urlencode
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -77,7 +77,7 @@ class FirefoxDesktop(_ProductDetails):
|
|||
"""
|
||||
if classified:
|
||||
platforms = OrderedDict()
|
||||
for k, v in self.platform_classification.iteritems():
|
||||
for k, v in self.platform_classification.items():
|
||||
for platform in v:
|
||||
platforms[platform] = self.platform_labels[platform]
|
||||
else:
|
||||
|
@ -88,7 +88,7 @@ class FirefoxDesktop(_ProductDetails):
|
|||
del platforms['win64-msi']
|
||||
del platforms['win-msi']
|
||||
|
||||
return platforms.items()
|
||||
return list(platforms.items())
|
||||
|
||||
def latest_version(self, channel='release'):
|
||||
version = self.version_map.get(channel, 'LATEST_FIREFOX_VERSION')
|
||||
|
@ -167,7 +167,7 @@ class FirefoxDesktop(_ProductDetails):
|
|||
version = version or self.latest_version(channel)
|
||||
|
||||
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):
|
||||
continue
|
||||
|
||||
|
@ -182,7 +182,7 @@ class FirefoxDesktop(_ProductDetails):
|
|||
if query is not None and not self._matches_query(build_info, query):
|
||||
continue
|
||||
|
||||
for platform, label in self.platform_labels.iteritems():
|
||||
for platform, label in self.platform_labels.items():
|
||||
build_info['platforms'][platform] = {
|
||||
'download_url': self.get_download_url(channel, version,
|
||||
platform, locale,
|
||||
|
@ -384,10 +384,10 @@ class FirefoxAndroid(_ProductDetails):
|
|||
min_version = '4.0.3' if version_int < 56 else '4.1'
|
||||
|
||||
# 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
|
||||
|
||||
return platforms.items()
|
||||
return list(platforms.items())
|
||||
|
||||
def latest_version(self, channel):
|
||||
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):
|
||||
continue
|
||||
|
||||
for arch, platform in self.platform_map.iteritems():
|
||||
for arch, platform in self.platform_map.items():
|
||||
# x86 builds are not localized yet
|
||||
if arch == 'x86' and locale not in ['multi', 'en-US']:
|
||||
continue
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
|
|
@ -178,15 +178,15 @@ redirectpatterns = (
|
|||
'https://support.mozilla.org/kb/how-does-phishing-and-malware-protection-work'),
|
||||
|
||||
# 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/'),
|
||||
|
||||
# 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/'),
|
||||
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/'),
|
||||
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/'),
|
||||
|
||||
# bug 960064
|
||||
|
@ -200,7 +200,7 @@ redirectpatterns = (
|
|||
'https://support.mozilla.org/kb/will-firefox-work-my-mobile-device'),
|
||||
|
||||
# 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\.-]+)?$',
|
||||
'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'),
|
||||
redirect(r'^projects/seamonkey/releases/(?P<vers>1\..*)\.html$',
|
||||
'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}/'),
|
||||
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}'),
|
||||
redirect(r'^projects/seamonkey/releases/updates/(?P<x>.*)$',
|
||||
'http://www.seamonkey-project.org/releases/updates/{x}'),
|
||||
|
@ -313,39 +313,39 @@ redirectpatterns = (
|
|||
redirect(r'^firefox/ie', 'firefox.new'),
|
||||
|
||||
# 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'),
|
||||
redirect('^projects/xul/xre(old)?\.html$',
|
||||
redirect(r'^projects/xul/xre(old)?\.html$',
|
||||
'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/'
|
||||
'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/'
|
||||
'Web/API/XSLTProcessor/XSL_Transformations_in_Mozilla_FAQ'),
|
||||
redirect('^projects/xslt/standalone\.html$',
|
||||
redirect(r'^projects/xslt/standalone\.html$',
|
||||
'https://developer.mozilla.org/docs/'
|
||||
'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'),
|
||||
redirect('^projects/plugins/install-scheme\.html$',
|
||||
redirect(r'^projects/plugins/install-scheme\.html$',
|
||||
'https://developer.mozilla.org/docs/'
|
||||
'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/'
|
||||
'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'),
|
||||
redirect('^projects/plugins/plugin-host-control\.html$',
|
||||
redirect(r'^projects/plugins/plugin-host-control\.html$',
|
||||
'https://developer.mozilla.org/docs/'
|
||||
'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'),
|
||||
redirect('^projects/netlib/http/http-debugging\.html$',
|
||||
redirect(r'^projects/netlib/http/http-debugging\.html$',
|
||||
'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'),
|
||||
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'),
|
||||
redirect(r'^projects/embedding/GRE\.html$',
|
||||
'https://developer.mozilla.org/docs/Archive/Mozilla/GRE'),
|
||||
|
@ -450,42 +450,42 @@ redirectpatterns = (
|
|||
# Bug 654614 /blocklist -> addons.m.o/blocked
|
||||
redirect(r'^blocklist(/.*)?$', 'https://addons.mozilla.org/blocked/'),
|
||||
|
||||
redirect('^products/firebird$', 'firefox'),
|
||||
redirect('^products/firebird/download/$', 'firefox.new'),
|
||||
redirect('^products/firefox/add-engines\.html$',
|
||||
redirect(r'^products/firebird$', 'firefox'),
|
||||
redirect(r'^products/firebird/download/$', 'firefox.new'),
|
||||
redirect(r'^products/firefox/add-engines\.html$',
|
||||
'https://addons.mozilla.org/search-engines.php'),
|
||||
redirect('^products/firefox/all$', '/firefox/all/'),
|
||||
redirect('^products/firefox/all\.html$', '/firefox/all/'),
|
||||
redirect('^products/firefox/banners\.html$', '/contribute/friends/'),
|
||||
redirect('^products/firefox/buttons\.html$', '/contribute/friends/'),
|
||||
redirect('^products/firefox/download', 'firefox.new'),
|
||||
redirect('^products/firefox/get$', 'firefox.new'),
|
||||
redirect('^products/firefox/$', 'firefox'),
|
||||
redirect('^products/firefox/live-bookmarks', '/firefox/features/'),
|
||||
redirect('^products/firefox/mirrors\.html$', 'http://www-archive.mozilla.org/mirrors.html'),
|
||||
redirect('^products/firefox/releases/$', '/firefox/releases/'),
|
||||
redirect('^products/firefox/releases/0\.9\.2\.html$',
|
||||
redirect(r'^products/firefox/all$', '/firefox/all/'),
|
||||
redirect(r'^products/firefox/all\.html$', '/firefox/all/'),
|
||||
redirect(r'^products/firefox/banners\.html$', '/contribute/friends/'),
|
||||
redirect(r'^products/firefox/buttons\.html$', '/contribute/friends/'),
|
||||
redirect(r'^products/firefox/download', 'firefox.new'),
|
||||
redirect(r'^products/firefox/get$', 'firefox.new'),
|
||||
redirect(r'^products/firefox/$', 'firefox'),
|
||||
redirect(r'^products/firefox/live-bookmarks', '/firefox/features/'),
|
||||
redirect(r'^products/firefox/mirrors\.html$', 'http://www-archive.mozilla.org/mirrors.html'),
|
||||
redirect(r'^products/firefox/releases/$', '/firefox/releases/'),
|
||||
redirect(r'^products/firefox/releases/0\.9\.2\.html$',
|
||||
'http://website-archive.mozilla.org/www.mozilla.org/firefox_releasenotes'
|
||||
'/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'
|
||||
'/en-US/firefox/releases/0.10.html'),
|
||||
redirect('^products/firefox/search', '/firefox/features/'),
|
||||
redirect('^products/firefox/shelf\.html$', 'https://blog.mozilla.org/press/awards/'),
|
||||
redirect('^products/firefox/smart-keywords\.html$',
|
||||
redirect(r'^products/firefox/search', '/firefox/features/'),
|
||||
redirect(r'^products/firefox/shelf\.html$', 'https://blog.mozilla.org/press/awards/'),
|
||||
redirect(r'^products/firefox/smart-keywords\.html$',
|
||||
'https://support.mozilla.org/en-US/kb/Smart+keywords'),
|
||||
redirect('^products/firefox/support/$', 'https://support.mozilla.org/'),
|
||||
redirect('^products/firefox/switch', 'firefox.new'),
|
||||
redirect('^products/firefox/system-requirements', '/firefox/system-requirements/'),
|
||||
redirect('^products/firefox/tabbed-browsing', 'firefox'),
|
||||
redirect('^products/firefox/text-zoom\.html$',
|
||||
redirect(r'^products/firefox/support/$', 'https://support.mozilla.org/'),
|
||||
redirect(r'^products/firefox/switch', 'firefox.new'),
|
||||
redirect(r'^products/firefox/system-requirements', '/firefox/system-requirements/'),
|
||||
redirect(r'^products/firefox/tabbed-browsing', 'firefox'),
|
||||
redirect(r'^products/firefox/text-zoom\.html$',
|
||||
'https://support.mozilla.org/kb/font-size-and-zoom-increase-size-of-web-pages'),
|
||||
redirect('^products/firefox/themes$', 'https://addons.mozilla.org/themes/'),
|
||||
redirect('^products/firefox/themes\.html$', 'https://addons.mozilla.org/themes/'),
|
||||
redirect('^products/firefox/ui-customize\.html$',
|
||||
redirect(r'^products/firefox/themes$', 'https://addons.mozilla.org/themes/'),
|
||||
redirect(r'^products/firefox/themes\.html$', 'https://addons.mozilla.org/themes/'),
|
||||
redirect(r'^products/firefox/ui-customize\.html$',
|
||||
'https://support.mozilla.org/kb/customize-firefox-controls-buttons-and-toolbars'),
|
||||
redirect('^products/firefox/upgrade', 'firefox.new'),
|
||||
redirect('^products/firefox/why/$', 'firefox'),
|
||||
redirect(r'^products/firefox/upgrade', 'firefox.new'),
|
||||
redirect(r'^products/firefox/why/$', 'firefox'),
|
||||
|
||||
# bug 857246 redirect /products/firefox/start/ to 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/tips', '/firefox/features/'),
|
||||
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/android/(?P<version>\d+\.\d+(?:\.\d+)?)$', '/firefox/android/{version}/releasenotes/'),
|
||||
redirect(r'^firefox/stats/', '/firefox/'),
|
||||
|
@ -569,9 +569,11 @@ redirectpatterns = (
|
|||
redirect(r'^firefox/organizations/faq/?$', 'firefox.organizations.organizations'),
|
||||
|
||||
# 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/',
|
||||
permanent=False),
|
||||
permanent=False,
|
||||
),
|
||||
|
||||
# bug 1430894
|
||||
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.firefox import views as fx_views
|
||||
from bedrock.firefox.firefox_details import FirefoxDesktop, FirefoxAndroid, FirefoxIOS
|
||||
from bedrock.firefox.utils import product_details
|
||||
from bedrock.firefox.firefox_details import FirefoxDesktop, FirefoxAndroid
|
||||
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': {}}
|
||||
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):
|
||||
def setUp(self):
|
||||
|
@ -94,12 +89,19 @@ class TestInstallerHelp(TestCase):
|
|||
locale=None)
|
||||
|
||||
|
||||
@patch.object(fx_views, 'firefox_desktop', firefox_desktop)
|
||||
class TestFirefoxAll(TestCase):
|
||||
pd_cache = caches['product-details']
|
||||
|
||||
def setUp(self):
|
||||
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'):
|
||||
with self.activate('en-US'):
|
||||
|
@ -121,35 +123,35 @@ class TestFirefoxAll(TestCase):
|
|||
doc = pq(resp.content)
|
||||
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[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[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[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[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[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[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[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[data-language="multi"] > ul > li > a')) == 2
|
||||
|
||||
|
@ -174,8 +176,10 @@ class TestFirefoxAll(TestCase):
|
|||
assert len(doc('.build-table')) == 1
|
||||
assert len(doc('.not-found.hide')) == 1
|
||||
|
||||
num_builds = len(firefox_desktop.get_filtered_full_builds('release'))
|
||||
num_builds += len(firefox_desktop.get_filtered_test_builds('release'))
|
||||
num_builds = len(
|
||||
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#en-US a')) == 7
|
||||
|
||||
|
@ -185,9 +189,9 @@ class TestFirefoxAll(TestCase):
|
|||
locale details are not updated yet, the filtered build list should not
|
||||
include the localized build.
|
||||
"""
|
||||
builds = firefox_desktop.get_filtered_full_builds('release')
|
||||
assert 'uz' in firefox_desktop.firefox_primary_builds
|
||||
assert 'uz' not in firefox_desktop.languages
|
||||
builds = self.firefox_desktop.get_filtered_full_builds('release')
|
||||
assert 'uz' in self.firefox_desktop.firefox_primary_builds
|
||||
assert 'uz' not in self.firefox_desktop.languages
|
||||
assert len([build for build in builds if build['locale'] == 'uz']) == 0
|
||||
|
||||
def test_android(self):
|
||||
|
@ -548,93 +552,6 @@ class TestFirstRun(TestCase):
|
|||
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())
|
||||
class TestTrackingProtectionTour(TestCase):
|
||||
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.test.client import RequestFactory
|
||||
|
@ -6,7 +6,6 @@ from django.test.client import RequestFactory
|
|||
from django_jinja.backend import Jinja2
|
||||
from pyquery import PyQuery as pq
|
||||
|
||||
from product_details import product_details
|
||||
from bedrock.mozorg.tests import TestCase
|
||||
|
||||
jinja_env = Jinja2.get_default()
|
||||
|
@ -20,6 +19,7 @@ def render(s, context=None):
|
|||
class TestDownloadButtons(TestCase):
|
||||
|
||||
def latest_version(self):
|
||||
from product_details import product_details
|
||||
return product_details.firefox_versions['LATEST_FIREFOX_VERSION']
|
||||
|
||||
def check_desktop_links(self, links):
|
||||
|
@ -46,8 +46,10 @@ class TestDownloadButtons(TestCase):
|
|||
|
||||
# Check that the rest of the links are Android and iOS
|
||||
assert pq(links[4]).attr('href') == settings.GOOGLE_PLAY_FIREFOX_LINK_UTMS
|
||||
assert (pq(links[5]).attr('href') ==
|
||||
settings.APPLE_APPSTORE_FIREFOX_LINK.replace('/{country}/', '/'))
|
||||
assert (
|
||||
pq(links[5]).attr('href') ==
|
||||
settings.APPLE_APPSTORE_FIREFOX_LINK.replace('/{country}/', '/')
|
||||
)
|
||||
|
||||
def test_button_force_direct(self):
|
||||
"""
|
||||
|
@ -289,6 +291,7 @@ class TestDownloadButtons(TestCase):
|
|||
class TestDownloadList(TestCase):
|
||||
|
||||
def latest_version(self):
|
||||
from product_details import product_details
|
||||
return product_details.firefox_versions['LATEST_FIREFOX_VERSION']
|
||||
|
||||
def check_desktop_links(self, links):
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
import json
|
||||
import os
|
||||
from urlparse import parse_qs
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from django.test import override_settings
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
|
@ -19,15 +19,20 @@ from bedrock.firefox import views
|
|||
from bedrock.mozorg.tests import TestCase
|
||||
|
||||
|
||||
@override_settings(STUB_ATTRIBUTION_HMAC_KEY='achievers',
|
||||
@override_settings(
|
||||
STUB_ATTRIBUTION_HMAC_KEY='achievers',
|
||||
STUB_ATTRIBUTION_RATE=1,
|
||||
STUB_ATTRIBUTION_MAX_LEN=200)
|
||||
STUB_ATTRIBUTION_MAX_LEN=200,
|
||||
)
|
||||
class TestStubAttributionCode(TestCase):
|
||||
def _get_request(self, params):
|
||||
rf = RequestFactory()
|
||||
return rf.get('/', params,
|
||||
return rf.get(
|
||||
'/',
|
||||
params,
|
||||
HTTP_X_REQUESTED_WITH='XMLHttpRequest',
|
||||
HTTP_ACCEPT='application/json')
|
||||
HTTP_ACCEPT='application/json',
|
||||
)
|
||||
|
||||
def test_not_ajax_request(self):
|
||||
req = RequestFactory().get('/', {'source': 'malibu'})
|
||||
|
@ -50,12 +55,16 @@ class TestStubAttributionCode(TestCase):
|
|||
assert resp['cache-control'] == 'max-age=300'
|
||||
data = json.loads(resp.content)
|
||||
# will it blend?
|
||||
attrs = parse_qs(querystringsafe_base64.decode(data['attribution_code']))
|
||||
attrs = parse_qs(
|
||||
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
|
||||
)
|
||||
# parse_qs returns a dict with lists for values
|
||||
attrs = {k: v[0] for k, v in attrs.items()}
|
||||
self.assertDictEqual(attrs, final_params)
|
||||
self.assertEqual(data['attribution_sig'],
|
||||
'1cdbee664f4e9ea32f14510995b41729a80eddc94cf26dc3c74894c6264c723c')
|
||||
self.assertEqual(
|
||||
data['attribution_sig'],
|
||||
'1cdbee664f4e9ea32f14510995b41729a80eddc94cf26dc3c74894c6264c723c',
|
||||
)
|
||||
|
||||
def test_no_valid_param_data(self):
|
||||
params = {'utm_source': 'br@ndt', 'utm_medium': 'ae<t>her'}
|
||||
|
@ -71,12 +80,16 @@ class TestStubAttributionCode(TestCase):
|
|||
assert resp['cache-control'] == 'max-age=300'
|
||||
data = json.loads(resp.content)
|
||||
# will it blend?
|
||||
attrs = parse_qs(querystringsafe_base64.decode(data['attribution_code']))
|
||||
attrs = parse_qs(
|
||||
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
|
||||
)
|
||||
# parse_qs returns a dict with lists for values
|
||||
attrs = {k: v[0] for k, v in attrs.items()}
|
||||
self.assertDictEqual(attrs, final_params)
|
||||
self.assertEqual(data['attribution_sig'],
|
||||
'1cdbee664f4e9ea32f14510995b41729a80eddc94cf26dc3c74894c6264c723c')
|
||||
self.assertEqual(
|
||||
data['attribution_sig'],
|
||||
'1cdbee664f4e9ea32f14510995b41729a80eddc94cf26dc3c74894c6264c723c',
|
||||
)
|
||||
|
||||
def test_some_valid_param_data(self):
|
||||
params = {'utm_source': 'brandt', 'utm_content': 'ae<t>her'}
|
||||
|
@ -92,12 +105,16 @@ class TestStubAttributionCode(TestCase):
|
|||
assert resp['cache-control'] == 'max-age=300'
|
||||
data = json.loads(resp.content)
|
||||
# will it blend?
|
||||
attrs = parse_qs(querystringsafe_base64.decode(data['attribution_code']))
|
||||
attrs = parse_qs(
|
||||
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
|
||||
)
|
||||
# parse_qs returns a dict with lists for values
|
||||
attrs = {k: v[0] for k, v in attrs.items()}
|
||||
self.assertDictEqual(attrs, final_params)
|
||||
self.assertEqual(data['attribution_sig'],
|
||||
'37946edae923b50f31f9dc10013dc0d23bed7dc6272611e12af46ff7a8d9d7dc')
|
||||
self.assertEqual(
|
||||
data['attribution_sig'],
|
||||
'37946edae923b50f31f9dc10013dc0d23bed7dc6272611e12af46ff7a8d9d7dc',
|
||||
)
|
||||
|
||||
def test_campaign_data_too_long(self):
|
||||
"""If the code is too long then the utm_campaign value should be truncated"""
|
||||
|
@ -121,14 +138,16 @@ class TestStubAttributionCode(TestCase):
|
|||
assert resp['cache-control'] == 'max-age=300'
|
||||
data = json.loads(resp.content)
|
||||
# will it blend?
|
||||
code = querystringsafe_base64.decode(data['attribution_code'])
|
||||
code = querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
|
||||
assert len(code) <= 200
|
||||
attrs = parse_qs(code)
|
||||
# parse_qs returns a dict with lists for values
|
||||
attrs = {k: v[0] for k, v in attrs.items()}
|
||||
self.assertDictEqual(attrs, final_params)
|
||||
self.assertEqual(data['attribution_sig'],
|
||||
'5f4f928ad022b15ce10d6dc962e21e12bbfba924d73a2605f3085760d3f93923')
|
||||
self.assertEqual(
|
||||
data['attribution_sig'],
|
||||
'5f4f928ad022b15ce10d6dc962e21e12bbfba924d73a2605f3085760d3f93923',
|
||||
)
|
||||
|
||||
def test_other_data_too_long_not_campaign(self):
|
||||
"""If the code is too long but not utm_campaign return error"""
|
||||
|
@ -161,12 +180,16 @@ class TestStubAttributionCode(TestCase):
|
|||
assert resp['cache-control'] == 'max-age=300'
|
||||
data = json.loads(resp.content)
|
||||
# will it blend?
|
||||
attrs = parse_qs(querystringsafe_base64.decode(data['attribution_code']))
|
||||
attrs = parse_qs(
|
||||
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
|
||||
)
|
||||
# parse_qs returns a dict with lists for values
|
||||
attrs = {k: v[0] for k, v in attrs.items()}
|
||||
self.assertDictEqual(attrs, final_params)
|
||||
self.assertEqual(data['attribution_sig'],
|
||||
'abcbb028f97d08b7f85d194e6d51b8a2d96823208fdd167ff5977786b562af66')
|
||||
self.assertEqual(
|
||||
data['attribution_sig'],
|
||||
'abcbb028f97d08b7f85d194e6d51b8a2d96823208fdd167ff5977786b562af66',
|
||||
)
|
||||
|
||||
def test_handles_referrer(self):
|
||||
params = {'utm_source': 'brandt', 'referrer': 'https://duckduckgo.com/privacy'}
|
||||
|
@ -182,15 +205,22 @@ class TestStubAttributionCode(TestCase):
|
|||
assert resp['cache-control'] == 'max-age=300'
|
||||
data = json.loads(resp.content)
|
||||
# will it blend?
|
||||
attrs = parse_qs(querystringsafe_base64.decode(data['attribution_code']))
|
||||
attrs = parse_qs(
|
||||
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
|
||||
)
|
||||
# parse_qs returns a dict with lists for values
|
||||
attrs = {k: v[0] for k, v in attrs.items()}
|
||||
self.assertDictEqual(attrs, final_params)
|
||||
self.assertEqual(data['attribution_sig'],
|
||||
'37946edae923b50f31f9dc10013dc0d23bed7dc6272611e12af46ff7a8d9d7dc')
|
||||
self.assertEqual(
|
||||
data['attribution_sig'],
|
||||
'37946edae923b50f31f9dc10013dc0d23bed7dc6272611e12af46ff7a8d9d7dc',
|
||||
)
|
||||
|
||||
def test_handles_referrer_no_source(self):
|
||||
params = {'referrer': 'https://example.com:5000/searchin', 'utm_medium': 'aether'}
|
||||
params = {
|
||||
'referrer': 'https://example.com:5000/searchin',
|
||||
'utm_medium': 'aether',
|
||||
}
|
||||
final_params = {
|
||||
'source': 'example.com:5000',
|
||||
'medium': 'referral',
|
||||
|
@ -203,12 +233,16 @@ class TestStubAttributionCode(TestCase):
|
|||
assert resp['cache-control'] == 'max-age=300'
|
||||
data = json.loads(resp.content)
|
||||
# will it blend?
|
||||
attrs = parse_qs(querystringsafe_base64.decode(data['attribution_code']))
|
||||
attrs = parse_qs(
|
||||
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
|
||||
)
|
||||
# parse_qs returns a dict with lists for values
|
||||
attrs = {k: v[0] for k, v in attrs.items()}
|
||||
self.assertDictEqual(attrs, final_params)
|
||||
self.assertEqual(data['attribution_sig'],
|
||||
'70e177b822f24fa9f8bc8e1caa382204632b3b2548db19bb32b97042c0ef0d47')
|
||||
self.assertEqual(
|
||||
data['attribution_sig'],
|
||||
'70e177b822f24fa9f8bc8e1caa382204632b3b2548db19bb32b97042c0ef0d47',
|
||||
)
|
||||
|
||||
def test_handles_referrer_utf8(self):
|
||||
"""Should ignore non-ascii domain names.
|
||||
|
@ -230,12 +264,16 @@ class TestStubAttributionCode(TestCase):
|
|||
assert resp['cache-control'] == 'max-age=300'
|
||||
data = json.loads(resp.content)
|
||||
# will it blend?
|
||||
attrs = parse_qs(querystringsafe_base64.decode(data['attribution_code']))
|
||||
attrs = parse_qs(
|
||||
querystringsafe_base64.decode(data['attribution_code'].encode()).decode()
|
||||
)
|
||||
# parse_qs returns a dict with lists for values
|
||||
attrs = {k: v[0] for k, v in attrs.items()}
|
||||
self.assertDictEqual(attrs, final_params)
|
||||
self.assertEqual(data['attribution_sig'],
|
||||
'1cdbee664f4e9ea32f14510995b41729a80eddc94cf26dc3c74894c6264c723c')
|
||||
self.assertEqual(
|
||||
data['attribution_sig'],
|
||||
'1cdbee664f4e9ea32f14510995b41729a80eddc94cf26dc3c74894c6264c723c',
|
||||
)
|
||||
|
||||
@override_settings(STUB_ATTRIBUTION_RATE=0.2)
|
||||
def test_rate_limit(self):
|
||||
|
@ -280,217 +318,285 @@ class TestSendToDeviceView(TestCase):
|
|||
return json.loads(resp.content)
|
||||
|
||||
def test_phone_or_email_required(self):
|
||||
resp_data = self._request({
|
||||
'platform': 'android',
|
||||
})
|
||||
resp_data = self._request({'platform': 'android'})
|
||||
assert not resp_data['success']
|
||||
assert 'phone-or-email' in resp_data['errors']
|
||||
assert not self.mock_send_sms.called
|
||||
assert not self.mock_subscribe.called
|
||||
|
||||
def test_send_android_sms(self):
|
||||
resp_data = self._request({
|
||||
'platform': 'android',
|
||||
'phone-or-email': '5558675309',
|
||||
})
|
||||
resp_data = self._request(
|
||||
{'platform': 'android', 'phone-or-email': '5558675309'}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_send_sms.assert_called_with('post', 'subscribe_sms', data={
|
||||
self.mock_send_sms.assert_called_with(
|
||||
'post',
|
||||
'subscribe_sms',
|
||||
data={
|
||||
'mobile_number': '5558675309',
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'],
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms'][
|
||||
'android'
|
||||
],
|
||||
'lang': 'en-US',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
def test_send_android_sms_non_en_us(self):
|
||||
resp_data = self._request({
|
||||
'platform': 'android',
|
||||
'phone-or-email': '015558675309',
|
||||
}, locale='de')
|
||||
resp_data = self._request(
|
||||
{'platform': 'android', 'phone-or-email': '015558675309'}, locale='de'
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_send_sms.assert_called_with('post', 'subscribe_sms', data={
|
||||
self.mock_send_sms.assert_called_with(
|
||||
'post',
|
||||
'subscribe_sms',
|
||||
data={
|
||||
'mobile_number': '015558675309',
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'],
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms'][
|
||||
'android'
|
||||
],
|
||||
'lang': 'de',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
def test_send_android_sms_with_country(self):
|
||||
resp_data = self._request({
|
||||
'platform': 'android',
|
||||
'phone-or-email': '5558675309',
|
||||
'country': 'de',
|
||||
})
|
||||
resp_data = self._request(
|
||||
{'platform': 'android', 'phone-or-email': '5558675309', 'country': 'de'}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_send_sms.assert_called_with('post', 'subscribe_sms', data={
|
||||
self.mock_send_sms.assert_called_with(
|
||||
'post',
|
||||
'subscribe_sms',
|
||||
data={
|
||||
'mobile_number': '5558675309',
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'],
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms'][
|
||||
'android'
|
||||
],
|
||||
'lang': 'en-US',
|
||||
'country': 'de',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
def test_send_android_sms_with_invalid_country(self):
|
||||
resp_data = self._request({
|
||||
'platform': 'android',
|
||||
'phone-or-email': '5558675309',
|
||||
'country': 'X2',
|
||||
})
|
||||
resp_data = self._request(
|
||||
{'platform': 'android', 'phone-or-email': '5558675309', 'country': 'X2'}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_send_sms.assert_called_with('post', 'subscribe_sms', data={
|
||||
self.mock_send_sms.assert_called_with(
|
||||
'post',
|
||||
'subscribe_sms',
|
||||
data={
|
||||
'mobile_number': '5558675309',
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'],
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms'][
|
||||
'android'
|
||||
],
|
||||
'lang': 'en-US',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
resp_data = self._request({
|
||||
'platform': 'android',
|
||||
'phone-or-email': '5558675309',
|
||||
'country': 'dude',
|
||||
})
|
||||
resp_data = self._request(
|
||||
{'platform': 'android', 'phone-or-email': '5558675309', 'country': 'dude'}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_send_sms.assert_called_with('post', 'subscribe_sms', data={
|
||||
self.mock_send_sms.assert_called_with(
|
||||
'post',
|
||||
'subscribe_sms',
|
||||
data={
|
||||
'mobile_number': '5558675309',
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'],
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms'][
|
||||
'android'
|
||||
],
|
||||
'lang': 'en-US',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
def test_send_android_sms_basket_error(self):
|
||||
self.mock_send_sms.side_effect = views.basket.BasketException
|
||||
resp_data = self._request({
|
||||
'platform': 'android',
|
||||
'phone-or-email': '5558675309',
|
||||
}, 400)
|
||||
resp_data = self._request(
|
||||
{'platform': 'android', 'phone-or-email': '5558675309'}, 400
|
||||
)
|
||||
assert not resp_data['success']
|
||||
assert 'system' in resp_data['errors']
|
||||
|
||||
def test_send_bad_sms_number(self):
|
||||
self.mock_send_sms.side_effect = views.basket.BasketException('mobile_number is invalid')
|
||||
resp_data = self._request({
|
||||
'platform': 'android',
|
||||
'phone-or-email': '555',
|
||||
})
|
||||
self.mock_send_sms.side_effect = views.basket.BasketException(
|
||||
'mobile_number is invalid'
|
||||
)
|
||||
resp_data = self._request({'platform': 'android', 'phone-or-email': '555'})
|
||||
assert not resp_data['success']
|
||||
assert 'number' in resp_data['errors']
|
||||
|
||||
def test_send_android_email(self):
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'platform': 'android',
|
||||
'phone-or-email': 'dude@example.com',
|
||||
'source-url': 'https://nihilism.info',
|
||||
})
|
||||
}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_subscribe.assert_called_with('dude@example.com',
|
||||
self.mock_subscribe.assert_called_with(
|
||||
'dude@example.com',
|
||||
views.SEND_TO_DEVICE_MESSAGE_SETS['default']['email']['android'],
|
||||
source_url='https://nihilism.info',
|
||||
lang='en-US')
|
||||
lang='en-US',
|
||||
)
|
||||
|
||||
def test_send_android_email_basket_error(self):
|
||||
self.mock_subscribe.side_effect = views.basket.BasketException
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'platform': 'android',
|
||||
'phone-or-email': 'dude@example.com',
|
||||
'source-url': 'https://nihilism.info',
|
||||
}, 400)
|
||||
},
|
||||
400,
|
||||
)
|
||||
assert not resp_data['success']
|
||||
assert 'system' in resp_data['errors']
|
||||
|
||||
def test_send_android_bad_email(self):
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'platform': 'android',
|
||||
'phone-or-email': '@example.com',
|
||||
'source-url': 'https://nihilism.info',
|
||||
})
|
||||
}
|
||||
)
|
||||
assert not resp_data['success']
|
||||
assert 'email' in resp_data['errors']
|
||||
assert not self.mock_subscribe.called
|
||||
|
||||
# an invalid value for 'message-set' should revert to 'default' message set
|
||||
def test_invalid_message_set(self):
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'platform': 'ios',
|
||||
'phone-or-email': '5558675309',
|
||||
'message-set': 'the-dude-is-not-in',
|
||||
})
|
||||
}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_send_sms.assert_called_with('post', 'subscribe_sms', data={
|
||||
self.mock_send_sms.assert_called_with(
|
||||
'post',
|
||||
'subscribe_sms',
|
||||
data={
|
||||
'mobile_number': '5558675309',
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['ios'],
|
||||
'lang': 'en-US',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
# /firefox/android/ embedded widget (bug 1221328)
|
||||
def test_android_embedded_email(self):
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'platform': 'android',
|
||||
'phone-or-email': 'dude@example.com',
|
||||
'message-set': 'fx-android',
|
||||
})
|
||||
}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_subscribe.assert_called_with('dude@example.com',
|
||||
self.mock_subscribe.assert_called_with(
|
||||
'dude@example.com',
|
||||
views.SEND_TO_DEVICE_MESSAGE_SETS['fx-android']['email']['android'],
|
||||
source_url=None,
|
||||
lang='en-US')
|
||||
lang='en-US',
|
||||
)
|
||||
|
||||
def test_android_embedded_sms(self):
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'platform': 'android',
|
||||
'phone-or-email': '5558675309',
|
||||
'message-set': 'fx-android',
|
||||
})
|
||||
}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_send_sms.assert_called_with('post', 'subscribe_sms', data={
|
||||
self.mock_send_sms.assert_called_with(
|
||||
'post',
|
||||
'subscribe_sms',
|
||||
data={
|
||||
'mobile_number': '5558675309',
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['fx-android']['sms']['android'],
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['fx-android']['sms'][
|
||||
'android'
|
||||
],
|
||||
'lang': 'en-US',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
# /firefox/mobile-download/desktop
|
||||
def test_fx_mobile_download_desktop_email(self):
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'phone-or-email': 'dude@example.com',
|
||||
'message-set': 'fx-mobile-download-desktop',
|
||||
})
|
||||
}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_subscribe.assert_called_with('dude@example.com',
|
||||
views.SEND_TO_DEVICE_MESSAGE_SETS['fx-mobile-download-desktop']['email']['all'],
|
||||
self.mock_subscribe.assert_called_with(
|
||||
'dude@example.com',
|
||||
views.SEND_TO_DEVICE_MESSAGE_SETS['fx-mobile-download-desktop']['email'][
|
||||
'all'
|
||||
],
|
||||
source_url=None,
|
||||
lang='en-US')
|
||||
lang='en-US',
|
||||
)
|
||||
|
||||
def test_fx_mobile_download_desktop_sms(self):
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'phone-or-email': '5558675309',
|
||||
'message-set': 'fx-mobile-download-desktop',
|
||||
})
|
||||
}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_send_sms.assert_called_with('post', 'subscribe_sms', data={
|
||||
self.mock_send_sms.assert_called_with(
|
||||
'post',
|
||||
'subscribe_sms',
|
||||
data={
|
||||
'mobile_number': '5558675309',
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['fx-mobile-download-desktop']['sms']['all'],
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS[
|
||||
'fx-mobile-download-desktop'
|
||||
]['sms']['all'],
|
||||
'lang': 'en-US',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
def test_sms_number_with_punctuation(self):
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'phone-or-email': '(555) 867-5309',
|
||||
'message-set': 'fx-mobile-download-desktop',
|
||||
})
|
||||
}
|
||||
)
|
||||
assert resp_data['success']
|
||||
self.mock_send_sms.assert_called_with('post', 'subscribe_sms', data={
|
||||
self.mock_send_sms.assert_called_with(
|
||||
'post',
|
||||
'subscribe_sms',
|
||||
data={
|
||||
'mobile_number': '5558675309',
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['fx-mobile-download-desktop']['sms']['all'],
|
||||
'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS[
|
||||
'fx-mobile-download-desktop'
|
||||
]['sms']['all'],
|
||||
'lang': 'en-US',
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
def test_sms_number_too_long(self):
|
||||
resp_data = self._request({
|
||||
resp_data = self._request(
|
||||
{
|
||||
'phone-or-email': '5558675309555867530912',
|
||||
'message-set': 'fx-mobile-download-desktop',
|
||||
})
|
||||
}
|
||||
)
|
||||
assert not resp_data['success']
|
||||
self.mock_send_sms.assert_not_called()
|
||||
assert 'number' in resp_data['errors']
|
||||
|
||||
def test_sms_number_too_short(self):
|
||||
resp_data = self._request({
|
||||
'phone-or-email': '555',
|
||||
'message-set': 'fx-mobile-download-desktop',
|
||||
})
|
||||
resp_data = self._request(
|
||||
{'phone-or-email': '555', 'message-set': 'fx-mobile-download-desktop'}
|
||||
)
|
||||
assert not resp_data['success']
|
||||
self.mock_send_sms.assert_not_called()
|
||||
assert 'number' in resp_data['errors']
|
||||
|
@ -504,7 +610,9 @@ class TestFirefoxNew(TestCase):
|
|||
req = RequestFactory().get('/firefox/new/')
|
||||
req.locale = 'en-US'
|
||||
views.new(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/new/trailhead/download.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/new/trailhead/download.html', ANY
|
||||
)
|
||||
|
||||
@patch.object(views, 'lang_file_is_active', lambda *x: False)
|
||||
def test_download_old_template(self, render_mock):
|
||||
|
@ -518,7 +626,9 @@ class TestFirefoxNew(TestCase):
|
|||
req = RequestFactory().get('/firefox/download/thanks/')
|
||||
req.locale = 'en-US'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/new/trailhead/thanks.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/new/trailhead/thanks.html', ANY
|
||||
)
|
||||
|
||||
@patch.object(views, 'lang_file_is_active', lambda *x: False)
|
||||
def test_thanks_old_template(self, render_mock):
|
||||
|
@ -532,7 +642,9 @@ class TestFirefoxNew(TestCase):
|
|||
req.locale = 'en-US'
|
||||
resp = views.new(req)
|
||||
assert resp.status_code == 301
|
||||
assert resp['location'].endswith('/firefox/download/thanks/?scene=2&dude=abides')
|
||||
assert resp['location'].endswith(
|
||||
'/firefox/download/thanks/?scene=2&dude=abides'
|
||||
)
|
||||
|
||||
# yandex - issue 5635
|
||||
|
||||
|
@ -549,7 +661,9 @@ class TestFirefoxNew(TestCase):
|
|||
req = RequestFactory().get('/firefox/new/')
|
||||
req.locale = 'ru'
|
||||
views.new(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/new/trailhead/download.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/new/trailhead/download.html', ANY
|
||||
)
|
||||
|
||||
|
||||
class TestFirefoxNewNoIndex(TestCase):
|
||||
|
@ -580,7 +694,9 @@ class TestFirefoxCampaign(TestCase):
|
|||
req = RequestFactory().get('/firefox/campaign/')
|
||||
req.locale = 'en-US'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/index-trailhead.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/index-trailhead.html', ANY
|
||||
)
|
||||
|
||||
# berlin campaign bug 1447445 + 3 berlin variations bug 1473357
|
||||
|
||||
|
@ -589,125 +705,165 @@ class TestFirefoxCampaign(TestCase):
|
|||
req = RequestFactory().get('/firefox/campaign/?xv=berlin')
|
||||
req.locale = 'de'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene1.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene1.html', ANY
|
||||
)
|
||||
|
||||
def test_berlin_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=berlin')
|
||||
req.locale = 'de'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene2.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene2.html', ANY
|
||||
)
|
||||
|
||||
def test_berlin_nonde_scene_1(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/new/?xv=berlin')
|
||||
req.locale = 'en-US'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/index-trailhead.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/index-trailhead.html', ANY
|
||||
)
|
||||
|
||||
def test_berlin_nonde_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=berlin')
|
||||
req.locale = 'en-US'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/new/trailhead/thanks.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/new/trailhead/thanks.html', ANY
|
||||
)
|
||||
|
||||
# herz
|
||||
def test_variation_herz_scene_1(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=herz')
|
||||
req.locale = 'de'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene1-herz.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene1-herz.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_herz_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=herz')
|
||||
req.locale = 'de'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene2-herz.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene2-herz.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_herz_nonde_scene_1(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=herz')
|
||||
req.locale = 'en-US'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/index-trailhead.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/index-trailhead.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_herz_nonde_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=herz')
|
||||
req.locale = 'en-US'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/new/trailhead/thanks.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/new/trailhead/thanks.html', ANY
|
||||
)
|
||||
|
||||
# geschwindigkeit
|
||||
def test_variation_speed_scene_1(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=geschwindigkeit')
|
||||
req.locale = 'de'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene1-gesch.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene1-gesch.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_speed_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=geschwindigkeit')
|
||||
req.locale = 'de'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene2-gesch.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene2-gesch.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_speed_nonde_scene_1(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=geschwindigkeit')
|
||||
req.locale = 'en-US'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/index-trailhead.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/index-trailhead.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_speed_nonde_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=geschwindigkeit')
|
||||
req.locale = 'en-US'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/new/trailhead/thanks.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/new/trailhead/thanks.html', ANY
|
||||
)
|
||||
|
||||
# privatsphare
|
||||
def test_variation_privacy_scene_1(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=privatsphare')
|
||||
req.locale = 'de'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene1-privat.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene1-privat.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_privacy_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=privatsphare')
|
||||
req.locale = 'de'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene2-privat.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene2-privat.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_privacy_nonde_scene_1(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=privatsphare')
|
||||
req.locale = 'en-US'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/index-trailhead.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/index-trailhead.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_privacy_nonde_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=privatsphare')
|
||||
req.locale = 'en-US'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/new/trailhead/thanks.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/new/trailhead/thanks.html', ANY
|
||||
)
|
||||
|
||||
# auf-deiner-seite
|
||||
def test_variation_oys_scene_1(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=auf-deiner-seite')
|
||||
req.locale = 'de'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene1-auf-deiner-seite.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene1-auf-deiner-seite.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_oys_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=auf-deiner-seite')
|
||||
req.locale = 'de'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene2-auf-deiner-seite.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene2-auf-deiner-seite.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_oys_nonde_scene_1(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=auf-deiner-seite')
|
||||
req.locale = 'en-US'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/index-trailhead.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/index-trailhead.html', ANY
|
||||
)
|
||||
|
||||
def test_variation_oys_nonde_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=auf-deiner-seite')
|
||||
req.locale = 'en-US'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/new/trailhead/thanks.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/new/trailhead/thanks.html', ANY
|
||||
)
|
||||
|
||||
# berlin video test issue 5637
|
||||
|
||||
|
@ -715,13 +871,17 @@ class TestFirefoxCampaign(TestCase):
|
|||
req = RequestFactory().get('/firefox/campaign/?xv=aus-gruenden')
|
||||
req.locale = 'de'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene1-aus-gruenden.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene1-aus-gruenden.html', ANY
|
||||
)
|
||||
|
||||
def test_berlin_video_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=aus-gruenden')
|
||||
req.locale = 'de'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/berlin/scene2-aus-gruenden.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/berlin/scene2-aus-gruenden.html', ANY
|
||||
)
|
||||
|
||||
# better browser test issue 5841
|
||||
|
||||
|
@ -729,27 +889,35 @@ class TestFirefoxCampaign(TestCase):
|
|||
req = RequestFactory().get('/firefox/campaign/?xv=betterbrowser')
|
||||
req.locale = 'en-US'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/better-browser/scene1.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/better-browser/scene1.html', ANY
|
||||
)
|
||||
|
||||
@patch.object(views, 'lang_file_is_active', lambda *x: True)
|
||||
def test_better_browser_scene_1_non_us(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=betterbrowser')
|
||||
req.locale = 'de'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/index-trailhead.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/index-trailhead.html', ANY
|
||||
)
|
||||
|
||||
def test_better_browser_scene_2(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=betterbrowser')
|
||||
req.locale = 'en-US'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/better-browser/scene2.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/better-browser/scene2.html', ANY
|
||||
)
|
||||
|
||||
@patch.object(views, 'lang_file_is_active', lambda *x: True)
|
||||
def test_better_browser_scene_2_non_us(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/download/thanks/?xv=betterbrowser')
|
||||
req.locale = 'fr'
|
||||
views.download_thanks(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/new/trailhead/thanks.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/new/trailhead/thanks.html', ANY
|
||||
)
|
||||
|
||||
# Safari SEM campaign bug #1479085
|
||||
|
||||
|
@ -757,14 +925,18 @@ class TestFirefoxCampaign(TestCase):
|
|||
req = RequestFactory().get('/firefox/campaign/?xv=safari')
|
||||
req.locale = 'en-US'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/compare/scene1-safari.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/compare/scene1-safari.html', ANY
|
||||
)
|
||||
|
||||
@patch.object(views, 'lang_file_is_active', lambda *x: True)
|
||||
def test_compare_safari_scene_1_non_us(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=safari')
|
||||
req.locale = 'fr'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/index-trailhead.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/index-trailhead.html', ANY
|
||||
)
|
||||
|
||||
# Edge SEM campaign Bug #1479086
|
||||
|
||||
|
@ -772,14 +944,18 @@ class TestFirefoxCampaign(TestCase):
|
|||
req = RequestFactory().get('/firefox/campaign/?xv=edge')
|
||||
req.locale = 'en-US'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/compare/scene1-edge.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/compare/scene1-edge.html', ANY
|
||||
)
|
||||
|
||||
@patch.object(views, 'lang_file_is_active', lambda *x: True)
|
||||
def test_compare_edge_scene_1_non_us(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/campaign/?xv=edge')
|
||||
req.locale = 'fr'
|
||||
views.campaign(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/campaign/index-trailhead.html', ANY)
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/campaign/index-trailhead.html', ANY
|
||||
)
|
||||
|
||||
|
||||
class TestFeedbackView(TestCase):
|
||||
|
@ -845,14 +1021,20 @@ class TestFirefoxHome(TestCase):
|
|||
def test_switch_off(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/')
|
||||
views.firefox_home(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/home/index.html', {'show_newsletter': False, 'variation': None})
|
||||
render_mock.assert_called_once_with(
|
||||
req,
|
||||
'firefox/home/index.html',
|
||||
{'show_newsletter': False, 'variation': None},
|
||||
)
|
||||
|
||||
@override_settings(DEV=True)
|
||||
@patch('bedrock.firefox.views.l10n_utils.render')
|
||||
def test_switch_on(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/')
|
||||
views.firefox_home(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/home/index.html', {'show_newsletter': True, 'variation': None})
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/home/index.html', {'show_newsletter': True, 'variation': None}
|
||||
)
|
||||
|
||||
@override_settings(DEV=True)
|
||||
@patch('bedrock.firefox.views.l10n_utils.render')
|
||||
|
@ -860,7 +1042,9 @@ class TestFirefoxHome(TestCase):
|
|||
req = RequestFactory().get('/firefox/')
|
||||
req.locale = 'de'
|
||||
views.firefox_home(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/home/index.html', {'show_newsletter': True, 'variation': None})
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/home/index.html', {'show_newsletter': True, 'variation': None}
|
||||
)
|
||||
|
||||
@override_settings(DEV=True)
|
||||
@patch('bedrock.firefox.views.l10n_utils.render')
|
||||
|
@ -868,7 +1052,9 @@ class TestFirefoxHome(TestCase):
|
|||
req = RequestFactory().get('/firefox/')
|
||||
req.locale = 'fr'
|
||||
views.firefox_home(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/home/index.html', {'show_newsletter': True, 'variation': None})
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/home/index.html', {'show_newsletter': True, 'variation': None}
|
||||
)
|
||||
|
||||
@override_settings(DEV=True)
|
||||
@patch('bedrock.firefox.views.l10n_utils.render')
|
||||
|
@ -876,25 +1062,40 @@ class TestFirefoxHome(TestCase):
|
|||
req = RequestFactory().get('/firefox/')
|
||||
req.locale = 'it'
|
||||
views.firefox_home(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/home/index.html', {'show_newsletter': False, 'variation': None})
|
||||
render_mock.assert_called_once_with(
|
||||
req,
|
||||
'firefox/home/index.html',
|
||||
{'show_newsletter': False, 'variation': None},
|
||||
)
|
||||
|
||||
@override_settings(DEV=False)
|
||||
@patch('bedrock.firefox.views.l10n_utils.render')
|
||||
def test_brand_var_a(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/?v=a')
|
||||
req.locale = 'en-US'
|
||||
views.firefox_home(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/home/index.html', {'show_newsletter': False, 'variation': 'a'})
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/home/index.html', {'show_newsletter': False, 'variation': 'a'}
|
||||
)
|
||||
|
||||
@override_settings(DEV=False)
|
||||
@patch('bedrock.firefox.views.l10n_utils.render')
|
||||
def test_brand_var_b(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/?v=b')
|
||||
req.locale = 'en-US'
|
||||
views.firefox_home(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/home/index-b.html', {'show_newsletter': False, 'variation': 'b'})
|
||||
render_mock.assert_called_once_with(
|
||||
req,
|
||||
'firefox/home/index-b.html',
|
||||
{'show_newsletter': False, 'variation': 'b'},
|
||||
)
|
||||
|
||||
@override_settings(DEV=False)
|
||||
@patch('bedrock.firefox.views.l10n_utils.render')
|
||||
def test_brand_locale_var_b(self, render_mock):
|
||||
req = RequestFactory().get('/firefox/?v=b')
|
||||
req.locale = 'fr'
|
||||
views.firefox_home(req)
|
||||
render_mock.assert_called_once_with(req, 'firefox/home/index.html', {'show_newsletter': False, 'variation': 'b'})
|
||||
render_mock.assert_called_once_with(
|
||||
req, 'firefox/home/index.html', {'show_newsletter': False, 'variation': 'b'}
|
||||
)
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
from django.conf.urls import url
|
||||
|
||||
from bedrock.mozorg.util import page
|
||||
|
||||
import views
|
||||
import bedrock.releasenotes.views
|
||||
from bedrock.mozorg.util import page
|
||||
from bedrock.releasenotes import version_re
|
||||
|
||||
from bedrock.firefox import views
|
||||
|
||||
latest_re = r'^firefox(?:/(?P<version>%s))?/%s/$'
|
||||
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)'
|
||||
channel_re = '(?P<channel>beta|aurora|developer|nightly|organizations)'
|
||||
releasenotes_re = latest_re % (version_re, r'(aurora|release)notes')
|
||||
android_releasenotes_re = releasenotes_re.replace('firefox', 'firefox/android')
|
||||
ios_releasenotes_re = releasenotes_re.replace('firefox', 'firefox/ios')
|
||||
android_releasenotes_re = releasenotes_re.replace(r'firefox', 'firefox/android')
|
||||
ios_releasenotes_re = releasenotes_re.replace(r'firefox', 'firefox/ios')
|
||||
sysreq_re = latest_re % (version_re, 'system-requirements')
|
||||
android_sysreq_re = sysreq_re.replace('firefox', 'firefox/android')
|
||||
ios_sysreq_re = sysreq_re.replace('firefox', 'firefox/ios')
|
||||
android_sysreq_re = sysreq_re.replace(r'firefox', 'firefox/android')
|
||||
ios_sysreq_re = sysreq_re.replace(r'firefox', 'firefox/ios')
|
||||
|
||||
|
||||
urlpatterns = (
|
||||
|
|
|
@ -7,33 +7,35 @@ import hashlib
|
|||
import hmac
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from urlparse 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
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import basket
|
||||
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.l10n_utils.dotlang import lang_file_is_active
|
||||
from product_details.version_compare import Version
|
||||
|
||||
from bedrock.base.urlresolvers import reverse
|
||||
from bedrock.base.waffle import switch
|
||||
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.mozorg.util import HttpResponseJSON
|
||||
from bedrock.newsletter.forms import NewsletterFooterForm
|
||||
from bedrock.releasenotes import version_re
|
||||
from bedrock.wordpress.views import BlogPostsView
|
||||
|
||||
|
||||
UA_REGEXP = re.compile(r"Firefox/(%s)" % version_re)
|
||||
|
||||
INSTALLER_CHANNElS = [
|
||||
|
@ -57,10 +59,7 @@ STUB_VALUE_RE = re.compile(r'^[a-z0-9-.%():_]+$', flags=re.IGNORECASE)
|
|||
def installer_help(request):
|
||||
installer_lang = request.GET.get('installer_lang', None)
|
||||
installer_channel = request.GET.get('channel', None)
|
||||
context = {
|
||||
'installer_lang': None,
|
||||
'installer_channel': None,
|
||||
}
|
||||
context = {'installer_lang': None, 'installer_channel': None}
|
||||
|
||||
if installer_lang and installer_lang in firefox_desktop.languages:
|
||||
context['installer_lang'] = installer_lang
|
||||
|
@ -75,14 +74,14 @@ def installer_help(request):
|
|||
def stub_attribution_code(request):
|
||||
"""Return a JSON response containing the HMAC signed stub attribution value"""
|
||||
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
|
||||
if not settings.STUB_ATTRIBUTION_RATE:
|
||||
# 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:
|
||||
response = HttpResponseJSON({'error': 'service not configured'}, status=403)
|
||||
response = JsonResponse({'error': 'service not configured'}, status=403)
|
||||
|
||||
if response:
|
||||
patch_response_headers(response, 300) # 5 min
|
||||
|
@ -118,9 +117,9 @@ def stub_attribution_code(request):
|
|||
|
||||
code_data = sign_attribution_codes(codes)
|
||||
if code_data:
|
||||
response = HttpResponseJSON(code_data)
|
||||
response = JsonResponse(code_data)
|
||||
else:
|
||||
response = HttpResponseJSON({'error': 'Invalid code'}, status=400)
|
||||
response = JsonResponse({'error': 'Invalid code'}, status=400)
|
||||
|
||||
patch_response_headers(response, 300) # 5 min
|
||||
return response
|
||||
|
@ -150,12 +149,9 @@ def sign_attribution_codes(codes):
|
|||
if len(code) > settings.STUB_ATTRIBUTION_MAX_LEN:
|
||||
return None
|
||||
|
||||
code = querystringsafe_base64.encode(code)
|
||||
sig = hmac.new(key, code, hashlib.sha256).hexdigest()
|
||||
return {
|
||||
'attribution_code': code,
|
||||
'attribution_sig': sig,
|
||||
}
|
||||
code = querystringsafe_base64.encode(code.encode())
|
||||
sig = hmac.new(key.encode(), code, hashlib.sha256).hexdigest()
|
||||
return {'attribution_code': code.decode(), 'attribution_sig': sig}
|
||||
|
||||
|
||||
@require_POST
|
||||
|
@ -166,15 +162,13 @@ def send_to_device_ajax(request):
|
|||
|
||||
# ensure a value was entered in phone or email field
|
||||
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)
|
||||
message_set = request.POST.get('message-set', 'default')
|
||||
|
||||
# begin collecting data to pass to form constructor
|
||||
data = {
|
||||
'platform': request.POST.get('platform'),
|
||||
}
|
||||
data = {'platform': request.POST.get('platform')}
|
||||
|
||||
# determine if email or phone number was submitted
|
||||
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)
|
||||
except basket.BasketException as e:
|
||||
if e.desc == 'mobile_number is invalid':
|
||||
return HttpResponseJSON({'success': False, 'errors': ['number']})
|
||||
return JsonResponse({'success': False, 'errors': ['number']})
|
||||
else:
|
||||
return HttpResponseJSON({'success': False, 'errors': ['system']},
|
||||
status=400)
|
||||
return JsonResponse(
|
||||
{'success': False, 'errors': ['system']}, status=400
|
||||
)
|
||||
else:
|
||||
return HttpResponseJSON({'success': False, 'errors': ['platform']})
|
||||
return JsonResponse({'success': False, 'errors': ['platform']})
|
||||
else: # email
|
||||
if platform in MESSAGES['email']:
|
||||
try:
|
||||
basket.subscribe(phone_or_email, MESSAGES['email'][platform],
|
||||
basket.subscribe(
|
||||
phone_or_email,
|
||||
MESSAGES['email'][platform],
|
||||
source_url=request.POST.get('source-url'),
|
||||
lang=locale)
|
||||
lang=locale,
|
||||
)
|
||||
except basket.BasketException:
|
||||
return HttpResponseJSON({'success': False, 'errors': ['system']},
|
||||
status=400)
|
||||
return JsonResponse(
|
||||
{'success': False, 'errors': ['system']}, status=400
|
||||
)
|
||||
else:
|
||||
return HttpResponseJSON({'success': False, 'errors': ['platform']})
|
||||
return JsonResponse({'success': False, 'errors': ['platform']})
|
||||
|
||||
resp_data = {'success': True}
|
||||
else:
|
||||
resp_data = {
|
||||
'success': False,
|
||||
'errors': form.errors.keys(),
|
||||
}
|
||||
resp_data = {'success': False, 'errors': list(form.errors)}
|
||||
|
||||
return HttpResponseJSON(resp_data)
|
||||
return JsonResponse(resp_data)
|
||||
|
||||
|
||||
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.
|
||||
# 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(request, platform, channel)
|
||||
|
@ -258,7 +258,8 @@ def all_downloads_unified(request):
|
|||
product_desktop = firefox_desktop
|
||||
|
||||
# Human-readable product labels
|
||||
products = OrderedDict([
|
||||
products = OrderedDict(
|
||||
[
|
||||
('desktop_release', 'Firefox'),
|
||||
('desktop_beta', 'Firefox Beta'),
|
||||
('desktop_developer', 'Firefox Developer Edition'),
|
||||
|
@ -267,7 +268,8 @@ def all_downloads_unified(request):
|
|||
('android_release', 'Firefox Android'),
|
||||
('android_beta', 'Firefox Android Beta'),
|
||||
('android_nightly', 'Firefox Android Nightly'),
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
channel_release = 'release'
|
||||
channel_beta = 'beta'
|
||||
|
@ -289,52 +291,84 @@ def all_downloads_unified(request):
|
|||
|
||||
context = {
|
||||
'products': products.items(),
|
||||
|
||||
'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_channel_label': product_desktop.channel_labels.get(channel_release, 'Firefox'),
|
||||
'desktop_release_full_builds': product_desktop.get_filtered_full_builds(
|
||||
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_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_channel_label': product_desktop.channel_labels.get(channel_beta, 'Firefox'),
|
||||
'desktop_beta_full_builds': product_desktop.get_filtered_full_builds(
|
||||
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_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_channel_label': product_desktop.channel_labels.get(channel_dev, 'Firefox'),
|
||||
'desktop_developer_full_builds': product_desktop.get_filtered_full_builds(
|
||||
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_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_channel_label': product_desktop.channel_labels.get(channel_nightly, 'Firefox'),
|
||||
'desktop_nightly_full_builds': product_desktop.get_filtered_full_builds(
|
||||
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_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_channel_label': product_desktop.channel_labels.get(channel_esr, 'Firefox'),
|
||||
'desktop_esr_full_builds': product_desktop.get_filtered_full_builds(
|
||||
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,
|
||||
|
||||
'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_channel_label': product_android.channel_labels.get(channel_release, 'Firefox'),
|
||||
'android_release_full_builds': product_android.get_filtered_full_builds(
|
||||
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_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_channel_label': product_android.channel_labels.get(channel_beta, 'Firefox'),
|
||||
'android_beta_full_builds': product_android.get_filtered_full_builds(
|
||||
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_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_channel_label': product_android.channel_labels.get(channel_nightly, 'Firefox'),
|
||||
'android_nightly_full_builds': product_android.get_filtered_full_builds(
|
||||
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,
|
||||
}
|
||||
|
||||
if latest_esr_next_version_desktop:
|
||||
context['desktop_esr_platforms_next'] = product_desktop.platforms(channel_esr_next, True)
|
||||
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_platforms_next'] = product_desktop.platforms(
|
||||
channel_esr_next, True
|
||||
)
|
||||
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
|
||||
|
||||
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/
|
||||
if platform == 'android' and channel == 'alpha':
|
||||
return HttpResponsePermanentRedirect(
|
||||
reverse('firefox.all', kwargs={'platform': 'android', 'channel': 'nightly'}))
|
||||
reverse('firefox.all', kwargs={'platform': 'android', 'channel': 'nightly'})
|
||||
)
|
||||
|
||||
version = product.latest_version(channel)
|
||||
query = request.GET.get('q')
|
||||
|
@ -387,10 +422,12 @@ def all_downloads(request, platform, channel):
|
|||
next_version = firefox_desktop.latest_version('esr_next')
|
||||
if next_version:
|
||||
context['full_builds_next_version'] = next_version.split('.', 1)[0]
|
||||
context['full_builds_next'] = firefox_desktop.get_filtered_full_builds('esr_next',
|
||||
next_version, query)
|
||||
context['test_builds_next'] = firefox_desktop.get_filtered_test_builds('esr_next',
|
||||
next_version, query)
|
||||
context['full_builds_next'] = firefox_desktop.get_filtered_full_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)
|
||||
|
||||
|
||||
|
@ -491,8 +528,6 @@ class FirstrunView(l10n_utils.LangFilesMixin, TemplateView):
|
|||
template = 'firefox/developer/firstrun.html'
|
||||
else:
|
||||
template = 'firefox/dev-firstrun.html'
|
||||
elif show_62_firstrun(version):
|
||||
template = 'firefox/firstrun/firstrun-quantum.html'
|
||||
else:
|
||||
template = 'firefox/firstrun/firstrun-quantum.html'
|
||||
|
||||
|
@ -539,7 +574,7 @@ class WhatsnewView(l10n_utils.LangFilesMixin, TemplateView):
|
|||
'fr',
|
||||
'ru',
|
||||
'de',
|
||||
'pl'
|
||||
'pl',
|
||||
]
|
||||
|
||||
return ctx
|
||||
|
@ -599,12 +634,14 @@ class WhatsnewView(l10n_utils.LangFilesMixin, TemplateView):
|
|||
|
||||
class FeedbackView(TemplateView):
|
||||
|
||||
donate_url = ('https://donate.mozilla.org/'
|
||||
donate_url = (
|
||||
'https://donate.mozilla.org/'
|
||||
'?utm_source=Heartbeat_survey&utm_medium=referral'
|
||||
'&utm_content=Heartbeat_{0}stars')
|
||||
'&utm_content=Heartbeat_{0}stars'
|
||||
)
|
||||
|
||||
def get_score(self):
|
||||
return self.request.GET.get('score', 0)
|
||||
return self.request.GET.get('score', '0')
|
||||
|
||||
def get_template_names(self):
|
||||
score = self.get_score()
|
||||
|
@ -626,12 +663,13 @@ class FeedbackView(TemplateView):
|
|||
|
||||
|
||||
class TrackingProtectionTourView(l10n_utils.LangFilesMixin, TemplateView):
|
||||
|
||||
def get_template_names(self):
|
||||
variation = self.request.GET.get('variation', None)
|
||||
|
||||
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:
|
||||
template = 'firefox/tracking-protection-tour/index.html'
|
||||
|
||||
|
@ -639,12 +677,13 @@ class TrackingProtectionTourView(l10n_utils.LangFilesMixin, TemplateView):
|
|||
|
||||
|
||||
class ContentBlockingTourView(l10n_utils.LangFilesMixin, TemplateView):
|
||||
|
||||
def get_template_names(self):
|
||||
variation = self.request.GET.get('variation', None)
|
||||
|
||||
if variation in ['2']:
|
||||
template = 'firefox/content-blocking-tour/variation-{}.html'.format(variation)
|
||||
template = 'firefox/content-blocking-tour/variation-{}.html'.format(
|
||||
variation
|
||||
)
|
||||
else:
|
||||
template = 'firefox/content-blocking-tour/index.html'
|
||||
|
||||
|
@ -656,7 +695,21 @@ def download_thanks(request):
|
|||
locale = l10n_utils.get_locale(request)
|
||||
variant = request.GET.get('v', 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
|
||||
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')
|
||||
query_string = request.META.get('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)
|
||||
# if no/incorrect scene specified, show scene 1
|
||||
else:
|
||||
|
@ -729,7 +784,9 @@ def new(request):
|
|||
|
||||
# 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)
|
||||
return l10n_utils.render(request, template, {'experience': experience, 'v': variant})
|
||||
return l10n_utils.render(
|
||||
request, template, {'experience': experience, 'v': variant}
|
||||
)
|
||||
|
||||
|
||||
def campaign(request):
|
||||
|
@ -779,16 +836,18 @@ def campaign(request):
|
|||
|
||||
# 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)
|
||||
return l10n_utils.render(request, template, {'experience': experience, 'v': variant})
|
||||
return l10n_utils.render(
|
||||
request, template, {'experience': experience, 'v': variant}
|
||||
)
|
||||
|
||||
|
||||
def ios_testflight(request):
|
||||
# no country field, so no need to send locale
|
||||
newsletter_form = NewsletterFooterForm('ios-beta-test-flight', '')
|
||||
|
||||
return l10n_utils.render(request,
|
||||
'firefox/testflight.html',
|
||||
{'newsletter_form': newsletter_form})
|
||||
return l10n_utils.render(
|
||||
request, 'firefox/testflight.html', {'newsletter_form': newsletter_form}
|
||||
)
|
||||
|
||||
|
||||
def ad_blocker(request):
|
||||
|
@ -847,10 +906,18 @@ def firefox_home(request):
|
|||
locale = l10n_utils.get_locale(request)
|
||||
variant = request.GET.get('v', None)
|
||||
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
|
||||
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
|
||||
|
||||
if locale == 'en-US' and variant is not None and variant != 'a':
|
||||
|
@ -858,9 +925,9 @@ def firefox_home(request):
|
|||
else:
|
||||
template = 'firefox/home/index.html'
|
||||
|
||||
return l10n_utils.render(request,
|
||||
template,
|
||||
{'show_newsletter': show_newsletter, 'variation': variant})
|
||||
return l10n_utils.render(
|
||||
request, template, {'show_newsletter': show_newsletter, 'variation': variant}
|
||||
)
|
||||
|
||||
|
||||
def firefox_concerts(request):
|
||||
|
@ -881,13 +948,13 @@ def firefox_accounts(request):
|
|||
locale = l10n_utils.get_locale(request)
|
||||
|
||||
# get localized blog post URL for 2019 page
|
||||
promise_query = ('?utm_source=www.mozilla.org&utm_medium=referral&utm_campaign=accounts-trailhead'
|
||||
'&utm_content=accounts-value&utm_term=respect-you-deserve')
|
||||
promise_query = (
|
||||
'?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'])
|
||||
|
||||
context = {
|
||||
'promise_url': promise_url + promise_query,
|
||||
}
|
||||
context = {'promise_url': promise_url + promise_query}
|
||||
|
||||
if lang_file_is_active('firefox/accounts-2019', locale):
|
||||
template_name = 'firefox/accounts-2019.html'
|
||||
|
@ -903,7 +970,7 @@ def election_with_cards(request):
|
|||
locale = l10n_utils.get_locale(request)
|
||||
ctx = {
|
||||
'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':
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<div class="filters">
|
||||
<h4>Filter by</h4>
|
||||
<ul>
|
||||
{% for key, label in grant_labels.iteritems() %}
|
||||
{% for key, label in grant_labels.items() %}
|
||||
{% if key == filter %}
|
||||
<li>{{ label }}</li>
|
||||
{% else %}
|
||||
|
|
|
@ -14,7 +14,7 @@ class TestGrants(TestCase):
|
|||
def test_grant_url_slug(self):
|
||||
"""Grant url slug must be composed of a-z, 0-9, _, and -."""
|
||||
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):
|
||||
"""Grant grantee must be a string."""
|
||||
|
@ -45,12 +45,12 @@ class TestGrants(TestCase):
|
|||
def test_grant_total_support(self):
|
||||
"""Grant total_support must look like a monetary amount."""
|
||||
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):
|
||||
"""Grant year must be in the range 2006 to next year."""
|
||||
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:
|
||||
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.mozorg.util import page
|
||||
|
||||
import views
|
||||
from bedrock.grants import views
|
||||
|
||||
|
||||
urlpatterns = (
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.http import Http404
|
|||
from lib import l10n_utils
|
||||
import bleach
|
||||
|
||||
from grants_db import GRANTS
|
||||
from bedrock.grants.grants_db import GRANTS
|
||||
|
||||
grant_labels = {
|
||||
'': 'All',
|
||||
|
@ -20,7 +20,7 @@ grant_labels = {
|
|||
|
||||
|
||||
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:
|
||||
raise Http404
|
||||
|
@ -38,7 +38,7 @@ def grants(request):
|
|||
raise Http404
|
||||
|
||||
if type_filter:
|
||||
grants = filter(lambda k: k.type == type_filter, GRANTS)
|
||||
grants = [k for k in GRANTS if k.type == type_filter]
|
||||
else:
|
||||
grants = GRANTS
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ class TestFraudReport(TestCase):
|
|||
return SimpleUploadedFile('image.png', io.read(), 'image/png')
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -78,7 +78,7 @@ class TestFraudReport(TestCase):
|
|||
response = legal_views.fraud_report(request)
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -96,7 +96,7 @@ class TestFraudReport(TestCase):
|
|||
response = legal_views.fraud_report(request)
|
||||
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -2,18 +2,14 @@
|
|||
# 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/.
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from lib import l10n_utils
|
||||
|
||||
from django.core.mail import EmailMessage
|
||||
from django.shortcuts import redirect
|
||||
from django.template.loader import render_to_string
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
from bedrock.base.urlresolvers import reverse
|
||||
|
||||
from forms import FraudReportForm
|
||||
|
||||
from bedrock.legal.forms import FraudReportForm
|
||||
from lib import l10n_utils
|
||||
|
||||
FRAUD_REPORT_EMAIL_FROM = 'Mozilla.com <noreply@mozilla.com>'
|
||||
FRAUD_REPORT_EMAIL_SUBJECT = 'New trademark infringement report: %s; %s'
|
||||
|
|
|
@ -25,11 +25,11 @@ class TestLoadLegalDoc(TestCase):
|
|||
|
||||
@patch('os.path.exists')
|
||||
@patch.object(views, 'listdir')
|
||||
@patch.object(views, 'StringIO')
|
||||
@patch.object(views.io, 'BytesIO')
|
||||
@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."""
|
||||
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
|
||||
listdir_mock.return_value = ['.mkdir', 'de.md', 'en-US.md']
|
||||
doc = views.load_legal_doc('the_dude_exists', 'de')
|
||||
|
@ -41,11 +41,11 @@ class TestLoadLegalDoc(TestCase):
|
|||
|
||||
@patch('os.path.exists')
|
||||
@patch.object(views, 'listdir')
|
||||
@patch.object(views, 'StringIO')
|
||||
@patch.object(views.io, 'BytesIO')
|
||||
@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."""
|
||||
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
|
||||
listdir_mock.return_value = ['.mkdir', 'de.md', 'en-US.md', 'sw.md']
|
||||
doc = views.load_legal_doc('the_dude_exists', 'de')
|
||||
|
@ -57,14 +57,14 @@ class TestLoadLegalDoc(TestCase):
|
|||
|
||||
@patch('os.path.exists')
|
||||
@patch.object(views, 'listdir')
|
||||
@patch.object(views, 'StringIO')
|
||||
@patch.object(views.io, 'BytesIO')
|
||||
@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"""
|
||||
bedrock_locale = 'hi-IN'
|
||||
ld_locale = 'hi'
|
||||
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
|
||||
listdir_mock.return_value = [ld_filename, 'en-US.md']
|
||||
doc = views.load_legal_doc('the_dude_exists', bedrock_locale)
|
||||
|
@ -76,13 +76,13 @@ class TestLoadLegalDoc(TestCase):
|
|||
|
||||
@patch('os.path.exists')
|
||||
@patch.object(views, 'listdir')
|
||||
@patch.object(views, 'StringIO')
|
||||
@patch.object(views.io, 'BytesIO')
|
||||
@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"""
|
||||
bedrock_locale = 'hi-IN'
|
||||
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]
|
||||
listdir_mock.return_value = [ld_filename, 'en-US.md']
|
||||
doc = views.load_legal_doc('the_dude_exists', bedrock_locale)
|
||||
|
@ -123,7 +123,7 @@ class TestLegalDocView(TestCase):
|
|||
legal_doc_name='the_dude_exists')
|
||||
resp = view(req)
|
||||
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
|
||||
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/.
|
||||
|
||||
from os import path, listdir
|
||||
import StringIO
|
||||
import io
|
||||
|
||||
from django.conf import settings
|
||||
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_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')]
|
||||
# convert legal-docs locales to bedrock equivalents
|
||||
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:
|
||||
# Parse the Markdown file
|
||||
md.markdownFromFile(input=source_file, output=output,
|
||||
extensions=['attr_list', 'headerid',
|
||||
OutlineExtension((('wrapper_cls', ''),))])
|
||||
content = output.getvalue().decode('utf8')
|
||||
md.markdownFromFile(
|
||||
input=source_file, output=output, extensions=[
|
||||
'markdown.extensions.attr_list',
|
||||
'markdown.extensions.toc',
|
||||
OutlineExtension((('wrapper_cls', ''),))
|
||||
])
|
||||
content = output.getvalue().decode('utf-8')
|
||||
except IOError:
|
||||
content = None
|
||||
finally:
|
||||
|
|
|
@ -6,7 +6,7 @@ import re
|
|||
from datetime import datetime
|
||||
|
||||
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 lib.l10n_utils import get_locale
|
||||
|
|
|
@ -13,7 +13,7 @@ from bedrock.externalfiles import ExternalFile
|
|||
|
||||
class CreditsFile(ExternalFile):
|
||||
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
|
||||
raise ValueError('Much smaller file than expected. {0} rows.'.format(len(rows)))
|
||||
|
||||
|
@ -57,7 +57,7 @@ class CreditsFile(ExternalFile):
|
|||
else:
|
||||
continue
|
||||
|
||||
sortkey = unicodedata.normalize('NFKD', sortkey.decode('utf8')).encode('ascii', 'ignore')
|
||||
names.append([name.decode('utf8'), sortkey.upper()])
|
||||
sortkey = unicodedata.normalize('NFKD', sortkey).encode('ascii', 'ignore').decode()
|
||||
names.append([name, sortkey.upper()])
|
||||
|
||||
return sorted(names, key=itemgetter(1))
|
||||
|
|
|
@ -8,13 +8,10 @@ import re
|
|||
from datetime import datetime
|
||||
from random import randrange
|
||||
|
||||
from django import forms
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.urls import reverse
|
||||
from django.forms import widgets
|
||||
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 _lazy
|
||||
|
||||
|
@ -37,7 +34,7 @@ class PrivacyWidget(widgets.CheckboxInput):
|
|||
"""Render a checkbox with privacy text. Lots of pages need this so
|
||||
it should be standardized"""
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
attrs['required'] = 'required'
|
||||
input_txt = super(PrivacyWidget, self).render(name, value, attrs)
|
||||
|
||||
|
@ -55,7 +52,7 @@ class PrivacyWidget(widgets.CheckboxInput):
|
|||
class HoneyPotWidget(widgets.TextInput):
|
||||
"""Render a text field to (hopefully) trick bots. Will be used on many pages."""
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
honeypot_txt = _(u'Leave this field empty.')
|
||||
# semi-randomized in case we have more than one per page.
|
||||
# this is maybe/probably overthought
|
||||
|
@ -89,20 +86,3 @@ class TelInput(widgets.TextInput):
|
|||
|
||||
class NumberInput(widgets.TextInput):
|
||||
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.')
|
||||
|
||||
# currently 15 categories
|
||||
if not len(forums.keys()) > 10:
|
||||
if not len(forums) > 10:
|
||||
raise ValueError('Forums file truncated or corrupted.')
|
||||
|
||||
return content
|
||||
|
|
|
@ -7,7 +7,7 @@ from bedrock.base.urlresolvers import reverse
|
|||
from bedrock.mozorg.util import page
|
||||
|
||||
|
||||
class PageNode(object):
|
||||
class PageNode:
|
||||
"""
|
||||
A utility for representing a hierarchical page structure.
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
|
|
@ -13,7 +13,14 @@ from django.utils.cache import add_never_cache_headers
|
|||
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):
|
||||
cache = (request.method != 'POST' and
|
||||
|
@ -41,8 +48,16 @@ class MozorgRequestTimingMiddleware(GraphiteRequestTimingMiddleware):
|
|||
f.process_view(request, view, view_args, view_kwargs)
|
||||
|
||||
|
||||
class ClacksOverheadMiddleware(object):
|
||||
class ClacksOverheadMiddleware:
|
||||
# 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
|
||||
def process_response(request, response):
|
||||
if response.status_code == 200:
|
||||
|
@ -50,23 +65,34 @@ class ClacksOverheadMiddleware(object):
|
|||
return response
|
||||
|
||||
|
||||
class HostnameMiddleware(object):
|
||||
def __init__(self):
|
||||
class HostnameMiddleware:
|
||||
def __init__(self, get_response=None):
|
||||
if not settings.ENABLE_HOSTNAME_MIDDLEWARE:
|
||||
raise MiddlewareNotUsed
|
||||
|
||||
values = [getattr(settings, x) for x in ['HOSTNAME', 'CLUSTER_NAME']]
|
||||
self.backend_server = '.'.join(x for x in values if x)
|
||||
|
||||
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):
|
||||
response['X-Backend-Server'] = self.backend_server
|
||||
return response
|
||||
|
||||
|
||||
class VaryNoCacheMiddleware(object):
|
||||
def __init__(self):
|
||||
class VaryNoCacheMiddleware:
|
||||
def __init__(self, get_response=None):
|
||||
if not settings.ENABLE_VARY_NOCACHE_MIDDLEWARE:
|
||||
raise MiddlewareNotUsed
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
return self.process_response(request, response)
|
||||
|
||||
@staticmethod
|
||||
def process_response(request, response):
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
import django_extensions.db.fields
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class TwitterCache(models.Model):
|
|||
updated = ModificationDateTimeField()
|
||||
objects = TwitterCacheManager()
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return u'Tweets from @' + self.account
|
||||
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ redirectpatterns = (
|
|||
'https://wiki.mozilla.org/Websites/Directory', locale_prefix=False),
|
||||
|
||||
# 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),
|
||||
|
||||
# bug 856075
|
||||
|
@ -99,14 +99,14 @@ redirectpatterns = (
|
|||
redirect(r'^firefox/backtoschool/firstrun/?$', 'firefox.firstrun'),
|
||||
|
||||
# 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(?P<path>.*)', 'http://www-archive.mozilla.org/ports{path}'),
|
||||
|
||||
redirect(r'^b2g', 'https://support.mozilla.org/products/firefox-os'),
|
||||
|
||||
# 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/'),
|
||||
|
||||
# Bug 1144949
|
||||
|
@ -132,7 +132,7 @@ redirectpatterns = (
|
|||
'https://marketplace.firefox.com/developers/'),
|
||||
|
||||
# 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 1238687 /privacy/tips -> teach/smarton/
|
||||
|
@ -143,7 +143,7 @@ redirectpatterns = (
|
|||
'mozorg.internet-health.privacy-security'),
|
||||
|
||||
# Bug 821047 /about/mission.html -> /mission/
|
||||
redirect(r'^about/mission.html$', '/mission/'),
|
||||
redirect(r'^about/mission\.html$', '/mission/'),
|
||||
|
||||
# Bug 784411 /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'),
|
||||
|
||||
# bug 1205632
|
||||
redirect(r'^js/language(?:/|/index.html)?$',
|
||||
redirect(r'^js/language(?:/|/index\.html)?$',
|
||||
'https://developer.mozilla.org/docs/Web/JavaScript/Language_Resources',
|
||||
locale_prefix=False),
|
||||
redirect(r'^js/language/js20(/.*)?$', 'http://www.ecmascript-lang.org',
|
||||
|
@ -238,12 +238,12 @@ redirectpatterns = (
|
|||
# bug 876810
|
||||
redirect(r'^hacking/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/committer/committers-agreement\.(?P<ext>odt|pdf|txt)$',
|
||||
'https://static.mozilla.com/foundation/documents/'
|
||||
'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'),
|
||||
|
||||
# bug 1165344
|
||||
|
@ -275,33 +275,33 @@ redirectpatterns = (
|
|||
redirect(r'^opportunities(?:/|/index\.html)?$', 'https://careers.mozilla.org/'),
|
||||
|
||||
# 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/'),
|
||||
redirect(r'^projects/security/membership-policy.html$',
|
||||
redirect(r'^projects/security/membership-policy\.html$',
|
||||
'/about/governance/policies/security-group/membership/'),
|
||||
redirect(r'^projects/security/secgrouplist.html$',
|
||||
redirect(r'^projects/security/secgrouplist\.html$',
|
||||
'/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/'),
|
||||
|
||||
# bug 818316, 1128579
|
||||
redirect(r'^projects/security/certs(?:/(?:index.html)?)?$',
|
||||
redirect(r'^projects/security/certs(?:/(?:index\.html)?)?$',
|
||||
'/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'),
|
||||
redirect(r'^projects/security/certs/pending(?:/(?:index.html)?)?$',
|
||||
redirect(r'^projects/security/certs/pending(?:/(?:index\.html)?)?$',
|
||||
'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/'),
|
||||
redirect(r'^projects/security/certs/policy/EnforcementPolicy.html$',
|
||||
redirect(r'^projects/security/certs/policy/EnforcementPolicy\.html$',
|
||||
'/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/'),
|
||||
redirect(r'^projects/security/certs/policy/InclusionPolicy.html$',
|
||||
redirect(r'^projects/security/certs/policy/InclusionPolicy\.html$',
|
||||
'/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'),
|
||||
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'),
|
||||
|
||||
# bug 1068931
|
||||
|
@ -309,11 +309,11 @@ redirectpatterns = (
|
|||
|
||||
# bug 887426
|
||||
redirect(r'^about/policies/?$', '/about/governance/policies/'),
|
||||
redirect(r'^about/policies/participation.html$', '/about/governance/policies/participation/'),
|
||||
redirect(r'^about/policies/policies.html$', '/about/governance/policies/'),
|
||||
redirect(r'^about/policies/participation\.html$', '/about/governance/policies/participation/'),
|
||||
redirect(r'^about/policies/policies\.html$', '/about/governance/policies/'),
|
||||
|
||||
# bug 882923
|
||||
redirect(r'^opt-out.html$', '/privacy/websites/#user-choices'),
|
||||
redirect(r'^opt-out\.html$', '/privacy/websites/#user-choices'),
|
||||
|
||||
# bug 878039
|
||||
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'),
|
||||
redirect(r'^access/windows/zoomtext\.html$',
|
||||
'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
|
||||
redirect(r'^access/(?P<page>.+)$',
|
||||
|
@ -385,7 +385,7 @@ redirectpatterns = (
|
|||
redirect(r'^MPL/boilerplate-1\.1/(.*)$',
|
||||
'http://website-archive.mozilla.org/www.mozilla.org/mpl/MPL/boilerplate-1.1/{}',
|
||||
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',
|
||||
locale_prefix=False),
|
||||
|
||||
|
@ -401,9 +401,9 @@ redirectpatterns = (
|
|||
# bug 724682
|
||||
redirect(r'^projects/mathml/demo/texvsmml\.html$',
|
||||
'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'),
|
||||
redirect(r'^projects/mathml/screenshots(?:/(?:index.html)?)?$',
|
||||
redirect(r'^projects/mathml/screenshots(?:/(?:index\.html)?)?$',
|
||||
'https://developer.mozilla.org/Mozilla_MathML_Project/Screenshots'),
|
||||
redirect(r'^projects/mathml/authoring\.html$',
|
||||
'https://developer.mozilla.org/en/Mozilla_MathML_Project/Authoring'),
|
||||
|
@ -458,7 +458,7 @@ redirectpatterns = (
|
|||
'https://developer.mozilla.org/docs/Mozilla/Projects/Rhino/Download_Rhino'),
|
||||
redirect(r'^rhino/doc\.html$',
|
||||
'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'),
|
||||
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
|
||||
# probably many links to them from other pages and sites that need to keep
|
||||
# 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'),
|
||||
redirect(r'^foundation/donate_form.pdf$',
|
||||
redirect(r'^foundation/donate_form\.pdf$',
|
||||
'https://static.mozilla.com/foundation/documents/donate_form.pdf', re_flags='i'),
|
||||
|
||||
# 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/
|
||||
# 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'),
|
||||
# Redirect foundation/anything/foo.html to foundation/anything/foo/,
|
||||
# with a redirect for the nice search engines
|
||||
redirect(r'^foundation/documents/(?P<page>index|mozilla-200.-financial-faq)\.html$',
|
||||
'/foundation/{page}/', re_flags='i'),
|
||||
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
|
||||
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/guide/?$', 'mozorg.about.policy.patents.guide'),
|
||||
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'),
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
|
||||
<!--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">
|
||||
<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>
|
||||
|
||||
{% for letter, names in credits.ordered.iteritems() %}
|
||||
{% for letter, names in credits.ordered.items() %}
|
||||
<h2><a name="{{ letter }}">{{ letter }}</a></h2>
|
||||
<p>
|
||||
{{ ',\n'.join(names) }}
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
# flake8: noqa
|
||||
import misc
|
||||
import social_widgets
|
||||
from bedrock.mozorg.templatetags import misc, social_widgets
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
# coding: utf-8
|
||||
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
import random
|
||||
import re
|
||||
from os import path
|
||||
from os.path import splitext
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
import urllib.parse
|
||||
|
||||
from django.conf import settings
|
||||
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_attrs = {}
|
||||
for platform, image in img_urls.iteritems():
|
||||
for platform, image in img_urls.items():
|
||||
if is_l10n:
|
||||
image = l10n_img_file_name(ctx, image)
|
||||
else:
|
||||
|
@ -188,7 +183,7 @@ def platform_img(ctx, url, optional_attributes=None):
|
|||
|
||||
img_attrs.update(optional_attributes)
|
||||
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
|
||||
# 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:
|
||||
continue
|
||||
videos[ext] = (v if 'prefix' not in kwargs else
|
||||
urlparse.urljoin(kwargs['prefix'], v))
|
||||
urllib.parse.urljoin(kwargs['prefix'], v))
|
||||
|
||||
if not videos:
|
||||
return ''
|
||||
|
@ -397,7 +392,8 @@ def donate_url(ctx, source=''):
|
|||
donate_url_params = settings.DONATE_PARAMS.get(
|
||||
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,
|
||||
currency=donate_url_params['currency'])
|
||||
|
||||
|
@ -530,7 +526,7 @@ def htmlattr(_list, **kwargs):
|
|||
|
||||
"""
|
||||
for tag in _list:
|
||||
for attr, value in kwargs.iteritems():
|
||||
for attr, value in kwargs.items():
|
||||
tag[attr] = value
|
||||
|
||||
return _list
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
# 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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import datetime
|
||||
import urllib
|
||||
import urllib.parse
|
||||
|
||||
from django_jinja import library
|
||||
|
||||
|
@ -29,7 +27,7 @@ def format_tweet_body(tweet):
|
|||
hash = hashtags['text']
|
||||
text = text.replace('#' + 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)))
|
||||
|
||||
# Mentions (@someone)
|
||||
|
@ -37,7 +35,7 @@ def format_tweet_body(tweet):
|
|||
name = user['screen_name']
|
||||
text = text.replace('@' + name,
|
||||
('<a href="https://twitter.com/%s" class="mention">@%s</a>'
|
||||
% (urllib.quote(name.encode('utf8')), name)))
|
||||
% (urllib.parse.quote(name.encode('utf8')), name)))
|
||||
|
||||
# URLs
|
||||
for url in entities['urls']:
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
# 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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
from textwrap import dedent
|
||||
|
||||
|
@ -24,7 +22,7 @@ class TestCredits(TestCase):
|
|||
Walter Sobchak,Sobchak
|
||||
Theodore Donald Kerabatsos,Kerabatsos
|
||||
Tantek Çelik,Çelik
|
||||
""".encode('utf8')))
|
||||
"""))
|
||||
self.assertListEqual(self.credits_file.rows, [
|
||||
['Tantek Çelik', 'CELIK'],
|
||||
['The Dude', 'DUDE'],
|
||||
|
@ -63,7 +61,7 @@ class TestCredits(TestCase):
|
|||
Walter Sobchak,Sobchak
|
||||
Theodore Donald Kerabatsos,Kerabatsos
|
||||
Tantek Çelik,Çelik
|
||||
""".encode('utf8')))
|
||||
"""))
|
||||
good_names = OrderedDict()
|
||||
good_names['C'] = ['Tantek Çelik']
|
||||
good_names['D'] = ['The Dude']
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.conf import settings
|
|||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
|
||||
import pytest
|
||||
from django_jinja.backend import Jinja2
|
||||
from jinja2 import Markup
|
||||
from mock import patch
|
||||
|
@ -205,20 +206,18 @@ class TestVideoTag(TestCase):
|
|||
assert doc('video source').length == 3
|
||||
|
||||
# Extensions in the right order?
|
||||
for i, ext in enumerate(('webm', 'ogv', 'mp4')):
|
||||
assert doc('video source:eq(%s)' % i).attr('src').endswith(ext)
|
||||
extensions = [os.path.splitext(el.attrib['src'])[1] for el in doc('video source')]
|
||||
assert extensions == ['.webm', '.ogv', '.mp4']
|
||||
|
||||
def test_prefix(self):
|
||||
# Prefix should be applied to all videos.
|
||||
doc = pq(self._render("{{ video('meh.mp4', 'meh.ogv', "
|
||||
"prefix='http://example.com/blah/') }}"))
|
||||
expected = ('http://example.com/blah/meh.ogv',
|
||||
'http://example.com/blah/meh.mp4')
|
||||
|
||||
assert doc('video source').length == 2
|
||||
|
||||
for i in xrange(2):
|
||||
assert doc('video source:eq(%s)' % i).attr('src') == expected[i]
|
||||
doc = pq(self._render(
|
||||
"{{ video('meh.mp4', 'meh.ogv', prefix='http://example.com/blah/') }}")
|
||||
)
|
||||
assert [el.attrib['src'] for el in doc('video source')] == [
|
||||
'http://example.com/blah/meh.ogv',
|
||||
'http://example.com/blah/meh.mp4',
|
||||
]
|
||||
|
||||
def test_fileformats(self):
|
||||
# URLs ending in strange extensions are ignored.
|
||||
|
@ -229,8 +228,8 @@ class TestVideoTag(TestCase):
|
|||
|
||||
assert doc('video source').length == 2
|
||||
|
||||
for i, ext in enumerate(('webm', 'ogv')):
|
||||
assert doc('video source:eq(%s)' % i).attr('src').endswith(ext)
|
||||
extensions = [os.path.splitext(el.attrib['src'])[1] for el in doc('video source')]
|
||||
assert extensions == ['.webm', '.ogv']
|
||||
|
||||
|
||||
@override_settings(STATIC_URL='/media/')
|
||||
|
@ -344,8 +343,10 @@ class TestPressBlogUrl(TestCase):
|
|||
assert self._render('oc') == 'https://blog.mozilla.org/press/'
|
||||
|
||||
|
||||
@override_settings(DONATE_LINK=TEST_DONATE_LINK,
|
||||
DONATE_PARAMS=TEST_DONATE_PARAMS)
|
||||
@override_settings(
|
||||
DONATE_LINK=TEST_DONATE_LINK,
|
||||
DONATE_PARAMS=TEST_DONATE_PARAMS,
|
||||
)
|
||||
class TestDonateUrl(TestCase):
|
||||
rf = RequestFactory()
|
||||
|
||||
|
@ -639,22 +640,22 @@ def test_f_unicode():
|
|||
assert s == u'\xe9 baz'
|
||||
|
||||
|
||||
def test_f_markup():
|
||||
format_string = 'Hello <b>{0}</b>'
|
||||
val_string = '<em>Steve</em>'
|
||||
expect = 'Hello <b><em>Steve</em></b>'
|
||||
|
||||
def markup_render(f, v):
|
||||
return render('{{ fmt|f(val) }}', {'fmt': f, 'val': v})
|
||||
|
||||
assert markup_render(format_string, val_string) == expect
|
||||
|
||||
format_markup = Markup(format_string)
|
||||
val_string = '<em>Steve</em>'
|
||||
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
|
||||
|
||||
@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>'
|
||||
s = render('{{ fmt|f(val) }}', {'fmt': f, 'val': v})
|
||||
assert expect == s
|
||||
|
||||
|
||||
def test_datetime():
|
||||
|
@ -687,7 +688,7 @@ def test_ifeq():
|
|||
|
||||
def test_csrf():
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -7,13 +7,12 @@
|
|||
import json
|
||||
import os.path
|
||||
|
||||
import tweepy
|
||||
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.templatetags.social_widgets import * # noqa
|
||||
|
||||
|
||||
TEST_FILES_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
'test_files')
|
||||
|
|
|
@ -217,9 +217,4 @@ class TestPageRoot(TestCase):
|
|||
# Mocking properties
|
||||
page.__get__ = lambda mock, self, cls: self.display_name
|
||||
|
||||
args = root.as_urlpatterns()
|
||||
|
||||
assert 'child1' in args
|
||||
assert 'child2' in args
|
||||
assert 'root' in args
|
||||
assert 'parent' not in args
|
||||
assert root.as_urlpatterns() == ['root', 'child1', 'child2']
|
||||
|
|
|
@ -41,9 +41,11 @@ class TestHostnameMiddleware(TestCase):
|
|||
self.middleware.process_response(self.request, self.response)
|
||||
self.assertEqual(self.response['X-Backend-Server'], 'foobar.oregon-b')
|
||||
|
||||
@override_settings(MIDDLEWARE_CLASSES=(list(settings.MIDDLEWARE_CLASSES) +
|
||||
['bedrock.mozorg.middleware.HostnameMiddleware']),
|
||||
HOSTNAME='foobar', CLUSTER_NAME='el-dudarino')
|
||||
@override_settings(
|
||||
MIDDLEWARE=(list(settings.MIDDLEWARE) + ['bedrock.mozorg.middleware.HostnameMiddleware']),
|
||||
HOSTNAME='foobar',
|
||||
CLUSTER_NAME='el-dudarino',
|
||||
)
|
||||
def test_request(self):
|
||||
response = self.client.get('/en-US/')
|
||||
self.assertEqual(response['X-Backend-Server'], 'foobar.el-dudarino')
|
||||
|
|
|
@ -25,18 +25,18 @@ class TestViews(TestCase):
|
|||
"""The download button should have the funnelcake ID."""
|
||||
with self.activate('en-US'):
|
||||
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):
|
||||
"""The download button should not have a bad funnelcake ID."""
|
||||
with self.activate('en-US'):
|
||||
resp = self.client.get(reverse('mozorg.home'), {'f': '5dude'})
|
||||
assert 'product=firefox-stub&' in resp.content
|
||||
assert 'product=firefox-stub-f5dude&' not in resp.content
|
||||
assert b'product=firefox-stub&' in resp.content
|
||||
assert b'product=firefox-stub-f5dude&' not in resp.content
|
||||
|
||||
resp = self.client.get(reverse('mozorg.home'), {'f': '999999999'})
|
||||
assert 'product=firefox-stub&' in resp.content
|
||||
assert 'product=firefox-stub-f999999999&' not in resp.content
|
||||
assert b'product=firefox-stub&' in resp.content
|
||||
assert b'product=firefox-stub-f999999999&' not in resp.content
|
||||
|
||||
|
||||
class TestRobots(TestCase):
|
||||
|
|
|
@ -12,9 +12,10 @@ from bedrock.mozorg.util import page
|
|||
def mock_view(request):
|
||||
return HttpResponse('test')
|
||||
|
||||
urlpatterns = (
|
||||
|
||||
urlpatterns = [
|
||||
url(r'', include('%s.urls' % settings.PROJECT_MODULE)),
|
||||
|
||||
# Used by test_helper
|
||||
page('base', 'base-resp.html'),
|
||||
)
|
||||
]
|
||||
|
|
|
@ -296,7 +296,7 @@ urlpatterns = (
|
|||
url(r'^oauth/fxa/error/$', views.oauth_fxa_error, name='mozorg.oauth.fxa-error'),
|
||||
|
||||
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'),
|
||||
page('technology/what-is-a-browser', 'mozorg/what-is-a-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
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render as django_render
|
||||
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):
|
||||
"""
|
||||
Define a bedrock page.
|
||||
|
@ -200,7 +188,7 @@ def get_fxa_oauth_token(code):
|
|||
try:
|
||||
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']
|
||||
except:
|
||||
except Exception:
|
||||
token = None
|
||||
|
||||
return token
|
||||
|
@ -212,7 +200,7 @@ def get_fxa_profile_email(token):
|
|||
|
||||
try:
|
||||
email = profileClient.get_email(token)
|
||||
except:
|
||||
except Exception:
|
||||
email = None
|
||||
|
||||
return email
|
||||
|
@ -229,5 +217,5 @@ def fxa_concert_rsvp(email, isFx):
|
|||
try:
|
||||
basket.request('post', 'fxa-concerts-rsvp', data=data)
|
||||
return True
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
|
|
|
@ -4,15 +4,16 @@
|
|||
|
||||
import re
|
||||
|
||||
from commonware.decorators import xframe_allow
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.http import Http404, HttpResponseRedirect, JsonResponse
|
||||
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.http import require_safe
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
from commonware.decorators import xframe_allow
|
||||
from lib import l10n_utils
|
||||
from lib.l10n_utils.dotlang import lang_file_is_active
|
||||
|
||||
from bedrock.base.waffle import switch
|
||||
from bedrock.contentcards.models import get_page_content_cards
|
||||
|
@ -22,13 +23,10 @@ from bedrock.mozorg.models import ContributorActivity
|
|||
from bedrock.mozorg.util import (
|
||||
fxa_concert_rsvp,
|
||||
get_fxa_oauth_token,
|
||||
get_fxa_profile_email,
|
||||
HttpResponseJSON
|
||||
get_fxa_profile_email
|
||||
)
|
||||
from bedrock.pocketfeed.models import PocketArticle
|
||||
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')
|
||||
forums_file = ForumsFile('forums')
|
||||
|
@ -60,7 +58,9 @@ def mozid_data_view(request, source_name):
|
|||
'totalactive': activity['total__sum'],
|
||||
'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
|
||||
|
|
|
@ -48,7 +48,7 @@ def get_lang_choices(newsletters=None):
|
|||
lang_name = product_details.languages[lang]['native']
|
||||
else:
|
||||
try:
|
||||
locale = [loc for loc in product_details.languages.keys()
|
||||
locale = [loc for loc in product_details.languages
|
||||
if loc.startswith(lang)][0]
|
||||
except IndexError:
|
||||
continue
|
||||
|
@ -120,7 +120,7 @@ class CountrySelectForm(forms.Form):
|
|||
|
||||
def __init__(self, locale, *args, **kwargs):
|
||||
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)
|
||||
self.fields['country'].choices = regions
|
||||
|
||||
|
@ -149,7 +149,7 @@ class ManageSubscriptionsForm(forms.Form):
|
|||
|
||||
def __init__(self, locale, *args, **kwargs):
|
||||
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()
|
||||
languages = [x[0] for x in lang_choices]
|
||||
|
||||
|
@ -249,7 +249,7 @@ class NewsletterFooterForm(forms.Form):
|
|||
# out which languages to list in the form.
|
||||
def __init__(self, newsletters, locale, data=None, *args, **kwargs):
|
||||
regions = product_details.get_regions(locale)
|
||||
regions = sorted(regions.iteritems(), key=itemgetter(1))
|
||||
regions = sorted(iter(regions.items()), key=itemgetter(1))
|
||||
|
||||
try:
|
||||
newsletters = validate_newsletters(newsletters)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
import basket
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django_extensions.db.fields.json
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class NewsletterManager(models.Manager):
|
|||
|
||||
self.all().delete()
|
||||
count = 0
|
||||
for slug, data in new_data.iteritems():
|
||||
for slug, data in new_data.items():
|
||||
self.create(
|
||||
slug=slug,
|
||||
data=data,
|
||||
|
@ -40,5 +40,5 @@ class Newsletter(models.Model):
|
|||
|
||||
objects = NewsletterManager()
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.slug
|
||||
|
|
|
@ -3,8 +3,8 @@ from bedrock.redirects.util import redirect
|
|||
|
||||
redirectpatterns = (
|
||||
# bug 926629
|
||||
redirect(r'^newsletter/about_mobile(?:/(?:index.html)?)?$', 'newsletter.subscribe'),
|
||||
redirect(r'^newsletter/about_mozilla(?:/(?:index.html)?)?$', 'mozorg.contribute.index'),
|
||||
redirect(r'^newsletter/new(?:/(?:index.html)?)?$', 'newsletter.subscribe'),
|
||||
redirect(r'^newsletter/ios(?:/(?:index.html)?)?$', 'firefox.mobile'),
|
||||
redirect(r'^newsletter/about_mobile(?:/(?:index\.html)?)?$', 'newsletter.subscribe'),
|
||||
redirect(r'^newsletter/about_mozilla(?:/(?:index\.html)?)?$', 'mozorg.contribute.index'),
|
||||
redirect(r'^newsletter/new(?:/(?:index\.html)?)?$', 'newsletter.subscribe'),
|
||||
redirect(r'^newsletter/ios(?:/(?:index\.html)?)?$', 'firefox.mobile'),
|
||||
)
|
||||
|
|
|
@ -30,7 +30,7 @@ class TestViews(TestCase):
|
|||
|
||||
@patch('bedrock.newsletter.views.l10n_utils.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})
|
||||
updated(req)
|
||||
self.assertEqual(mock_render.call_args[0][2]['token'], token)
|
||||
|
@ -53,7 +53,7 @@ class TestViews(TestCase):
|
|||
@patch('basket.base.request')
|
||||
class TestExistingNewsletterView(TestCase):
|
||||
def setUp(self):
|
||||
self.token = unicode(uuid.uuid4())
|
||||
self.token = str(uuid.uuid4())
|
||||
self.user = {
|
||||
'newsletters': [u'mozilla-and-you'],
|
||||
'token': self.token,
|
||||
|
@ -129,7 +129,7 @@ class TestExistingNewsletterView(TestCase):
|
|||
# or they are marked 'show' and 'active' in the settings
|
||||
get_newsletters.return_value = newsletters
|
||||
# Find a newsletter without 'show' and subscribe the user to it
|
||||
for newsletter, data in newsletters.iteritems():
|
||||
for newsletter, data in newsletters.items():
|
||||
if not data.get('show', False):
|
||||
self.user['newsletters'] = [newsletter]
|
||||
break
|
||||
|
@ -148,10 +148,10 @@ class TestExistingNewsletterView(TestCase):
|
|||
|
||||
shown = set([form.initial['newsletter'] for form in forms])
|
||||
inactive = set([newsletter for newsletter, data
|
||||
in newsletters.iteritems()
|
||||
in newsletters.items()
|
||||
if not data.get('active', False)])
|
||||
to_show = set([newsletter for newsletter, data
|
||||
in newsletters.iteritems()
|
||||
in newsletters.items()
|
||||
if data.get('show', False)]) - inactive
|
||||
subscribed = set(self.user['newsletters'])
|
||||
|
||||
|
@ -171,7 +171,7 @@ class TestExistingNewsletterView(TestCase):
|
|||
|
||||
def test_get_user_not_found(self, mock_basket_request):
|
||||
# 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,))
|
||||
with patch.multiple('basket',
|
||||
request=DEFAULT) as basket_patches:
|
||||
|
@ -203,7 +203,7 @@ class TestExistingNewsletterView(TestCase):
|
|||
def test_post_user_not_found(self, mock_basket_request):
|
||||
# User submits form and passed token, but no user was found
|
||||
# Should issue message and redirect to recovery
|
||||
rand_token = unicode(uuid.uuid4())
|
||||
rand_token = str(uuid.uuid4())
|
||||
url = reverse('newsletter.existing.token', args=(rand_token,))
|
||||
with patch.multiple('basket',
|
||||
update_user=DEFAULT,
|
||||
|
@ -250,10 +250,9 @@ class TestExistingNewsletterView(TestCase):
|
|||
# Should have called update_user with subscription list
|
||||
self.assertEqual(1, basket_patches['update_user'].call_count)
|
||||
kwargs = basket_patches['update_user'].call_args[1]
|
||||
self.assertEqual(
|
||||
{'newsletters': u'mozilla-and-you,firefox-tips', 'lang': u'en'},
|
||||
kwargs
|
||||
)
|
||||
self.assertEqual(set(kwargs), set(['newsletters', 'lang']))
|
||||
self.assertEqual(kwargs['lang'], 'en')
|
||||
self.assertEqual(set(kwargs['newsletters'].split(',')), set(['mozilla-and-you', 'firefox-tips']))
|
||||
# Should not have called unsubscribe
|
||||
self.assertEqual(0, basket_patches['unsubscribe'].call_count)
|
||||
# Should not have called subscribe
|
||||
|
@ -266,7 +265,7 @@ class TestExistingNewsletterView(TestCase):
|
|||
def test_unsubscribing(self, get_newsletters, mock_basket_request):
|
||||
get_newsletters.return_value = newsletters
|
||||
# They unsubscribe from the one newsletter they're subscribed to
|
||||
self.data['form-0-subscribed_radio'] = u'false'
|
||||
self.data['form-0-subscribed_radio'] = u'False'
|
||||
url = reverse('newsletter.existing.token', args=(self.token,))
|
||||
with patch.multiple('basket',
|
||||
update_user=DEFAULT,
|
||||
|
@ -413,7 +412,7 @@ class TestExistingNewsletterView(TestCase):
|
|||
|
||||
class TestConfirmView(TestCase):
|
||||
def setUp(self):
|
||||
self.token = unicode(uuid.uuid4())
|
||||
self.token = str(uuid.uuid4())
|
||||
self.url = reverse('newsletter.confirm', kwargs={'token': self.token})
|
||||
|
||||
def test_normal(self):
|
||||
|
@ -469,7 +468,7 @@ class TestConfirmView(TestCase):
|
|||
|
||||
class TestSetCountryView(TestCase):
|
||||
def setUp(self):
|
||||
self.token = unicode(uuid.uuid4())
|
||||
self.token = str(uuid.uuid4())
|
||||
self.url = reverse('newsletter.country', kwargs={'token': self.token})
|
||||
|
||||
def test_normal_submit(self):
|
||||
|
@ -509,8 +508,8 @@ class TestRecoveryView(TestCase):
|
|||
def test_unknown_email(self, mock_basket):
|
||||
"""Unknown email addresses give helpful error message"""
|
||||
data = {'email': 'unknown@example.com'}
|
||||
mock_basket.side_effect = basket.BasketException(status_code=404,
|
||||
code=basket.errors.BASKET_UNKNOWN_EMAIL)
|
||||
mock_basket.side_effect = basket.BasketException(
|
||||
status_code=404, code=basket.errors.BASKET_UNKNOWN_EMAIL)
|
||||
rsp = self.client.post(self.url, data)
|
||||
self.assertTrue(mock_basket.called)
|
||||
self.assertEqual(200, rsp.status_code)
|
||||
|
@ -652,7 +651,7 @@ class TestNewsletterSubscribe(TestCase):
|
|||
resp = self.ajax_request(data)
|
||||
resp_data = json.loads(resp.content)
|
||||
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')
|
||||
def test_returns_ajax_basket_error(self, subscribe_mock):
|
||||
|
@ -668,7 +667,7 @@ class TestNewsletterSubscribe(TestCase):
|
|||
resp = self.ajax_request(data)
|
||||
resp_data = json.loads(resp.content)
|
||||
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):
|
||||
"""A normal GET should show the form."""
|
||||
|
|
|
@ -19,9 +19,9 @@ def get_languages_for_newsletters(newsletters=None):
|
|||
"""
|
||||
all_newsletters = get_newsletters()
|
||||
if newsletters is None:
|
||||
newsletters = all_newsletters.values()
|
||||
newsletters = list(all_newsletters.values())
|
||||
else:
|
||||
if isinstance(newsletters, basestring):
|
||||
if isinstance(newsletters, str):
|
||||
newsletters = [nl.strip() for nl in newsletters.split(',')]
|
||||
newsletters = [all_newsletters.get(nl, {}) for nl in newsletters]
|
||||
|
||||
|
|
|
@ -8,34 +8,30 @@ from cgi import escape
|
|||
from collections import defaultdict
|
||||
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.errors
|
||||
import commonware.log
|
||||
from jinja2 import Markup
|
||||
|
||||
import lib.l10n_utils as l10n_utils
|
||||
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 lib.l10n_utils.dotlang import _, _lazy
|
||||
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
|
||||
# utils.get_newsletters in our tests
|
||||
from bedrock.base.views import get_geo_from_request
|
||||
from bedrock.mozorg.util import HttpResponseJSON
|
||||
from bedrock.newsletter import utils
|
||||
|
||||
from .forms import (CountrySelectForm, EmailForm, ManageSubscriptionsForm,
|
||||
NewsletterFooterForm, NewsletterForm)
|
||||
|
||||
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
|
||||
# as already subscribed.
|
||||
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
|
||||
# ['show'] == True or the user is already subscribed
|
||||
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
|
||||
# that language code.
|
||||
newsletter_languages = defaultdict(list)
|
||||
for newsletter, data in newsletter_data.iteritems():
|
||||
for newsletter, data in newsletter_data.items():
|
||||
for lang in data['languages']:
|
||||
newsletter_languages[lang].append(newsletter)
|
||||
newsletter_languages = mark_safe(json.dumps(newsletter_languages))
|
||||
|
@ -567,7 +563,7 @@ def updated(request):
|
|||
# so we can read them. (Well, except for the free-form reason.)
|
||||
for i, reason in enumerate(REASONS):
|
||||
if _post_or_get(request, 'reason%d' % i):
|
||||
reasons.append(unicode(reason))
|
||||
reasons.append(str(reason))
|
||||
if _post_or_get(request, 'reason-text-p'):
|
||||
reasons.append(_post_or_get(request, 'reason-text', ''))
|
||||
|
||||
|
@ -663,11 +659,11 @@ def newsletter_subscribe(request):
|
|||
**kwargs)
|
||||
except basket.BasketException as e:
|
||||
if e.code == basket.errors.BASKET_INVALID_EMAIL:
|
||||
errors.append(unicode(invalid_email_address))
|
||||
errors.append(str(invalid_email_address))
|
||||
else:
|
||||
log.exception("Error subscribing %s to newsletter %s" %
|
||||
(data['email'], data['newsletters']))
|
||||
errors.append(unicode(general_error))
|
||||
errors.append(str(general_error))
|
||||
|
||||
else:
|
||||
if 'email' in form.errors:
|
||||
|
@ -679,7 +675,7 @@ def newsletter_subscribe(request):
|
|||
errors.extend(form.errors[fieldname])
|
||||
|
||||
# form error messages may contain unsanitized user input
|
||||
errors = map(escape, errors)
|
||||
errors = list(map(escape, errors))
|
||||
|
||||
if request.is_ajax():
|
||||
# return JSON
|
||||
|
@ -691,7 +687,7 @@ def newsletter_subscribe(request):
|
|||
else:
|
||||
resp = {'success': True}
|
||||
|
||||
return HttpResponseJSON(resp)
|
||||
return JsonResponse(resp)
|
||||
else:
|
||||
ctx = {'newsletter_form': form}
|
||||
if not errors:
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import requests
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.db.utils import DatabaseError
|
||||
|
||||
|
@ -62,7 +60,7 @@ class PocketArticleManager(models.Manager):
|
|||
try:
|
||||
if obj:
|
||||
if obj.time_shared != article['time_shared']:
|
||||
for key, value in article.iteritems():
|
||||
for key, value in article.items():
|
||||
setattr(obj, key, value)
|
||||
obj.save()
|
||||
update_count += 1
|
||||
|
@ -97,7 +95,7 @@ class PocketArticle(models.Model):
|
|||
get_latest_by = 'time_shared'
|
||||
ordering = ['-time_shared']
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
|
|
|
@ -20,9 +20,8 @@ def test_get_articles_data(req_mock):
|
|||
|
||||
api.get_articles_data(count=7)
|
||||
|
||||
req_mock.post.assert_called_once_with('test_url',
|
||||
json=expected_payload,
|
||||
timeout=5)
|
||||
req_mock.post.assert_called_once_with(
|
||||
'test_url', json=expected_payload, timeout=5)
|
||||
|
||||
|
||||
@patch.object(api, 'requests')
|
||||
|
|
|
@ -174,7 +174,7 @@ redirectpatterns = (
|
|||
redirect(r'^press/mozilla-foundation\.html$',
|
||||
'https://blog.mozilla.org/press/2003/07/mozilla-org-announces-launch-of-the-'
|
||||
'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/'),
|
||||
redirect(r'^press/open-source-security\.html$',
|
||||
'https://blog.mozilla.org/press/2000/01/open-source-development-of-security-products-'
|
||||
|
|
|
@ -66,7 +66,7 @@ class TestPressInquiry(TestCase):
|
|||
response = self.view(request)
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -84,7 +84,7 @@ class TestPressInquiry(TestCase):
|
|||
response = self.view(request)
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -199,7 +199,7 @@ class TestSpeakerRequest(TestCase):
|
|||
response = self.view(request)
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -217,7 +217,7 @@ class TestSpeakerRequest(TestCase):
|
|||
response = self.view(request)
|
||||
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
from django.core.urlresolvers import Resolver404
|
||||
from django.urls import Resolver404
|
||||
|
||||
from .util import get_resolver
|
||||
|
||||
|
||||
class RedirectsMiddleware(object):
|
||||
def __init__(self, resolver=None):
|
||||
class RedirectsMiddleware:
|
||||
def __init__(self, get_response=None, resolver=None):
|
||||
self.get_response = get_response
|
||||
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):
|
||||
try:
|
||||
resolver_match = self.resolver.resolve(request.path_info)
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -13,7 +13,7 @@ patterns = [
|
|||
redirect(r'^dude/already/10th/', '/far/out/'),
|
||||
redirect(r'^walter/prior/restraint/', '/finishes/coffee/'),
|
||||
]
|
||||
middleware = RedirectsMiddleware(get_resolver(patterns))
|
||||
middleware = RedirectsMiddleware(resolver=get_resolver(patterns))
|
||||
|
||||
|
||||
class TestRedirectsMiddleware(TestCase):
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# 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
|
||||
# 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.client import RequestFactory
|
||||
|
||||
|
@ -88,12 +88,12 @@ class TestNoRedirectUrlPattern(TestCase):
|
|||
no_redirect(r'^iam/the/walrus/$'),
|
||||
redirect(r'^iam/the/.*/$', '/coo/coo/cachoo/'),
|
||||
])
|
||||
middleware = RedirectsMiddleware(resolver)
|
||||
middleware = RedirectsMiddleware(resolver=resolver)
|
||||
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
||||
self.assertIsNone(resp)
|
||||
|
||||
# including locale
|
||||
middleware = RedirectsMiddleware(resolver)
|
||||
middleware = RedirectsMiddleware(resolver=resolver)
|
||||
resp = middleware.process_request(self.rf.get('/pt-BR/iam/the/walrus/'))
|
||||
self.assertIsNone(resp)
|
||||
|
||||
|
@ -109,7 +109,7 @@ class TestNoRedirectUrlPattern(TestCase):
|
|||
redirect(r'^iam/the/walrus/$', '/coo/coo/cachoo/'),
|
||||
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/'))
|
||||
self.assertIsNone(resp)
|
||||
|
||||
|
@ -129,10 +129,10 @@ class TestRedirectUrlPattern(TestCase):
|
|||
|
||||
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')
|
||||
assert isinstance(url_pattern, RegexURLPattern)
|
||||
assert isinstance(url_pattern, URLPattern)
|
||||
assert url_pattern.name == 'Lebowski'
|
||||
|
||||
def test_no_query(self):
|
||||
|
@ -292,7 +292,7 @@ class TestRedirectUrlPattern(TestCase):
|
|||
Should be able to capture info from URL and use in redirection.
|
||||
"""
|
||||
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/'))
|
||||
assert resp.status_code == 301
|
||||
assert resp['Location'] == '/donnie/the/walrus/'
|
||||
|
@ -303,7 +303,7 @@ class TestRedirectUrlPattern(TestCase):
|
|||
"""
|
||||
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('/pt-BR/iam/the/walrus/'))
|
||||
assert resp.status_code == 301
|
||||
assert resp['Location'] == '/pt-BR/donnie/the/walrus/'
|
||||
|
@ -314,7 +314,7 @@ class TestRedirectUrlPattern(TestCase):
|
|||
"""
|
||||
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/'))
|
||||
assert resp.status_code == 301
|
||||
assert resp['Location'] == '/donnie/the/walrus/'
|
||||
|
@ -325,7 +325,7 @@ class TestRedirectUrlPattern(TestCase):
|
|||
"""
|
||||
resolver = get_resolver([redirect(r'^iam/the/(?P<name>.+)/$',
|
||||
'/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/'))
|
||||
assert resp.status_code == 301
|
||||
assert resp['Location'] == '/donnie/the/walrus/'
|
||||
|
@ -340,7 +340,7 @@ class TestRedirectUrlPattern(TestCase):
|
|||
"""
|
||||
resolver = get_resolver([redirect(r'^iam/the/(.+)/$', '/donnie/the/{}/',
|
||||
locale_prefix=False)])
|
||||
middleware = RedirectsMiddleware(resolver)
|
||||
middleware = RedirectsMiddleware(resolver=resolver)
|
||||
resp = middleware.process_request(self.rf.get('/iam/the/walrus/'))
|
||||
assert resp.status_code == 301
|
||||
assert resp['Location'] == '/donnie/the/walrus/'
|
||||
|
@ -351,7 +351,7 @@ class TestRedirectUrlPattern(TestCase):
|
|||
"""
|
||||
resolver = get_resolver([redirect(r'^iam/the(/.+)?/$', '/donnie/the{}/',
|
||||
locale_prefix=False)])
|
||||
middleware = RedirectsMiddleware(resolver)
|
||||
middleware = RedirectsMiddleware(resolver=resolver)
|
||||
resp = middleware.process_request(self.rf.get('/iam/the/'))
|
||||
assert resp.status_code == 301
|
||||
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/$', '/dammit/donnie/', re_flags='i'),
|
||||
])
|
||||
middleware = RedirectsMiddleware(resolver)
|
||||
middleware = RedirectsMiddleware(resolver=resolver)
|
||||
resp = middleware.process_request(self.rf.get('/IAm/The/Walrus/'))
|
||||
assert resp.status_code == 301
|
||||
assert resp['Location'] == '/dammit/donnie/'
|
||||
|
@ -391,7 +391,7 @@ class TestRedirectUrlPattern(TestCase):
|
|||
"""
|
||||
resolver = get_resolver([redirect(r'^editor/(?P<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'
|
||||
'%3C/span%3E%3C/a%3E%C2%A0'))
|
||||
assert resp.status_code == 301
|
||||
|
|
|
@ -3,21 +3,20 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import re
|
||||
from urllib import urlencode
|
||||
from urlparse import parse_qs
|
||||
from urllib.parse import parse_qs, urlencode
|
||||
|
||||
from django.core.urlresolvers import NoReverseMatch, RegexURLResolver, reverse
|
||||
import commonware.log
|
||||
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.html import strip_tags
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
|
||||
import commonware.log
|
||||
|
||||
from bedrock.mozorg.decorators import cache_control_expires
|
||||
|
||||
|
||||
log = commonware.log.getLogger('redirects.util')
|
||||
LOCALE_RE = r'^(?P<locale>\w{2,3}(?:-\w{2})?/)?'
|
||||
HTTP_RE = re.compile(r'^https?://', re.IGNORECASE)
|
||||
|
@ -31,7 +30,8 @@ def register(patterns):
|
|||
|
||||
|
||||
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):
|
||||
|
@ -166,7 +166,7 @@ def redirect(pattern, to, permanent=True, locale_prefix=True, anchor=None, name=
|
|||
view_decorators.append(cache_control_expires(cache_timeout))
|
||||
|
||||
if vary:
|
||||
if isinstance(vary, basestring):
|
||||
if isinstance(vary, str):
|
||||
vary = [vary]
|
||||
view_decorators.append(vary_on_headers(*vary))
|
||||
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
# Adapted from django-mozilla-product-details
|
||||
version_re = (r"\d+" # major (x in x.y)
|
||||
"\.\d+" # minor1 (y in x.y)
|
||||
"\.?(?:\d+)?" # minor2 (z in x.y.z)
|
||||
"\.?(?:\d+)?" # minor3 (w in x.y.z.w)
|
||||
"(?:a|b(?:eta)?)?" # alpha/beta
|
||||
"(?:\d*)" # alpha/beta version
|
||||
"(?:pre)?" # pre release
|
||||
"(?:\d)?" # pre release version
|
||||
"(?:esr)?") # extended support release
|
||||
r"\.\d+" # minor1 (y in x.y)
|
||||
r"\.?(?:\d+)?" # minor2 (z in x.y.z)
|
||||
r"\.?(?:\d+)?" # minor3 (w in x.y.z.w)
|
||||
r"(?:a|b(?:eta)?)?" # alpha/beta
|
||||
r"(?:\d*)" # alpha/beta version
|
||||
r"(?:pre)?" # pre release
|
||||
r"(?:\d)?" # pre release version
|
||||
r"(?:esr)?") # extended support release
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import print_function
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import bedrock.releasenotes.models
|
||||
|
||||
|
|
|
@ -22,7 +22,11 @@ from bedrock.releasenotes.utils import memoize
|
|||
LONG_RN_CACHE_TIMEOUT = 7200 # 2 hours
|
||||
cache = caches['release-notes']
|
||||
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):
|
||||
for key, value in data.items():
|
||||
if not hasattr(self, key):
|
||||
|
@ -171,7 +175,7 @@ class ProductRelease(models.Model):
|
|||
class Meta:
|
||||
ordering = ['-release_date']
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@cached_property
|
||||
|
@ -180,7 +184,7 @@ class ProductRelease(models.Model):
|
|||
|
||||
@cached_property
|
||||
def major_version_int(self):
|
||||
return self.version_obj.major
|
||||
return self.version_obj.major or 0
|
||||
|
||||
@cached_property
|
||||
def version_obj(self):
|
||||
|
|
|
@ -19,7 +19,6 @@ from bedrock.releasenotes.models import ProductRelease
|
|||
|
||||
TESTS_PATH = Path(__file__).parent
|
||||
DATA_PATH = str(TESTS_PATH.joinpath('data'))
|
||||
firefox_desktop = FirefoxDesktop(json_dir=DATA_PATH)
|
||||
RELEASES_PATH = str(TESTS_PATH)
|
||||
|
||||
|
||||
|
@ -256,8 +255,9 @@ class TestReleaseNotesIndex(TestCase):
|
|||
self.pd_cache.clear()
|
||||
|
||||
@patch('bedrock.releasenotes.views.l10n_utils.render')
|
||||
@patch('bedrock.releasenotes.views.firefox_desktop', firefox_desktop)
|
||||
def test_relnotes_index_firefox(self, render_mock):
|
||||
firefox_desktop = FirefoxDesktop(json_dir=DATA_PATH)
|
||||
with patch('bedrock.releasenotes.views.firefox_desktop', firefox_desktop):
|
||||
render_mock().render.return_value = HttpResponse('')
|
||||
with self.activate('en-US'):
|
||||
self.client.get(reverse('firefox.releases.index'))
|
||||
|
|
|
@ -23,6 +23,7 @@ SUPPORT_URLS = {
|
|||
|
||||
def release_notes_template(channel, product, version=None):
|
||||
channel = channel or 'release'
|
||||
version = version or 0
|
||||
if product == 'Firefox' and channel == 'Aurora' and version >= 35:
|
||||
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
|
||||
# Firefox 59 wasn't ESR. Firefox 60 became the next ESR instead, and since
|
||||
# then ESR is offered every 8 major releases.
|
||||
esr_major_versions = (range(10, 59, 7) +
|
||||
range(60, int(firefox_desktop.latest_version().split('.')[0]), 8))
|
||||
esr_major_versions = (
|
||||
list(range(10, 59, 7)) +
|
||||
list(range(60, int(firefox_desktop.latest_version().split('.')[0]), 8)))
|
||||
|
||||
if product == 'Firefox':
|
||||
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))
|
||||
releases[major_version] = {
|
||||
'major': release,
|
||||
'minor': sorted(filter(lambda x: re.findall(major_pattern, x),
|
||||
minor_releases),
|
||||
key=lambda x: map(lambda y: int(y), x.split('.')))
|
||||
'minor': sorted([x for x in minor_releases if re.findall(major_pattern, x)],
|
||||
key=lambda x: [int(y) for y in x.split('.')])
|
||||
}
|
||||
|
||||
return l10n_utils.render(
|
||||
|
@ -175,8 +176,7 @@ def nightly_feed(request):
|
|||
releases = get_releases_or_404('firefox', 'nightly', 5)
|
||||
|
||||
for release in releases:
|
||||
link = reverse('firefox.desktop.releasenotes',
|
||||
args=(release.version, 'release'))
|
||||
link = reverse('firefox.desktop.releasenotes', args=(release.version, 'release'))
|
||||
|
||||
for note in release.notes:
|
||||
if note.id in notes:
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче