* Complete removal of nose test artifacts

* Update base requirements to Django 2.2 and python 3 compatibile packages
This commit is contained in:
Chris Beaven 2019-07-03 19:24:42 +12:00 коммит произвёл Josh Mize
Родитель 301bcfbd28
Коммит bf67e8bf34
177 изменённых файлов: 3602 добавлений и 3012 удалений

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

@ -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

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

@ -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&amp;utm_medium=referral&amp;utm_campaign=accounts-trailhead'
'&amp;utm_content=accounts-value&amp;utm_term=respect-you-deserve')
promise_query = (
'?utm_source=www.mozilla.org&amp;utm_medium=referral&amp;utm_campaign=accounts-trailhead'
'&amp;utm_content=accounts-value&amp;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&amp;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 &lt;b&gt;&lt;em&gt;Steve&lt;/em&gt;&lt;/b&gt;'
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 &lt;b&gt;&lt;em&gt;Steve&lt;/em&gt;&lt;/b&gt;'
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:

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