From bf67e8bf342935b4c2e20662ef1ed9a135624aa2 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Wed, 3 Jul 2019 19:24:42 +1200 Subject: [PATCH] Django 2.2 (#7196) * Complete removal of nose test artifacts * Update base requirements to Django 2.2 and python 3 compatibile packages --- .dockerignore | 2 + Dockerfile | 5 +- Makefile | 2 +- bedrock/base/cache.py | 10 +- bedrock/base/log_settings.py | 2 +- .../management/commands/update_www_config.py | 3 +- bedrock/base/middleware.py | 48 +- bedrock/base/migrations/0001_initial.py | 1 - bedrock/base/models.py | 2 +- bedrock/base/templatetags/helpers.py | 23 +- bedrock/base/tests/test_accepted_locales.py | 6 +- bedrock/base/tests/test_middleware.py | 11 +- bedrock/base/tests/test_simple_dict_cache.py | 26 +- bedrock/base/tests/test_urlresolvers.py | 6 +- bedrock/base/urlresolvers.py | 6 +- bedrock/base/views.py | 12 +- .../commands/update_content_cards.py | 2 - .../contentcards/migrations/0001_initial.py | 2 - bedrock/contentcards/models.py | 2 +- bedrock/etc/urls.py | 2 +- .../management/commands/update_ical_feeds.py | 2 - bedrock/events/migrations/0001_initial.py | 2 - .../migrations/0002_auto_20150612_0919.py | 2 - bedrock/events/models.py | 9 +- bedrock/events/tests/test_models.py | 6 +- bedrock/externalfiles/__init__.py | 9 +- .../commands/update_externalfiles.py | 2 - .../externalfiles/migrations/0001_initial.py | 2 - bedrock/firefox/firefox_details.py | 16 +- bedrock/firefox/migrations/0001_initial.py | 2 - .../0002_delete_firefoxosfeedlink.py | 2 - bedrock/firefox/redirects.py | 106 +- bedrock/firefox/tests/test_base.py | 131 +- bedrock/firefox/tests/test_firefox_details.py | 1174 ++++++--- bedrock/firefox/tests/test_helpers.py | 11 +- bedrock/firefox/tests/test_views.py | 645 +++-- bedrock/firefox/urls.py | 13 +- bedrock/firefox/views.py | 307 ++- bedrock/grants/templates/grants/index.html | 2 +- bedrock/grants/tests/test_base.py | 6 +- bedrock/grants/urls.py | 2 +- bedrock/grants/views.py | 6 +- bedrock/legal/tests/test_forms.py | 6 +- bedrock/legal/views.py | 10 +- bedrock/legal_docs/tests/test_base.py | 26 +- bedrock/legal_docs/views.py | 15 +- bedrock/mozorg/context_processors.py | 2 +- bedrock/mozorg/credits.py | 6 +- bedrock/mozorg/forms.py | 26 +- bedrock/mozorg/forums.py | 2 +- bedrock/mozorg/hierarchy.py | 2 +- .../commands/update_product_details_files.py | 2 - bedrock/mozorg/middleware.py | 38 +- bedrock/mozorg/migrations/0001_initial.py | 2 - bedrock/mozorg/migrations/0002_blogarticle.py | 2 - .../migrations/0003_delete_blogarticle.py | 2 - bedrock/mozorg/models.py | 2 +- bedrock/mozorg/redirects.py | 68 +- .../templates/mozorg/about/forums/forums.html | 2 +- bedrock/mozorg/templates/mozorg/credits.html | 2 +- bedrock/mozorg/templatetags/__init__.py | 3 +- bedrock/mozorg/templatetags/misc.py | 18 +- bedrock/mozorg/templatetags/social_widgets.py | 8 +- bedrock/mozorg/tests/test_credits.py | 6 +- bedrock/mozorg/tests/test_helper_misc.py | 63 +- .../tests/test_helper_social_widgets.py | 7 +- bedrock/mozorg/tests/test_hierarchy.py | 9 +- bedrock/mozorg/tests/test_middleware.py | 8 +- bedrock/mozorg/tests/test_views.py | 10 +- bedrock/mozorg/tests/urls.py | 5 +- bedrock/mozorg/urls.py | 2 +- bedrock/mozorg/util.py | 18 +- bedrock/mozorg/views.py | 18 +- bedrock/newsletter/forms.py | 8 +- .../commands/update_newsletter_data.py | 2 - bedrock/newsletter/migrations/0001_initial.py | 2 - bedrock/newsletter/models.py | 4 +- bedrock/newsletter/redirects.py | 8 +- bedrock/newsletter/tests/test_views.py | 35 +- bedrock/newsletter/utils.py | 4 +- bedrock/newsletter/views.py | 40 +- bedrock/pocketfeed/api.py | 2 - .../management/commands/update_pocketfeed.py | 2 - bedrock/pocketfeed/migrations/0001_initial.py | 2 - .../migrations/0002_auto_20180723_0805.py | 2 - bedrock/pocketfeed/models.py | 6 +- bedrock/pocketfeed/tests/test_api.py | 5 +- bedrock/press/redirects.py | 2 +- bedrock/press/tests/test_base.py | 8 +- bedrock/redirects/middleware.py | 13 +- bedrock/redirects/redirects.py | 2278 ++++++++--------- bedrock/redirects/tests/test_middleware.py | 2 +- bedrock/redirects/tests/test_util.py | 30 +- bedrock/redirects/util.py | 18 +- bedrock/releasenotes/__init__.py | 18 +- .../commands/update_release_notes.py | 2 - .../releasenotes/migrations/0001_initial.py | 2 - bedrock/releasenotes/models.py | 12 +- bedrock/releasenotes/tests/test_base.py | 42 +- bedrock/releasenotes/views.py | 14 +- .../commands/update_security_advisories.py | 66 +- bedrock/security/migrations/0001_initial.py | 2 - .../migrations/0002_auto_20161013_0642.py | 2 - .../security/migrations/0003_halloffamer.py | 2 - bedrock/security/migrations/0004_mitrecve.py | 6 +- .../migrations/0005_mitrecve_mfsa_ids.py | 4 +- bedrock/security/models.py | 10 +- bedrock/security/tests/test_commands.py | 4 +- bedrock/security/tests/test_utils.py | 2 +- bedrock/security/tests/test_views.py | 35 +- bedrock/security/utils.py | 2 +- bedrock/security/views.py | 4 +- bedrock/settings/__init__.py | 4 +- bedrock/settings/base.py | 10 +- settings_test.py => bedrock/settings/test.py | 2 +- bedrock/sitemaps/utils.py | 28 +- bedrock/utils/git.py | 14 +- bedrock/utils/migrations/0001_initial.py | 2 - .../migrations/0002_auto_20180522_1249.py | 2 - bedrock/utils/models.py | 2 +- bedrock/utils/tests/test_git.py | 2 - bedrock/utils/tests/test_views.py | 2 - bedrock/utils/views.py | 2 +- bedrock/wordpress/api.py | 1 - .../management/commands/update_wordpress.py | 2 - bedrock/wordpress/migrations/0001_initial.py | 2 - bedrock/wordpress/models.py | 7 +- bedrock/wordpress/views.py | 4 +- bin/cron.py | 4 +- bin/move_hashed_staticfiles.py | 4 +- bin/open-compare.py | 12 +- bin/run-db-download.py | 2 - bin/run-db-upload.py | 2 - bin/run-tests.sh | 2 +- docker/envfiles/master.env | 2 +- lib/l10n_utils/__init__.py | 2 +- lib/l10n_utils/dotlang.py | 4 +- lib/l10n_utils/extract.py | 4 +- lib/l10n_utils/gettext.py | 16 +- .../management/commands/l10n_check.py | 32 +- .../management/commands/l10n_extract.py | 6 +- .../management/commands/l10n_merge.py | 4 +- .../management/commands/l10n_update.py | 2 - lib/l10n_utils/template.py | 8 +- lib/l10n_utils/tests/__init__.py | 6 +- lib/l10n_utils/tests/test_commands.py | 12 +- lib/l10n_utils/tests/test_dotlang.py | 27 +- lib/l10n_utils/tests/test_extract.py | 18 +- lib/l10n_utils/tests/test_gettext.py | 2 +- lib/l10n_utils/translation.py | 6 +- lib/l10n_utils/utils.py | 2 +- requirements/base.txt | 202 +- requirements/dev.txt | 162 +- requirements/prod.txt | 51 +- scripts/check_calendars.py | 5 +- scripts/update_tableau_data.py | 10 +- setup.cfg | 25 +- setup.py | 7 +- tests/conftest.py | 16 +- tests/functional/conftest.py | 29 +- tests/functional/firefox/test_accounts.py | 4 +- tests/functional/test_contact.py | 14 +- tests/functional/test_history.py | 5 +- tests/functional/test_link_hreflang_tags.py | 2 +- tests/functional/test_navigation.py | 11 +- tests/pages/__init__.py | 1 + tests/pages/base.py | 14 +- tests/pages/contact.py | 9 +- tests/pages/firefox/base.py | 2 +- tests/pages/leadership.py | 5 +- tests/redirects/base.py | 15 +- tests/redirects/map_301.py | 2 - tests/redirects/map_globalconf.py | 27 +- tests/redirects/map_htaccess.py | 2 - tests/redirects/map_locales.py | 2 - tests/redirects/test_redirects.py | 1 - tests/redirects/test_urls.py | 4 +- 177 files changed, 3602 insertions(+), 3012 deletions(-) rename settings_test.py => bedrock/settings/test.py (67%) diff --git a/.dockerignore b/.dockerignore index a715c9d790..a4d17b5876 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,4 @@ .git .env +**/__pycache__ +**/*.pyc diff --git a/Dockerfile b/Dockerfile index 65bb697d11..e7de471d01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/Makefile b/Makefile index 301d4d72ca..0fb5975394 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ clean: git clean -f lint: .docker-build-pull - ${DC} run test flake8 bedrock lib tests + ${DC} run test flake8 ${DC} run assets gulp js:lint css:lint test: .docker-build-pull diff --git a/bedrock/base/cache.py b/bedrock/base/cache.py index ce8e490f3a..a317706025 100644 --- a/bedrock/base/cache.py +++ b/bedrock/base/cache.py @@ -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 diff --git a/bedrock/base/log_settings.py b/bedrock/base/log_settings.py index 44f9f919a4..5f29ac3acf 100644 --- a/bedrock/base/log_settings.py +++ b/bedrock/base/log_settings.py @@ -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: diff --git a/bedrock/base/management/commands/update_www_config.py b/bedrock/base/management/commands/update_www_config.py index 3be8f7b7b7..3c827786c7 100644 --- a/bedrock/base/management/commands/update_www_config.py +++ b/bedrock/base/management/commands/update_www_config.py @@ -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 diff --git a/bedrock/base/middleware.py b/bedrock/base/middleware.py index 67e4b48fd4..6fb56c7000 100644 --- a/bedrock/base/middleware.py +++ b/bedrock/base/middleware.py @@ -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 diff --git a/bedrock/base/migrations/0001_initial.py b/bedrock/base/migrations/0001_initial.py index a6c680bb09..f7966e721d 100644 --- a/bedrock/base/migrations/0001_initial.py +++ b/bedrock/base/migrations/0001_initial.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals from django.db import migrations, models diff --git a/bedrock/base/models.py b/bedrock/base/models.py index 0053dc1716..28b489809c 100644 --- a/bedrock/base/models.py +++ b/bedrock/base/models.py @@ -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) diff --git a/bedrock/base/templatetags/helpers.py b/bedrock/base/templatetags/helpers.py index 020a7277ef..b3412aa460 100644 --- a/bedrock/base/templatetags/helpers.py +++ b/bedrock/base/templatetags/helpers.py @@ -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 diff --git a/bedrock/base/tests/test_accepted_locales.py b/bedrock/base/tests/test_accepted_locales.py index 39b6656c15..ae76a11e8b 100644 --- a/bedrock/base/tests/test_accepted_locales.py +++ b/bedrock/base/tests/test_accepted_locales.py @@ -82,9 +82,9 @@ 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 ' - 'allowed locales.') + assert settings.LANGUAGE_URL_MAP == {'en-us': 'en-US', 'fr': 'fr'}, ( + 'DEV is True, but DEV_LANGUAGES are not used to define the ' + 'allowed locales.') def test_prod_languages(self): """Test the accepted locales on prod instances. diff --git a/bedrock/base/tests/test_middleware.py b/bedrock/base/tests/test_middleware.py index 7beeec8be0..a2ec78df8b 100644 --- a/bedrock/base/tests/test_middleware.py +++ b/bedrock/base/tests/test_middleware.py @@ -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) diff --git a/bedrock/base/tests/test_simple_dict_cache.py b/bedrock/base/tests/test_simple_dict_cache.py index 944bfef35b..232daa8a24 100644 --- a/bedrock/base/tests/test_simple_dict_cache.py +++ b/bedrock/base/tests/test_simple_dict_cache.py @@ -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 diff --git a/bedrock/base/tests/test_urlresolvers.py b/bedrock/base/tests/test_urlresolvers.py index 8c9fab9c39..0a2b64f15d 100644 --- a/bedrock/base/tests/test_urlresolvers.py +++ b/bedrock/base/tests/test_urlresolvers.py @@ -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 diff --git a/bedrock/base/urlresolvers.py b/bedrock/base/urlresolvers.py index 83a5f163ab..ace093032b 100644 --- a/bedrock/base/urlresolvers.py +++ b/bedrock/base/urlresolvers.py @@ -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) diff --git a/bedrock/base/views.py b/bedrock/base/views.py index 1de6e4e3f2..2f2c7e465e 100644 --- a/bedrock/base/views.py +++ b/bedrock/base/views.py @@ -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, }) diff --git a/bedrock/contentcards/management/commands/update_content_cards.py b/bedrock/contentcards/management/commands/update_content_cards.py index 34fe12c9be..df9c51ce3f 100644 --- a/bedrock/contentcards/management/commands/update_content_cards.py +++ b/bedrock/contentcards/management/commands/update_content_cards.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from django.conf import settings from django.core.management.base import BaseCommand diff --git a/bedrock/contentcards/migrations/0001_initial.py b/bedrock/contentcards/migrations/0001_initial.py index 5837911911..8c9c0e2922 100644 --- a/bedrock/contentcards/migrations/0001_initial.py +++ b/bedrock/contentcards/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django_extensions.db.fields.json diff --git a/bedrock/contentcards/models.py b/bedrock/contentcards/models.py index 44d2818903..55a242aec3 100644 --- a/bedrock/contentcards/models.py +++ b/bedrock/contentcards/models.py @@ -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 diff --git a/bedrock/etc/urls.py b/bedrock/etc/urls.py index 9c7fe02f3e..e485104c41 100644 --- a/bedrock/etc/urls.py +++ b/bedrock/etc/urls.py @@ -4,7 +4,7 @@ from django.conf.urls import url from bedrock.mozorg.util import page -import views +from bedrock.etc import views urlpatterns = ( diff --git a/bedrock/events/management/commands/update_ical_feeds.py b/bedrock/events/management/commands/update_ical_feeds.py index fe9a734821..d9f63dd573 100644 --- a/bedrock/events/management/commands/update_ical_feeds.py +++ b/bedrock/events/management/commands/update_ical_feeds.py @@ -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 diff --git a/bedrock/events/migrations/0001_initial.py b/bedrock/events/migrations/0001_initial.py index 257ae8779d..882bca4e9e 100644 --- a/bedrock/events/migrations/0001_initial.py +++ b/bedrock/events/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/bedrock/events/migrations/0002_auto_20150612_0919.py b/bedrock/events/migrations/0002_auto_20150612_0919.py index facb3d3b00..2f60cfda9d 100644 --- a/bedrock/events/migrations/0002_auto_20150612_0919.py +++ b/bedrock/events/migrations/0002_auto_20150612_0919.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/bedrock/events/models.py b/bedrock/events/models.py index a94e360465..2b6f3c19f5 100644 --- a/bedrock/events/models.py +++ b/bedrock/events/models.py @@ -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'{0:%b}'.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) diff --git a/bedrock/events/tests/test_models.py b/bedrock/events/tests/test_models.py index 606635ae7c..aaa61d39e2 100644 --- a/bedrock/events/tests/test_models.py +++ b/bedrock/events/tests/test_models.py @@ -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): """ diff --git a/bedrock/externalfiles/__init__.py b/bedrock/externalfiles/__init__.py index aa51a49211..65c0317bc5 100644 --- a/bedrock/externalfiles/__init__.py +++ b/bedrock/externalfiles/__init__.py @@ -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() diff --git a/bedrock/externalfiles/management/commands/update_externalfiles.py b/bedrock/externalfiles/management/commands/update_externalfiles.py index 534e7d0ae5..fc2905ac2c 100644 --- a/bedrock/externalfiles/management/commands/update_externalfiles.py +++ b/bedrock/externalfiles/management/commands/update_externalfiles.py @@ -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 diff --git a/bedrock/externalfiles/migrations/0001_initial.py b/bedrock/externalfiles/migrations/0001_initial.py index b001ebd4bd..ef12966c0e 100644 --- a/bedrock/externalfiles/migrations/0001_initial.py +++ b/bedrock/externalfiles/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/bedrock/firefox/firefox_details.py b/bedrock/firefox/firefox_details.py index ca00f20656..1f314159ec 100644 --- a/bedrock/firefox/firefox_details.py +++ b/bedrock/firefox/firefox_details.py @@ -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 diff --git a/bedrock/firefox/migrations/0001_initial.py b/bedrock/firefox/migrations/0001_initial.py index 299fb74329..6bb26580e8 100644 --- a/bedrock/firefox/migrations/0001_initial.py +++ b/bedrock/firefox/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/bedrock/firefox/migrations/0002_delete_firefoxosfeedlink.py b/bedrock/firefox/migrations/0002_delete_firefoxosfeedlink.py index 274d9c9fa1..fb0828d29a 100644 --- a/bedrock/firefox/migrations/0002_delete_firefoxosfeedlink.py +++ b/bedrock/firefox/migrations/0002_delete_firefoxosfeedlink.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations diff --git a/bedrock/firefox/redirects.py b/bedrock/firefox/redirects.py index 45bc236f96..db3e657909 100644 --- a/bedrock/firefox/redirects.py +++ b/bedrock/firefox/redirects.py @@ -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[\/\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/(?P1\..*)\.html$', 'http://www.seamonkey-project.org/releases/{vers}'), - redirect(r'^projects/seamonkey/releases/seamonkey(?P.*)/index.html$', + redirect(r'^projects/seamonkey/releases/seamonkey(?P.*)/index\.html$', 'http://www.seamonkey-project.org/releases/seamonkey{x}/'), - redirect(r'^projects/seamonkey/releases/seamonkey(?P.*/.*).html$', + redirect(r'^projects/seamonkey/releases/seamonkey(?P.*/.*)\.html$', 'http://www.seamonkey-project.org/releases/seamonkey{x}'), redirect(r'^projects/seamonkey/releases/updates/(?P.*)$', '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\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'), diff --git a/bedrock/firefox/tests/test_base.py b/bedrock/firefox/tests/test_base.py index aaef7a9fbb..1118180ffa 100644 --- a/bedrock/firefox/tests/test_base.py +++ b/bedrock/firefox/tests/test_base.py @@ -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): diff --git a/bedrock/firefox/tests/test_firefox_details.py b/bedrock/firefox/tests/test_firefox_details.py index 9595afff10..b5852f18c4 100644 --- a/bedrock/firefox/tests/test_firefox_details.py +++ b/bedrock/firefox/tests/test_firefox_details.py @@ -3,25 +3,18 @@ # 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 os -from urlparse import parse_qsl, urlparse +from urllib.parse import parse_qsl, urlparse from django.core.cache import caches - -from mock import patch, Mock +from mock import Mock, patch from bedrock.firefox.firefox_details import FirefoxDesktop, FirefoxAndroid, FirefoxIOS from bedrock.mozorg.tests import TestCase - TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), 'test_data') PROD_DETAILS_DIR = os.path.join(TEST_DATA_DIR, 'product_details_json') -firefox_desktop = FirefoxDesktop(json_dir=PROD_DETAILS_DIR) -firefox_android = FirefoxAndroid(json_dir=PROD_DETAILS_DIR) -firefox_ios = FirefoxIOS(json_dir=PROD_DETAILS_DIR) - - GOOD_PLATS = {'Windows': {}, 'OS X': {}, 'Linux': {}} GOOD_BUILDS = { 'en-US': { @@ -29,12 +22,8 @@ GOOD_BUILDS = { '26.0b2': GOOD_PLATS, '27.0a1': GOOD_PLATS, }, - 'de': { - '25.0': GOOD_PLATS, - }, - 'fr': { - '24.0': GOOD_PLATS, # prev release - } + 'de': {'25.0': GOOD_PLATS}, + 'fr': {'24.0': GOOD_PLATS}, # prev release } GOOD_VERSIONS = { 'LATEST_FIREFOX_VERSION': '25.0', @@ -51,55 +40,74 @@ GOOD_VERSIONS_NO_DEV = { } -@patch.object(firefox_desktop, 'firefox_primary_builds', GOOD_BUILDS) -@patch.object(firefox_desktop, 'firefox_beta_builds', {}) -@patch.object(firefox_desktop, 'firefox_versions', GOOD_VERSIONS) -class TestLatestBuilds(TestCase): +class PatchFirefoxDesktopMixin: + firefox_desktop_versions = GOOD_VERSIONS + + def setUp(self): + self.firefox_desktop = FirefoxDesktop(json_dir=PROD_DETAILS_DIR) + self.p1 = patch.object( + self.firefox_desktop, 'firefox_primary_builds', GOOD_BUILDS + ) + self.p2 = patch.object(self.firefox_desktop, 'firefox_beta_builds', {}) + self.p3 = patch.object( + self.firefox_desktop, 'firefox_versions', self.firefox_desktop_versions + ) + self.p1.start() + self.p2.start() + self.p3.start() + + def tearDown(self): + self.p1.stop() + self.p2.stop() + self.p3.stop() + + +class TestLatestBuilds(PatchFirefoxDesktopMixin, TestCase): def test_latest_builds(self): """Should return platforms if localized build does exist.""" - result = firefox_desktop.latest_builds('de', 'release') + result = self.firefox_desktop.latest_builds('de', 'release') self.assertEqual(result[0], '25.0') self.assertIs(result[1], GOOD_PLATS) def test_latest_builds_is_none_if_no_build(self): """Should return None if the localized build for the channel doesn't exist.""" - result = firefox_desktop.latest_builds('fr', 'release') + result = self.firefox_desktop.latest_builds('fr', 'release') self.assertIsNone(result) def test_latest_builds_channels(self): """Should work with all channels.""" - result = firefox_desktop.latest_builds('en-US', 'beta') + result = self.firefox_desktop.latest_builds('en-US', 'beta') self.assertEqual(result[0], '26.0b2') self.assertIs(result[1], GOOD_PLATS) - result = firefox_desktop.latest_builds('en-US', 'alpha') + result = self.firefox_desktop.latest_builds('en-US', 'alpha') self.assertEqual(result[0], '26.0b2') self.assertIs(result[1], GOOD_PLATS) -@patch.object(firefox_desktop, 'firefox_primary_builds', GOOD_BUILDS) -@patch.object(firefox_desktop, 'firefox_beta_builds', {}) -@patch.object(firefox_desktop, 'firefox_versions', GOOD_VERSIONS_NO_DEV) -class TestLatestBuildsNoDevEdition(TestCase): +class TestLatestBuildsNoDevEdition(PatchFirefoxDesktopMixin, TestCase): """Should get same results as above and not blow up""" + + firefox_desktop_versions = GOOD_VERSIONS_NO_DEV + def test_latest_builds(self): """Should return platforms if localized build does exist.""" - result = firefox_desktop.latest_builds('de', 'release') + result = self.firefox_desktop.latest_builds('de', 'release') self.assertEqual(result[0], '25.0') self.assertIs(result[1], GOOD_PLATS) def test_latest_builds_is_none_if_no_build(self): """Should return None if the localized build for the channel doesn't exist.""" - result = firefox_desktop.latest_builds('fr', 'release') + result = self.firefox_desktop.latest_builds('fr', 'release') self.assertIsNone(result) def test_latest_builds_channels(self): """Should work with all channels.""" - result = firefox_desktop.latest_builds('en-US', 'beta') + result = self.firefox_desktop.latest_builds('en-US', 'beta') self.assertEqual(result[0], '26.0b2') self.assertIs(result[1], GOOD_PLATS) - result = firefox_desktop.latest_builds('en-US', 'alpha') + result = self.firefox_desktop.latest_builds('en-US', 'alpha') self.assertEqual(result[0], '26.0b2') self.assertIs(result[1], GOOD_PLATS) @@ -109,170 +117,290 @@ class TestFirefoxDesktop(TestCase): def setUp(self): self.pd_cache.clear() + self.firefox_desktop = FirefoxDesktop(json_dir=PROD_DETAILS_DIR) def test_get_download_url(self): - url = firefox_desktop.get_download_url('release', '17.0.1', 'osx', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-latest-ssl'), - ('os', 'osx'), - ('lang', 'pt-BR')]) + url = self.firefox_desktop.get_download_url( + 'release', '17.0.1', 'osx', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-latest-ssl'), ('os', 'osx'), ('lang', 'pt-BR')], + ) # Windows 64-bit - url = firefox_desktop.get_download_url('release', '38.0', 'win64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-stub'), - ('os', 'win64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'release', '38.0', 'win64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-stub'), ('os', 'win64'), ('lang', 'en-US')], + ) # Windows 64-bit MSI installer - url = firefox_desktop.get_download_url('release', '38.0', 'win64-msi', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-msi-latest-ssl'), - ('os', 'win64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'release', '38.0', 'win64-msi', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-msi-latest-ssl'), ('os', 'win64'), ('lang', 'en-US')], + ) # Linux 64-bit - url = firefox_desktop.get_download_url('release', '17.0.1', 'linux64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-latest-ssl'), - ('os', 'linux64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'release', '17.0.1', 'linux64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-latest-ssl'), ('os', 'linux64'), ('lang', 'en-US')], + ) def test_get_download_url_esr(self): """ The ESR version should give us a bouncer url. There is no stub for ESR. """ - url = firefox_desktop.get_download_url('esr', '28.0a2', 'win', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-latest-ssl'), - ('os', 'win'), - ('lang', 'en-US')]) # MSI installer - url = firefox_desktop.get_download_url('esr', '28.0a2', 'win-msi', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-msi-latest-ssl'), - ('os', 'win'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('esr', '28.0a2', 'win64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-latest-ssl'), - ('os', 'win64'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('esr', '28.0a2', 'osx', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-latest-ssl'), - ('os', 'osx'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('esr', '28.0a2', 'linux', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-latest-ssl'), - ('os', 'linux'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('esr', '28.0a2', 'linux64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-latest-ssl'), - ('os', 'linux64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'esr', '28.0a2', 'win-msi', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-esr-msi-latest-ssl'), + ('os', 'win'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'esr', '28.0a2', 'win64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-esr-latest-ssl'), ('os', 'win64'), ('lang', 'en-US')], + ) + url = self.firefox_desktop.get_download_url( + 'esr', '28.0a2', 'osx', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-esr-latest-ssl'), ('os', 'osx'), ('lang', 'en-US')], + ) + url = self.firefox_desktop.get_download_url( + 'esr', '28.0a2', 'linux', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-esr-latest-ssl'), ('os', 'linux'), ('lang', 'en-US')], + ) + url = self.firefox_desktop.get_download_url( + 'esr', '28.0a2', 'linux64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-esr-latest-ssl'), + ('os', 'linux64'), + ('lang', 'en-US'), + ], + ) def test_get_download_url_esr_next(self): """ The ESR_NEXT version should give us a bouncer url with 'esr-next'. There is no stub for ESR. """ - url = firefox_desktop.get_download_url('esr_next', '52.4.1esr', 'win', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-next-latest-ssl'), - ('os', 'win'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('esr_next', '52.4.1esr', 'win64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-next-latest-ssl'), - ('os', 'win64'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('esr_next', '52.4.1esr', 'win64-msi', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-next-msi-latest-ssl'), - ('os', 'win64'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('esr_next', '52.4.1esr', 'osx', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-next-latest-ssl'), - ('os', 'osx'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('esr_next', '52.4.1esr', 'linux', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-next-latest-ssl'), - ('os', 'linux'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('esr_next', '52.4.1esr', 'linux64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-esr-next-latest-ssl'), - ('os', 'linux64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'esr_next', '52.4.1esr', 'win', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-esr-next-latest-ssl'), + ('os', 'win'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'esr_next', '52.4.1esr', 'win64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-esr-next-latest-ssl'), + ('os', 'win64'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'esr_next', '52.4.1esr', 'win64-msi', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-esr-next-msi-latest-ssl'), + ('os', 'win64'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'esr_next', '52.4.1esr', 'osx', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-esr-next-latest-ssl'), + ('os', 'osx'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'esr_next', '52.4.1esr', 'linux', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-esr-next-latest-ssl'), + ('os', 'linux'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'esr_next', '52.4.1esr', 'linux64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-esr-next-latest-ssl'), + ('os', 'linux64'), + ('lang', 'en-US'), + ], + ) def test_get_download_url_devedition(self): """ The Developer Edition version should give us a bouncer url. For Windows, a stub url should be returned. """ - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'win', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-stub'), - ('os', 'win'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'win64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-stub'), - ('os', 'win64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'win', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-devedition-stub'), ('os', 'win'), ('lang', 'en-US')], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'win64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-stub'), + ('os', 'win64'), + ('lang', 'en-US'), + ], + ) # MSI installer - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'win64-msi', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-msi-latest-ssl'), - ('os', 'win64'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'osx', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'osx'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'linux', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'linux'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'linux64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'linux64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'win64-msi', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-msi-latest-ssl'), + ('os', 'win64'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'osx', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'osx'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'linux', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'linux'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'linux64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'linux64'), + ('lang', 'en-US'), + ], + ) def test_get_download_url_devedition_full(self): """ The Developer Edition version should give us a bouncer url. For Windows, a full url should be returned. """ - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'win', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'win'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'win64', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'win64'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'osx', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'osx'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'linux', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'linux'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'linux64', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'linux64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'win', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'win'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'win64', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'win64'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'osx', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'osx'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'linux', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'linux'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'linux64', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'linux64'), + ('lang', 'en-US'), + ], + ) def test_get_download_url_devedition_l10n(self): """ @@ -280,105 +408,195 @@ class TestFirefoxDesktop(TestCase): should be returned for win32/64, while other platforms get a full url. The product name is the same as en-US. """ - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'win', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-stub'), - ('os', 'win'), - ('lang', 'pt-BR')]) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'win', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-devedition-stub'), ('os', 'win'), ('lang', 'pt-BR')], + ) # MSI installer - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'win-msi', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-msi-latest-ssl'), - ('os', 'win'), - ('lang', 'pt-BR')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'win64', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-stub'), - ('os', 'win64'), - ('lang', 'pt-BR')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'osx', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'osx'), - ('lang', 'pt-BR')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'linux', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'linux'), - ('lang', 'pt-BR')]) - url = firefox_desktop.get_download_url('alpha', '28.0a2', 'linux64', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-devedition-latest-ssl'), - ('os', 'linux64'), - ('lang', 'pt-BR')]) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'win-msi', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-msi-latest-ssl'), + ('os', 'win'), + ('lang', 'pt-BR'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'win64', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-stub'), + ('os', 'win64'), + ('lang', 'pt-BR'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'osx', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'osx'), + ('lang', 'pt-BR'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'linux', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'linux'), + ('lang', 'pt-BR'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'alpha', '28.0a2', 'linux64', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-devedition-latest-ssl'), + ('os', 'linux64'), + ('lang', 'pt-BR'), + ], + ) def test_get_download_url_nightly(self): """ The Nightly version should give us a bouncer url. For Windows, a stub url should be returned. """ - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'win', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-stub'), - ('os', 'win'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'win', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-nightly-stub'), ('os', 'win'), ('lang', 'en-US')], + ) # MSI installer - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'win64-msi', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-msi-latest-ssl'), - ('os', 'win64'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'win64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-stub'), - ('os', 'win64'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'osx', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-ssl'), - ('os', 'osx'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'linux', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-ssl'), - ('os', 'linux'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'linux64', 'en-US', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-ssl'), - ('os', 'linux64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'win64-msi', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-msi-latest-ssl'), + ('os', 'win64'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'win64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-nightly-stub'), ('os', 'win64'), ('lang', 'en-US')], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'osx', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-ssl'), + ('os', 'osx'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'linux', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-ssl'), + ('os', 'linux'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'linux64', 'en-US', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-ssl'), + ('os', 'linux64'), + ('lang', 'en-US'), + ], + ) def test_get_download_url_nightly_full(self): """ The Nightly version should give us a bouncer url. For Windows, a full url should be returned. """ - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'win', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-ssl'), - ('os', 'win'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'win64', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-ssl'), - ('os', 'win64'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'osx', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-ssl'), - ('os', 'osx'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'linux', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-ssl'), - ('os', 'linux'), - ('lang', 'en-US')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'linux64', 'en-US', True, True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-ssl'), - ('os', 'linux64'), - ('lang', 'en-US')]) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'win', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-ssl'), + ('os', 'win'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'win64', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-ssl'), + ('os', 'win64'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'osx', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-ssl'), + ('os', 'osx'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'linux', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-ssl'), + ('os', 'linux'), + ('lang', 'en-US'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'linux64', 'en-US', True, True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-ssl'), + ('os', 'linux64'), + ('lang', 'en-US'), + ], + ) def test_get_download_url_nightly_l10n(self): """ @@ -386,54 +604,90 @@ class TestFirefoxDesktop(TestCase): returned for win32/64, while other platforms get a full url. The product name is slightly different from en-US. """ - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'win', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-stub'), - ('os', 'win'), - ('lang', 'pt-BR')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'win64', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-stub'), - ('os', 'win64'), - ('lang', 'pt-BR')]) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'win', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-nightly-stub'), ('os', 'win'), ('lang', 'pt-BR')], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'win64', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-nightly-stub'), ('os', 'win64'), ('lang', 'pt-BR')], + ) # MSI installer - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'win64-msi', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-msi-latest-l10n-ssl'), - ('os', 'win64'), - ('lang', 'pt-BR')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'osx', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-l10n-ssl'), - ('os', 'osx'), - ('lang', 'pt-BR')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'linux', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-l10n-ssl'), - ('os', 'linux'), - ('lang', 'pt-BR')]) - url = firefox_desktop.get_download_url('nightly', '50.0a1', 'linux64', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-nightly-latest-l10n-ssl'), - ('os', 'linux64'), - ('lang', 'pt-BR')]) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'win64-msi', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-msi-latest-l10n-ssl'), + ('os', 'win64'), + ('lang', 'pt-BR'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'osx', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-l10n-ssl'), + ('os', 'osx'), + ('lang', 'pt-BR'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'linux', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-l10n-ssl'), + ('os', 'linux'), + ('lang', 'pt-BR'), + ], + ) + url = self.firefox_desktop.get_download_url( + 'nightly', '50.0a1', 'linux64', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'firefox-nightly-latest-l10n-ssl'), + ('os', 'linux64'), + ('lang', 'pt-BR'), + ], + ) def test_get_download_url_scene2_funnelcake(self): - scene2 = firefox_desktop.download_base_url_transition - url = firefox_desktop.get_download_url('release', '45.0', 'win', 'en-US') + scene2 = self.firefox_desktop.download_base_url_transition + url = self.firefox_desktop.get_download_url('release', '45.0', 'win', 'en-US') self.assertEqual(url, scene2) - url = firefox_desktop.get_download_url('release', '45.0', 'win', 'en-US', funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'win', 'en-US', funnelcake_id='64' + ) self.assertEqual(url, scene2 + '?f=64') def test_get_download_url_scene2_with_locale(self): - scene2 = firefox_desktop.download_base_url_transition - url = firefox_desktop.get_download_url('release', '45.0', 'win', 'de', - locale_in_transition=True) + scene2 = self.firefox_desktop.download_base_url_transition + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'win', 'de', locale_in_transition=True + ) self.assertEqual(url, '/de' + scene2) - url = firefox_desktop.get_download_url('release', '45.0', 'win', 'fr', - locale_in_transition=True, - funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', + '45.0', + 'win', + 'fr', + locale_in_transition=True, + funnelcake_id='64', + ) self.assertEqual(url, '/fr' + scene2 + '?f=64') def get_download_url_ssl(self): @@ -442,192 +696,265 @@ class TestFirefoxDesktop(TestCase): """ # SSL-enabled links won't be used for Windows builds (but SSL download # is enabled by default for stub installers) - url = firefox_desktop.get_download_url('release', '27.0', 'win', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-stub'), - ('os', 'win'), - ('lang', 'pt-BR')]) + url = self.firefox_desktop.get_download_url( + 'release', '27.0', 'win', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-stub'), ('os', 'win'), ('lang', 'pt-BR')], + ) # SSL-enabled links will be used for OS X builds - url = firefox_desktop.get_download_url('release', '27.0', 'osx', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-latest-ssl'), - ('os', 'osx'), - ('lang', 'pt-BR')]) + url = self.firefox_desktop.get_download_url( + 'release', '27.0', 'osx', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-latest-ssl'), ('os', 'osx'), ('lang', 'pt-BR')], + ) # SSL-enabled links will be used for Linux builds - url = firefox_desktop.get_download_url('release', '27.0', 'linux', 'pt-BR', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'firefox-latest-ssl'), - ('os', 'linux'), - ('lang', 'pt-BR')]) + url = self.firefox_desktop.get_download_url( + 'release', '27.0', 'linux', 'pt-BR', True + ) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'firefox-latest-ssl'), ('os', 'linux'), ('lang', 'pt-BR')], + ) def test_filter_builds_by_locale_name(self): # search english - builds = firefox_desktop.get_filtered_full_builds('release', None, - 'ujara') + builds = self.firefox_desktop.get_filtered_full_builds('release', None, 'ujara') assert len(builds) == 1 assert builds[0]['name_en'] == 'Gujarati' # search native - builds = firefox_desktop.get_filtered_full_builds('release', None, - u'જરા') + builds = self.firefox_desktop.get_filtered_full_builds('release', None, u'જરા') assert len(builds) == 1 assert builds[0]['name_en'] == 'Gujarati' # with a space - builds = firefox_desktop.get_filtered_full_builds('release', None, - 'british english') + builds = self.firefox_desktop.get_filtered_full_builds( + 'release', None, 'british english' + ) assert len(builds) == 1 assert builds[0]['name_en'] == 'English (British)' # with a comma - builds = firefox_desktop.get_filtered_full_builds('release', None, - u'French, Français') + builds = self.firefox_desktop.get_filtered_full_builds( + 'release', None, u'French, Français' + ) assert len(builds) == 1 assert builds[0]['name_en'] == 'French' def test_windows64_build(self): # Aurora - builds = firefox_desktop.get_filtered_full_builds('alpha') + builds = self.firefox_desktop.get_filtered_full_builds('alpha') url = builds[0]['platforms']['win64']['download_url'] assert parse_qsl(urlparse(url).query)[1] == ('os', 'win64') # Beta - builds = firefox_desktop.get_filtered_full_builds('beta') + builds = self.firefox_desktop.get_filtered_full_builds('beta') url = builds[0]['platforms']['win64']['download_url'] assert parse_qsl(urlparse(url).query)[1] == ('os', 'win64') # Release - builds = firefox_desktop.get_filtered_full_builds('release') + builds = self.firefox_desktop.get_filtered_full_builds('release') url = builds[0]['platforms']['win64']['download_url'] assert parse_qsl(urlparse(url).query)[1] == ('os', 'win64') # ESR - builds = firefox_desktop.get_filtered_full_builds('esr') + builds = self.firefox_desktop.get_filtered_full_builds('esr') url = builds[0]['platforms']['win64']['download_url'] assert parse_qsl(urlparse(url).query)[1] == ('os', 'win64') def test_linux64_build(self): - builds = firefox_desktop.get_filtered_full_builds('release') + builds = self.firefox_desktop.get_filtered_full_builds('release') url = builds[0]['platforms']['linux64']['download_url'] assert parse_qsl(urlparse(url).query)[1] == ('os', 'linux64') - @patch.object(firefox_desktop._storage, 'data', - Mock(return_value=dict(FIREFOX_ESR='24.2'))) def test_esr_versions(self): """ESR versions should be dynamic based on data.""" - assert firefox_desktop.esr_major_versions == [24] - assert firefox_desktop.esr_minor_versions == ['24.2'] + with patch.object( + self.firefox_desktop._storage, + 'data', + Mock(return_value=dict(FIREFOX_ESR='24.2')), + ): + assert self.firefox_desktop.esr_major_versions == [24] + assert self.firefox_desktop.esr_minor_versions == ['24.2'] - @patch.object(firefox_desktop._storage, 'data', - Mock(return_value=dict(FIREFOX_ESR='24.6.0', - FIREFOX_ESR_NEXT='31.0.0'))) def test_esr_versions_prev(self): """ESR versions should show previous when available.""" - assert firefox_desktop.esr_major_versions == [24, 31] - assert firefox_desktop.esr_minor_versions == ['24.6.0', '31.0.0'] + with patch.object( + self.firefox_desktop._storage, + 'data', + Mock(return_value=dict(FIREFOX_ESR='24.6.0', FIREFOX_ESR_NEXT='31.0.0')), + ): + assert self.firefox_desktop.esr_major_versions == [24, 31] + assert self.firefox_desktop.esr_minor_versions == ['24.6.0', '31.0.0'] - @patch.object(firefox_desktop._storage, 'data', - Mock(return_value=dict(LATEST_FIREFOX_VERSION='Phoenix', - FIREFOX_ESR='Albuquerque'))) def test_esr_versions_no_latest(self): """ESR versions should not blow up if current version is broken.""" - assert firefox_desktop.esr_major_versions == [] - assert firefox_desktop.esr_minor_versions == [] + with patch.object( + self.firefox_desktop._storage, + 'data', + Mock( + return_value=dict( + LATEST_FIREFOX_VERSION='Phoenix', FIREFOX_ESR='Albuquerque' + ) + ), + ): + assert self.firefox_desktop.esr_major_versions == [] + assert self.firefox_desktop.esr_minor_versions == [] - @patch.object(firefox_desktop._storage, 'data', - Mock(return_value=dict(LATEST_FIREFOX_VERSION='18.0.1'))) def test_latest_major_version(self): """latest_major_version should return an int of the major version.""" - assert firefox_desktop.latest_major_version('release') == 18 + with patch.object( + self.firefox_desktop._storage, + 'data', + Mock(return_value=dict(LATEST_FIREFOX_VERSION='18.0.1')), + ): + assert self.firefox_desktop.latest_major_version('release') == 18 - @patch.object(firefox_desktop._storage, 'data', - Mock(return_value=dict(LATEST_FIREFOX_VERSION='Phoenix'))) def test_latest_major_version_no_int(self): """latest_major_version should return 0 when no int.""" - assert firefox_desktop.latest_major_version('release') == 0 + with patch.object( + self.firefox_desktop._storage, + 'data', + Mock(return_value=dict(LATEST_FIREFOX_VERSION='Phoenix')), + ): + assert self.firefox_desktop.latest_major_version('release') == 0 - @patch.dict(os.environ, FUNNELCAKE_64_LOCALES='en-US', FUNNELCAKE_64_PLATFORMS='win') + @patch.dict( + os.environ, FUNNELCAKE_64_LOCALES='en-US', FUNNELCAKE_64_PLATFORMS='win' + ) def test_funnelcake_direct_links_en_us_win_only(self): """ Ensure funnelcake params are included for Windows en-US builds only. """ - url = firefox_desktop.get_download_url('release', '45.0', 'win', 'en-US', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'win', 'en-US', force_direct=True, funnelcake_id='64' + ) assert 'product=firefox-stub-f64' in url - url = firefox_desktop.get_download_url('release', '45.0', 'win64', 'en-US', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'win64', 'en-US', force_direct=True, funnelcake_id='64' + ) assert 'product=firefox-stub-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'win', 'de', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'win', 'de', force_direct=True, funnelcake_id='64' + ) assert 'product=firefox-stub-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'osx', 'en-US', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'osx', 'en-US', force_direct=True, funnelcake_id='64' + ) assert 'product=firefox-stub-f64' not in url def test_no_funnelcake_direct_links_if_not_configured(self): """ Ensure funnelcake params are included for Linux and OSX en-US builds only. """ - url = firefox_desktop.get_download_url('release', '45.0', 'win', 'en-US', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'win', 'en-US', force_direct=True, funnelcake_id='64' + ) assert '-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'win', 'en-US', force_direct=True, force_full_installer=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', + '45.0', + 'win', + 'en-US', + force_direct=True, + force_full_installer=True, + funnelcake_id='64', + ) assert '-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'win', 'en-US', force_direct=True, force_funnelcake=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', + '45.0', + 'win', + 'en-US', + force_direct=True, + force_funnelcake=True, + funnelcake_id='64', + ) assert '-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'osx', 'de', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'osx', 'de', force_direct=True, funnelcake_id='64' + ) assert '-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'osx', 'en-US', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'osx', 'en-US', force_direct=True, funnelcake_id='64' + ) assert '-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'osx', 'fr', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'osx', 'fr', force_direct=True, funnelcake_id='64' + ) assert '-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'linux', 'de', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'linux', 'de', force_direct=True, funnelcake_id='64' + ) assert '-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'linux', 'en-US', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'linux', 'en-US', force_direct=True, funnelcake_id='64' + ) assert '-f64' not in url - url = firefox_desktop.get_download_url('release', '45.0', 'linux', 'fr', force_direct=True, funnelcake_id='64') + url = self.firefox_desktop.get_download_url( + 'release', '45.0', 'linux', 'fr', force_direct=True, funnelcake_id='64' + ) assert '-f64' not in url def test_stub_installer_win_only(self): """ Ensure that builds not in the setting don't get stub. """ - url = firefox_desktop.get_download_url('release', '19.0', 'osx', 'en-US') + url = self.firefox_desktop.get_download_url('release', '19.0', 'osx', 'en-US') assert 'product=firefox-stub&' not in url - url = firefox_desktop.get_download_url('beta', '20.0b4', 'win', 'fr') + url = self.firefox_desktop.get_download_url('beta', '20.0b4', 'win', 'fr') assert 'product=firefox-beta-stub&' in url - url = firefox_desktop.get_download_url('beta', '20.0b4', 'win64', 'fr') + url = self.firefox_desktop.get_download_url('beta', '20.0b4', 'win64', 'fr') assert 'product=firefox-beta-stub&' in url - url = firefox_desktop.get_download_url('beta', '20.0b4', 'linux', 'fr') + url = self.firefox_desktop.get_download_url('beta', '20.0b4', 'linux', 'fr') assert 'product=firefox-beta-stub&' not in url class TestFirefoxAndroid(TestCase): - google_play_url_base = ('https://play.google.com/store/apps/details' - '?id=org.mozilla.') + google_play_url_base = ( + 'https://play.google.com/store/apps/details' '?id=org.mozilla.' + ) + + def setUp(self): + self.firefox_android = FirefoxAndroid(json_dir=PROD_DETAILS_DIR) - @patch.object(firefox_android._storage, 'data', - Mock(return_value=dict(version='22.0.1'))) def test_latest_release_version(self): """latest_version should return the latest release version.""" - assert firefox_android.latest_version('release') == '22.0.1' + with patch.object( + self.firefox_android._storage, + 'data', + Mock(return_value=dict(version='22.0.1')), + ): + assert self.firefox_android.latest_version('release') == '22.0.1' - @patch.object(firefox_android._storage, 'data', - Mock(return_value=dict(beta_version='23.0'))) def test_latest_beta_version(self): """latest_version should return the latest beta version.""" - assert firefox_android.latest_version('beta') == '23.0' + with patch.object( + self.firefox_android._storage, + 'data', + Mock(return_value=dict(beta_version='23.0')), + ): + assert self.firefox_android.latest_version('beta') == '23.0' def test_get_download_url_nightly(self): """ @@ -635,26 +962,36 @@ class TestFirefoxAndroid(TestCase): 'org.mozilla.fennec_aurora' product regardless of the architecture type, if the force_direct option is unspecified. """ - assert ( - firefox_android.get_download_url('nightly', 'arm') - .startswith(self.google_play_url_base + 'fennec_aurora')) - assert ( - firefox_android.get_download_url('nightly', 'x86') - .startswith(self.google_play_url_base + 'fennec_aurora')) + assert self.firefox_android.get_download_url('nightly', 'arm').startswith( + self.google_play_url_base + 'fennec_aurora' + ) + assert self.firefox_android.get_download_url('nightly', 'x86').startswith( + self.google_play_url_base + 'fennec_aurora' + ) def test_get_download_url_nightly_direct(self): """ get_download_url should return a bouncer link depending on the architecture type, if the force_direct option is True. """ - url = firefox_android.get_download_url('nightly', 'arm', 'multi', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'fennec-nightly-latest'), - ('os', 'android'), ('lang', 'multi')]) - url = firefox_android.get_download_url('nightly', 'x86', 'multi', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'fennec-nightly-latest'), - ('os', 'android-x86'), ('lang', 'multi')]) + url = self.firefox_android.get_download_url('nightly', 'arm', 'multi', True) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'fennec-nightly-latest'), + ('os', 'android'), + ('lang', 'multi'), + ], + ) + url = self.firefox_android.get_download_url('nightly', 'x86', 'multi', True) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'fennec-nightly-latest'), + ('os', 'android-x86'), + ('lang', 'multi'), + ], + ) def test_get_download_url_beta(self): """ @@ -662,26 +999,32 @@ class TestFirefoxAndroid(TestCase): 'org.mozilla.firefox_beta' product regardless of the architecture type, if the force_direct option is unspecified. """ - assert ( - firefox_android.get_download_url('beta', 'arm') - .startswith(self.google_play_url_base + 'firefox_beta')) - assert ( - firefox_android.get_download_url('beta', 'x86') - .startswith(self.google_play_url_base + 'firefox_beta')) + assert self.firefox_android.get_download_url('beta', 'arm').startswith( + self.google_play_url_base + 'firefox_beta' + ) + assert self.firefox_android.get_download_url('beta', 'x86').startswith( + self.google_play_url_base + 'firefox_beta' + ) def test_get_download_url_beta_direct(self): """ get_download_url should return a bouncer link depending on the architecture type, if the force_direct option is True. """ - url = firefox_android.get_download_url('beta', 'arm', 'multi', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'fennec-beta-latest'), - ('os', 'android'), ('lang', 'multi')]) - url = firefox_android.get_download_url('beta', 'x86', 'multi', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'fennec-beta-latest'), - ('os', 'android-x86'), ('lang', 'multi')]) + url = self.firefox_android.get_download_url('beta', 'arm', 'multi', True) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'fennec-beta-latest'), ('os', 'android'), ('lang', 'multi')], + ) + url = self.firefox_android.get_download_url('beta', 'x86', 'multi', True) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [ + ('product', 'fennec-beta-latest'), + ('os', 'android-x86'), + ('lang', 'multi'), + ], + ) def test_get_download_url_release(self): """ @@ -689,36 +1032,47 @@ class TestFirefoxAndroid(TestCase): 'org.mozilla.firefox' product regardless of the architecture type, if the force_direct option is unspecified. """ - assert ( - firefox_android.get_download_url('release', 'arm') - .startswith(self.google_play_url_base + 'firefox')) - assert ( - firefox_android.get_download_url('release', 'x86') - .startswith(self.google_play_url_base + 'firefox')) + assert self.firefox_android.get_download_url('release', 'arm').startswith( + self.google_play_url_base + 'firefox' + ) + assert self.firefox_android.get_download_url('release', 'x86').startswith( + self.google_play_url_base + 'firefox' + ) def test_get_download_url_release_direct(self): """ get_download_url should return a bouncer link depending on the architecture type, if the force_direct option is True. """ - url = firefox_android.get_download_url('release', 'arm', 'multi', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'fennec-latest'), - ('os', 'android'), ('lang', 'multi')]) - url = firefox_android.get_download_url('release', 'x86', 'multi', True) - self.assertListEqual(parse_qsl(urlparse(url).query), - [('product', 'fennec-latest'), - ('os', 'android-x86'), ('lang', 'multi')]) + url = self.firefox_android.get_download_url('release', 'arm', 'multi', True) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'fennec-latest'), ('os', 'android'), ('lang', 'multi')], + ) + url = self.firefox_android.get_download_url('release', 'x86', 'multi', True) + self.assertListEqual( + parse_qsl(urlparse(url).query), + [('product', 'fennec-latest'), ('os', 'android-x86'), ('lang', 'multi')], + ) -@patch.object(firefox_ios._storage, 'data', - Mock(return_value=dict(ios_version='5.0', ios_beta_version='6.0'))) class TestFirefoxIos(TestCase): + def setUp(self): + self.firefox_ios = FirefoxIOS(json_dir=PROD_DETAILS_DIR) + self.patcher = patch.object( + self.firefox_ios._storage, + 'data', + Mock(return_value=dict(ios_version='5.0', ios_beta_version='6.0')), + ) + self.patcher.start() + + def tearDown(self): + self.patcher.stop() def test_latest_release_version(self): """latest_version should return the latest release version.""" - assert firefox_ios.latest_version('release') == '5.0' + assert self.firefox_ios.latest_version('release') == '5.0' def test_latest_beta_version(self): """latest_version should return the latest beta version.""" - assert firefox_ios.latest_version('beta') == '6.0' + assert self.firefox_ios.latest_version('beta') == '6.0' diff --git a/bedrock/firefox/tests/test_helpers.py b/bedrock/firefox/tests/test_helpers.py index 15afcf4517..d42dae246b 100644 --- a/bedrock/firefox/tests/test_helpers.py +++ b/bedrock/firefox/tests/test_helpers.py @@ -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): diff --git a/bedrock/firefox/tests/test_views.py b/bedrock/firefox/tests/test_views.py index d242d52f46..48975761bc 100644 --- a/bedrock/firefox/tests/test_views.py +++ b/bedrock/firefox/tests/test_views.py @@ -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', - STUB_ATTRIBUTION_RATE=1, - STUB_ATTRIBUTION_MAX_LEN=200) +@override_settings( + STUB_ATTRIBUTION_HMAC_KEY='achievers', + STUB_ATTRIBUTION_RATE=1, + STUB_ATTRIBUTION_MAX_LEN=200, +) class TestStubAttributionCode(TestCase): def _get_request(self, params): rf = RequestFactory() - return rf.get('/', params, - HTTP_X_REQUESTED_WITH='XMLHttpRequest', - HTTP_ACCEPT='application/json') + return rf.get( + '/', + params, + HTTP_X_REQUESTED_WITH='XMLHttpRequest', + 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': 'aeher'} @@ -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': 'aeher'} @@ -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""" @@ -106,13 +123,13 @@ class TestStubAttributionCode(TestCase): 'utm_medium': 'aether', 'utm_content': 'A144_A000_0000000', 'utm_campaign': 'The%7cDude%7cabides%7cI%7cdont%7cknow%7cabout%7cyou%7c' - 'but%7cI%7ctake%7ccomfort%7cin%7cthat' * 2, + 'but%7cI%7ctake%7ccomfort%7cin%7cthat' * 2, } final_params = { 'source': 'brandt', 'medium': 'aether', 'campaign': 'The|Dude|abides|I|dont|know|about|you|but|I|take|comfort|in|' - 'thatThe|Dude|abides|I|dont|know|about_', + 'thatThe|Dude|abides|I|dont|know|about_', 'content': 'A144_A000_0000000', } req = self._get_request(params) @@ -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""" @@ -137,7 +156,7 @@ class TestStubAttributionCode(TestCase): 'utm_campaign': 'dude', 'utm_content': 'A144_A000_0000000', 'utm_medium': 'The%7cDude%7cabides%7cI%7cdont%7cknow%7cabout%7cyou%7c' - 'but%7cI%7ctake%7ccomfort%7cin%7cthat' * 2, + 'but%7cI%7ctake%7ccomfort%7cin%7cthat' * 2, } final_params = {'error': 'Invalid code'} req = self._get_request(params) @@ -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={ - 'mobile_number': '5558675309', - 'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'], - 'lang': 'en-US', - }) + 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' + ], + '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={ - 'mobile_number': '015558675309', - 'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'], - 'lang': 'de', - }) + 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' + ], + '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={ - 'mobile_number': '5558675309', - 'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'], - 'lang': 'en-US', - 'country': 'de', - }) + 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' + ], + '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={ - 'mobile_number': '5558675309', - 'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'], - 'lang': 'en-US', - }) + 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' + ], + '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={ - 'mobile_number': '5558675309', - 'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['android'], - 'lang': 'en-US', - }) + 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' + ], + '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({ - 'platform': 'android', - 'phone-or-email': 'dude@example.com', - 'source-url': 'https://nihilism.info', - }) + 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', - views.SEND_TO_DEVICE_MESSAGE_SETS['default']['email']['android'], - source_url='https://nihilism.info', - lang='en-US') + 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', + ) def test_send_android_email_basket_error(self): self.mock_subscribe.side_effect = views.basket.BasketException - resp_data = self._request({ - 'platform': 'android', - 'phone-or-email': 'dude@example.com', - 'source-url': 'https://nihilism.info', - }, 400) + resp_data = self._request( + { + 'platform': 'android', + 'phone-or-email': 'dude@example.com', + 'source-url': 'https://nihilism.info', + }, + 400, + ) assert not resp_data['success'] assert 'system' in resp_data['errors'] def test_send_android_bad_email(self): - resp_data = self._request({ - 'platform': 'android', - 'phone-or-email': '@example.com', - 'source-url': 'https://nihilism.info', - }) + 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({ - 'platform': 'ios', - 'phone-or-email': '5558675309', - 'message-set': 'the-dude-is-not-in', - }) + 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={ - 'mobile_number': '5558675309', - 'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['default']['sms']['ios'], - 'lang': 'en-US', - }) + 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({ - 'platform': 'android', - 'phone-or-email': 'dude@example.com', - 'message-set': 'fx-android', - }) + 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', - views.SEND_TO_DEVICE_MESSAGE_SETS['fx-android']['email']['android'], - source_url=None, - lang='en-US') + 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', + ) def test_android_embedded_sms(self): - resp_data = self._request({ - 'platform': 'android', - 'phone-or-email': '5558675309', - 'message-set': 'fx-android', - }) + 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={ - 'mobile_number': '5558675309', - 'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['fx-android']['sms']['android'], - 'lang': 'en-US', - }) + 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' + ], + 'lang': 'en-US', + }, + ) # /firefox/mobile-download/desktop def test_fx_mobile_download_desktop_email(self): - resp_data = self._request({ - 'phone-or-email': 'dude@example.com', - 'message-set': 'fx-mobile-download-desktop', - }) + 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'], - source_url=None, - lang='en-US') + 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', + ) def test_fx_mobile_download_desktop_sms(self): - resp_data = self._request({ - 'phone-or-email': '5558675309', - 'message-set': 'fx-mobile-download-desktop', - }) + 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={ - 'mobile_number': '5558675309', - 'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['fx-mobile-download-desktop']['sms']['all'], - 'lang': 'en-US', - }) + 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'], + 'lang': 'en-US', + }, + ) def test_sms_number_with_punctuation(self): - resp_data = self._request({ - 'phone-or-email': '(555) 867-5309', - 'message-set': 'fx-mobile-download-desktop', - }) + 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={ - 'mobile_number': '5558675309', - 'msg_name': views.SEND_TO_DEVICE_MESSAGE_SETS['fx-mobile-download-desktop']['sms']['all'], - 'lang': 'en-US', - }) + 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'], + 'lang': 'en-US', + }, + ) def test_sms_number_too_long(self): - resp_data = self._request({ - 'phone-or-email': '5558675309555867530912', - 'message-set': 'fx-mobile-download-desktop', - }) + 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'} + ) diff --git a/bedrock/firefox/urls.py b/bedrock/firefox/urls.py index 47de3b87d0..78237ee217 100644 --- a/bedrock/firefox/urls.py +++ b/bedrock/firefox/urls.py @@ -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%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 = '(?Pandroid|ios)' channel_re = '(?Pbeta|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 = ( diff --git a/bedrock/firefox/views.py b/bedrock/firefox/views.py index 8a71d432a1..04983f5f8a 100644 --- a/bedrock/firefox/views.py +++ b/bedrock/firefox/views.py @@ -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], - source_url=request.POST.get('source-url'), - lang=locale) + basket.subscribe( + phone_or_email, + MESSAGES['email'][platform], + source_url=request.POST.get('source-url'), + 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,16 +258,18 @@ def all_downloads_unified(request): product_desktop = firefox_desktop # Human-readable product labels - products = OrderedDict([ - ('desktop_release', 'Firefox'), - ('desktop_beta', 'Firefox Beta'), - ('desktop_developer', 'Firefox Developer Edition'), - ('desktop_nightly', 'Firefox Nightly'), - ('desktop_esr', 'Firefox Extended Support Release'), - ('android_release', 'Firefox Android'), - ('android_beta', 'Firefox Android Beta'), - ('android_nightly', 'Firefox Android Nightly'), - ]) + products = OrderedDict( + [ + ('desktop_release', 'Firefox'), + ('desktop_beta', 'Firefox Beta'), + ('desktop_developer', 'Firefox Developer Edition'), + ('desktop_nightly', 'Firefox Nightly'), + ('desktop_esr', 'Firefox Extended Support Release'), + ('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/' - '?utm_source=Heartbeat_survey&utm_medium=referral' - '&utm_content=Heartbeat_{0}stars') + donate_url = ( + 'https://donate.mozilla.org/' + '?utm_source=Heartbeat_survey&utm_medium=referral' + '&utm_content=Heartbeat_{0}stars' + ) def get_score(self): - return self.request.GET.get('score', 0) + return self.request.GET.get('score', '0') def get_template_names(self): score = self.get_score() @@ -626,12 +663,13 @@ class FeedbackView(TemplateView): class TrackingProtectionTourView(l10n_utils.LangFilesMixin, TemplateView): - def get_template_names(self): variation = self.request.GET.get('variation', None) if variation in ['0', '1', '2']: - template = 'firefox/tracking-protection-tour/variation-{}.html'.format(variation) + template = 'firefox/tracking-protection-tour/variation-{}.html'.format( + variation + ) else: template = 'firefox/tracking-protection-tour/index.html' @@ -639,12 +677,13 @@ class TrackingProtectionTourView(l10n_utils.LangFilesMixin, TemplateView): class ContentBlockingTourView(l10n_utils.LangFilesMixin, TemplateView): - def get_template_names(self): variation = self.request.GET.get('variation', None) if variation in ['2']: - template = 'firefox/content-blocking-tour/variation-{}.html'.format(variation) + template = 'firefox/content-blocking-tour/variation-{}.html'.format( + variation + ) else: template = 'firefox/content-blocking-tour/index.html' @@ -656,7 +695,21 @@ def download_thanks(request): locale = l10n_utils.get_locale(request) variant = request.GET.get('v', None) newsletter = request.GET.get('n', None) - show_newsletter = locale in ['en-US', 'en-GB', 'en-CA', 'es-ES', 'es-AR', 'es-CL', 'es-MX', 'pt-BR', 'fr', 'ru', 'id', 'de', 'pl'] + show_newsletter = locale in [ + 'en-US', + 'en-GB', + 'en-CA', + 'es-ES', + 'es-AR', + 'es-CL', + 'es-MX', + 'pt-BR', + 'fr', + 'ru', + 'id', + 'de', + 'pl', + ] # ensure variant matches pre-defined value if variant not in ['b']: # place expected ?v= values in this list @@ -716,7 +769,9 @@ def new(request): thanks_url = reverse('firefox.download.thanks') query_string = request.META.get('QUERY_STRING', '') if query_string: - thanks_url = '?'.join([thanks_url, force_text(query_string, errors='ignore')]) + thanks_url = '?'.join( + [thanks_url, force_text(query_string, errors='ignore')] + ) return HttpResponsePermanentRedirect(thanks_url) # if no/incorrect scene specified, show scene 1 else: @@ -729,7 +784,9 @@ def new(request): # no harm done by passing 'v' to template, even when no experiment is running # (also makes tests easier to maintain by always sending a context) - return l10n_utils.render(request, template, {'experience': experience, 'v': variant}) + return l10n_utils.render( + request, template, {'experience': experience, 'v': variant} + ) def campaign(request): @@ -779,16 +836,18 @@ def campaign(request): # no harm done by passing 'v' to template, even when no experiment is running # (also makes tests easier to maintain by always sending a context) - return l10n_utils.render(request, template, {'experience': experience, 'v': variant}) + return l10n_utils.render( + request, template, {'experience': experience, 'v': variant} + ) def ios_testflight(request): # no country field, so no need to send locale newsletter_form = NewsletterFooterForm('ios-beta-test-flight', '') - return l10n_utils.render(request, - 'firefox/testflight.html', - {'newsletter_form': newsletter_form}) + return l10n_utils.render( + request, 'firefox/testflight.html', {'newsletter_form': newsletter_form} + ) def ad_blocker(request): @@ -847,10 +906,18 @@ def firefox_home(request): locale = l10n_utils.get_locale(request) variant = request.GET.get('v', None) newsletter_locales = ['en-US', 'en-GB', 'en-CA', 'en-ZA', 'fr', 'de'] - show_newsletter = switch('firefox_pre_download_newsletter') and locale in newsletter_locales + show_newsletter = ( + switch('firefox_pre_download_newsletter') and locale in newsletter_locales + ) # ensure variant matches pre-defined value - if variant not in ['a', 'b', 'c', 'd', 'e']: # place expected ?v= values in this list + if variant not in [ + 'a', + 'b', + 'c', + 'd', + 'e', + ]: # place expected ?v= values in this list variant = None if locale == 'en-US' and variant is not None and variant != 'a': @@ -858,9 +925,9 @@ def firefox_home(request): else: template = 'firefox/home/index.html' - return l10n_utils.render(request, - template, - {'show_newsletter': show_newsletter, 'variation': variant}) + return l10n_utils.render( + request, template, {'show_newsletter': show_newsletter, 'variation': variant} + ) def firefox_concerts(request): @@ -881,13 +948,13 @@ def firefox_accounts(request): locale = l10n_utils.get_locale(request) # get localized blog post URL for 2019 page - promise_query = ('?utm_source=www.mozilla.org&utm_medium=referral&utm_campaign=accounts-trailhead' - '&utm_content=accounts-value&utm_term=respect-you-deserve') + promise_query = ( + '?utm_source=www.mozilla.org&utm_medium=referral&utm_campaign=accounts-trailhead' + '&utm_content=accounts-value&utm_term=respect-you-deserve' + ) promise_url = PROMISE_BLOG_URLS.get(locale, PROMISE_BLOG_URLS['en-US']) - context = { - 'promise_url': promise_url + promise_query, - } + context = {'promise_url': promise_url + promise_query} if lang_file_is_active('firefox/accounts-2019', locale): template_name = 'firefox/accounts-2019.html' @@ -903,7 +970,7 @@ def election_with_cards(request): locale = l10n_utils.get_locale(request) ctx = { 'page_content_cards': get_page_content_cards('election-en', locale), - 'active_locales': ['de', 'fr', 'en-US'] + 'active_locales': ['de', 'fr', 'en-US'], } if locale == 'de': diff --git a/bedrock/grants/templates/grants/index.html b/bedrock/grants/templates/grants/index.html index 31a39ffb9e..313e203796 100644 --- a/bedrock/grants/templates/grants/index.html +++ b/bedrock/grants/templates/grants/index.html @@ -32,7 +32,7 @@

Filter by

    - {% for key, label in grant_labels.iteritems() %} + {% for key, label in grant_labels.items() %} {% if key == filter %}
  • {{ label }}
  • {% else %} diff --git a/bedrock/grants/tests/test_base.py b/bedrock/grants/tests/test_base.py index a31665efd2..681e56c599 100644 --- a/bedrock/grants/tests/test_base.py +++ b/bedrock/grants/tests/test_base.py @@ -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) diff --git a/bedrock/grants/urls.py b/bedrock/grants/urls.py index 0639750b64..18e187afda 100644 --- a/bedrock/grants/urls.py +++ b/bedrock/grants/urls.py @@ -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 = ( diff --git a/bedrock/grants/views.py b/bedrock/grants/views.py index 02e35f13ff..77bb106c9f 100644 --- a/bedrock/grants/views.py +++ b/bedrock/grants/views.py @@ -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 diff --git a/bedrock/legal/tests/test_forms.py b/bedrock/legal/tests/test_forms.py index 81cf759431..2d221401b7 100644 --- a/bedrock/legal/tests/test_forms.py +++ b/bedrock/legal/tests/test_forms.py @@ -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): """ diff --git a/bedrock/legal/views.py b/bedrock/legal/views.py index 350de54937..7e1d8e4fdf 100644 --- a/bedrock/legal/views.py +++ b/bedrock/legal/views.py @@ -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 ' FRAUD_REPORT_EMAIL_SUBJECT = 'New trademark infringement report: %s; %s' diff --git a/bedrock/legal_docs/tests/test_base.py b/bedrock/legal_docs/tests/test_base.py index 33148e7ae2..cb84cda866 100644 --- a/bedrock/legal_docs/tests/test_base.py +++ b/bedrock/legal_docs/tests/test_base.py @@ -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') diff --git a/bedrock/legal_docs/views.py b/bedrock/legal_docs/views.py index 58ebaa3584..6a9a6133da 100644 --- a/bedrock/legal_docs/views.py +++ b/bedrock/legal_docs/views.py @@ -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: diff --git a/bedrock/mozorg/context_processors.py b/bedrock/mozorg/context_processors.py index 6784f8fa0a..0a7aaf1f97 100644 --- a/bedrock/mozorg/context_processors.py +++ b/bedrock/mozorg/context_processors.py @@ -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 diff --git a/bedrock/mozorg/credits.py b/bedrock/mozorg/credits.py index 47ddfff79d..9015c9a73f 100644 --- a/bedrock/mozorg/credits.py +++ b/bedrock/mozorg/credits.py @@ -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)) diff --git a/bedrock/mozorg/forms.py b/bedrock/mozorg/forms.py index 630b77aa99..91646bdd00 100644 --- a/bedrock/mozorg/forms.py +++ b/bedrock/mozorg/forms.py @@ -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) diff --git a/bedrock/mozorg/forums.py b/bedrock/mozorg/forums.py index d6dbfd6922..c1d87819f4 100644 --- a/bedrock/mozorg/forums.py +++ b/bedrock/mozorg/forums.py @@ -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 diff --git a/bedrock/mozorg/hierarchy.py b/bedrock/mozorg/hierarchy.py index d3e0ee8094..f785f4f4ff 100644 --- a/bedrock/mozorg/hierarchy.py +++ b/bedrock/mozorg/hierarchy.py @@ -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. diff --git a/bedrock/mozorg/management/commands/update_product_details_files.py b/bedrock/mozorg/management/commands/update_product_details_files.py index 20dae6eb51..70ccd6b200 100644 --- a/bedrock/mozorg/management/commands/update_product_details_files.py +++ b/bedrock/mozorg/management/commands/update_product_details_files.py @@ -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 diff --git a/bedrock/mozorg/middleware.py b/bedrock/mozorg/middleware.py index c054442348..8b2b150bf8 100644 --- a/bedrock/mozorg/middleware.py +++ b/bedrock/mozorg/middleware.py @@ -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): diff --git a/bedrock/mozorg/migrations/0001_initial.py b/bedrock/mozorg/migrations/0001_initial.py index 78e7bc05dd..b2a7fd4107 100644 --- a/bedrock/mozorg/migrations/0001_initial.py +++ b/bedrock/mozorg/migrations/0001_initial.py @@ -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 diff --git a/bedrock/mozorg/migrations/0002_blogarticle.py b/bedrock/mozorg/migrations/0002_blogarticle.py index c97327b6e9..f9acc2a3a5 100644 --- a/bedrock/mozorg/migrations/0002_blogarticle.py +++ b/bedrock/mozorg/migrations/0002_blogarticle.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/bedrock/mozorg/migrations/0003_delete_blogarticle.py b/bedrock/mozorg/migrations/0003_delete_blogarticle.py index eb94f0576d..13f193f121 100644 --- a/bedrock/mozorg/migrations/0003_delete_blogarticle.py +++ b/bedrock/mozorg/migrations/0003_delete_blogarticle.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations diff --git a/bedrock/mozorg/models.py b/bedrock/mozorg/models.py index c6abd8303f..69d1a2a5de 100644 --- a/bedrock/mozorg/models.py +++ b/bedrock/mozorg/models.py @@ -50,7 +50,7 @@ class TwitterCache(models.Model): updated = ModificationDateTimeField() objects = TwitterCacheManager() - def __unicode__(self): + def __str__(self): return u'Tweets from @' + self.account diff --git a/bedrock/mozorg/redirects.py b/bedrock/mozorg/redirects.py index c86836217d..b10efe216b 100644 --- a/bedrock/mozorg/redirects.py +++ b/bedrock/mozorg/redirects.py @@ -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.*)', '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\.(?Podt|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.+)$', @@ -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$', + redirect(r'^foundation/documents/(?P[^/]+)\.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/(?Pabout|careers|licensing|moco|mocosc).html$', + redirect(r'^foundation/(?Pabout|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/(?Pindex|mozilla-200.-financial-faq)\.html$', '/foundation/{page}/', re_flags='i'), redirect(r'^foundation/(?P(?: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'), diff --git a/bedrock/mozorg/templates/mozorg/about/forums/forums.html b/bedrock/mozorg/templates/mozorg/about/forums/forums.html index f9182fc34f..c0abd74bfe 100644 --- a/bedrock/mozorg/templates/mozorg/about/forums/forums.html +++ b/bedrock/mozorg/templates/mozorg/about/forums/forums.html @@ -62,7 +62,7 @@ -{% for title, forums_list in forums.ordered.iteritems() %} +{% for title, forums_list in forums.ordered.items() %}
    {{ _('Back to top') }} diff --git a/bedrock/mozorg/templates/mozorg/credits.html b/bedrock/mozorg/templates/mozorg/credits.html index 93282eab06..4c5fb83734 100644 --- a/bedrock/mozorg/templates/mozorg/credits.html +++ b/bedrock/mozorg/templates/mozorg/credits.html @@ -19,7 +19,7 @@

    We would like to thank our contributors, whose efforts over many years have made this software what it is.

    -{% for letter, names in credits.ordered.iteritems() %} +{% for letter, names in credits.ordered.items() %}

    {{ letter }}

    {{ ',\n'.join(names) }} diff --git a/bedrock/mozorg/templatetags/__init__.py b/bedrock/mozorg/templatetags/__init__.py index 018f751047..93e1563a48 100644 --- a/bedrock/mozorg/templatetags/__init__.py +++ b/bedrock/mozorg/templatetags/__init__.py @@ -1,3 +1,2 @@ # flake8: noqa -import misc -import social_widgets +from bedrock.mozorg.templatetags import misc, social_widgets diff --git a/bedrock/mozorg/templatetags/misc.py b/bedrock/mozorg/templatetags/misc.py index 7b173b3b1b..fe4e01b275 100644 --- a/bedrock/mozorg/templatetags/misc.py +++ b/bedrock/mozorg/templatetags/misc.py @@ -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 diff --git a/bedrock/mozorg/templatetags/social_widgets.py b/bedrock/mozorg/templatetags/social_widgets.py index 17eead38a9..b11a3dc7fd 100644 --- a/bedrock/mozorg/templatetags/social_widgets.py +++ b/bedrock/mozorg/templatetags/social_widgets.py @@ -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, ('#%s' % ('%23' + urllib.quote(hash.encode('utf8')), + ' class="hash">#%s' % ('%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, ('@%s' - % (urllib.quote(name.encode('utf8')), name))) + % (urllib.parse.quote(name.encode('utf8')), name))) # URLs for url in entities['urls']: diff --git a/bedrock/mozorg/tests/test_credits.py b/bedrock/mozorg/tests/test_credits.py index 6a686e7103..e09cd3f8da 100644 --- a/bedrock/mozorg/tests/test_credits.py +++ b/bedrock/mozorg/tests/test_credits.py @@ -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'] diff --git a/bedrock/mozorg/tests/test_helper_misc.py b/bedrock/mozorg/tests/test_helper_misc.py index 3c24f85a0e..664e485d35 100644 --- a/bedrock/mozorg/tests/test_helper_misc.py +++ b/bedrock/mozorg/tests/test_helper_misc.py @@ -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 {0}' - val_string = 'Steve' +format_string = 'Hello {0}' +format_markup = Markup(format_string) +val_string = 'Steve' +val_markup = Markup(val_string) + + +@pytest.mark.parametrize('f, v', [ + (format_string, val_string), + (format_string, val_markup), + (format_markup, val_string), + (format_markup, val_markup), +]) +def test_f_markup(f, v): expect = 'Hello <b><em>Steve</em></b>' - - 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_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 + 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 = "" + csrf = '' assert csrf in s diff --git a/bedrock/mozorg/tests/test_helper_social_widgets.py b/bedrock/mozorg/tests/test_helper_social_widgets.py index 861dce787c..9c97ad2541 100644 --- a/bedrock/mozorg/tests/test_helper_social_widgets.py +++ b/bedrock/mozorg/tests/test_helper_social_widgets.py @@ -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') diff --git a/bedrock/mozorg/tests/test_hierarchy.py b/bedrock/mozorg/tests/test_hierarchy.py index 84186f3f8c..293d72724b 100644 --- a/bedrock/mozorg/tests/test_hierarchy.py +++ b/bedrock/mozorg/tests/test_hierarchy.py @@ -26,7 +26,7 @@ class TestPageNode(TestCase): """ child = PageNode('test', path='asdf') PageRoot('test', path='blah', children=[ - PageNode('test', path='whoo', children=[child]) + PageNode('test', path='whoo', children=[child]) ]) assert child.full_path == 'blah/whoo/asdf' @@ -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'] diff --git a/bedrock/mozorg/tests/test_middleware.py b/bedrock/mozorg/tests/test_middleware.py index e34014c9d9..4cbcb4e228 100644 --- a/bedrock/mozorg/tests/test_middleware.py +++ b/bedrock/mozorg/tests/test_middleware.py @@ -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') diff --git a/bedrock/mozorg/tests/test_views.py b/bedrock/mozorg/tests/test_views.py index 8a93e5851e..57e9fce10e 100644 --- a/bedrock/mozorg/tests/test_views.py +++ b/bedrock/mozorg/tests/test_views.py @@ -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): diff --git a/bedrock/mozorg/tests/urls.py b/bedrock/mozorg/tests/urls.py index 826772606c..3f227aaa5f 100644 --- a/bedrock/mozorg/tests/urls.py +++ b/bedrock/mozorg/tests/urls.py @@ -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'), -) +] diff --git a/bedrock/mozorg/urls.py b/bedrock/mozorg/urls.py index 0b1c6ffc18..19c7255ceb 100644 --- a/bedrock/mozorg/urls.py +++ b/bedrock/mozorg/urls.py @@ -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'), diff --git a/bedrock/mozorg/util.py b/bedrock/mozorg/util.py index 26885ff40e..15d7cf659c 100644 --- a/bedrock/mozorg/util.py +++ b/bedrock/mozorg/util.py @@ -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 diff --git a/bedrock/mozorg/views.py b/bedrock/mozorg/views.py index 2318b703ee..ec6c6486bc 100644 --- a/bedrock/mozorg/views.py +++ b/bedrock/mozorg/views.py @@ -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 diff --git a/bedrock/newsletter/forms.py b/bedrock/newsletter/forms.py index 60067eaebd..48126f695f 100644 --- a/bedrock/newsletter/forms.py +++ b/bedrock/newsletter/forms.py @@ -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) diff --git a/bedrock/newsletter/management/commands/update_newsletter_data.py b/bedrock/newsletter/management/commands/update_newsletter_data.py index ce403e9c1a..447531f48d 100644 --- a/bedrock/newsletter/management/commands/update_newsletter_data.py +++ b/bedrock/newsletter/management/commands/update_newsletter_data.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from django.core.management.base import BaseCommand, CommandError import basket diff --git a/bedrock/newsletter/migrations/0001_initial.py b/bedrock/newsletter/migrations/0001_initial.py index dfa2b76f78..975f27b932 100644 --- a/bedrock/newsletter/migrations/0001_initial.py +++ b/bedrock/newsletter/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django_extensions.db.fields.json diff --git a/bedrock/newsletter/models.py b/bedrock/newsletter/models.py index 6afa2f55da..4a15fe9c89 100644 --- a/bedrock/newsletter/models.py +++ b/bedrock/newsletter/models.py @@ -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 diff --git a/bedrock/newsletter/redirects.py b/bedrock/newsletter/redirects.py index fb9351bfcc..4114327ac1 100644 --- a/bedrock/newsletter/redirects.py +++ b/bedrock/newsletter/redirects.py @@ -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'), ) diff --git a/bedrock/newsletter/tests/test_views.py b/bedrock/newsletter/tests/test_views.py index 7a11d25251..e8c07b7906 100644 --- a/bedrock/newsletter/tests/test_views.py +++ b/bedrock/newsletter/tests/test_views.py @@ -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.""" diff --git a/bedrock/newsletter/utils.py b/bedrock/newsletter/utils.py index b80d08a627..57e7d1c46b 100644 --- a/bedrock/newsletter/utils.py +++ b/bedrock/newsletter/utils.py @@ -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] diff --git a/bedrock/newsletter/views.py b/bedrock/newsletter/views.py index 3cf3fa311b..8227c75df3 100644 --- a/bedrock/newsletter/views.py +++ b/bedrock/newsletter/views.py @@ -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: diff --git a/bedrock/pocketfeed/api.py b/bedrock/pocketfeed/api.py index 8f7c537900..98a40c8b93 100644 --- a/bedrock/pocketfeed/api.py +++ b/bedrock/pocketfeed/api.py @@ -1,5 +1,3 @@ -from __future__ import print_function, unicode_literals - import datetime import re import requests diff --git a/bedrock/pocketfeed/management/commands/update_pocketfeed.py b/bedrock/pocketfeed/management/commands/update_pocketfeed.py index 6dd19ae74e..8f6d0eae8d 100644 --- a/bedrock/pocketfeed/management/commands/update_pocketfeed.py +++ b/bedrock/pocketfeed/management/commands/update_pocketfeed.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from django.conf import settings from django.core.management.base import BaseCommand, CommandError diff --git a/bedrock/pocketfeed/migrations/0001_initial.py b/bedrock/pocketfeed/migrations/0001_initial.py index 9f5ba18377..9e1b19df14 100644 --- a/bedrock/pocketfeed/migrations/0001_initial.py +++ b/bedrock/pocketfeed/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/bedrock/pocketfeed/migrations/0002_auto_20180723_0805.py b/bedrock/pocketfeed/migrations/0002_auto_20180723_0805.py index 9bbfdd1640..1e867b5b59 100644 --- a/bedrock/pocketfeed/migrations/0002_auto_20180723_0805.py +++ b/bedrock/pocketfeed/migrations/0002_auto_20180723_0805.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/bedrock/pocketfeed/models.py b/bedrock/pocketfeed/models.py index 6a2f4831da..1886cadeca 100644 --- a/bedrock/pocketfeed/models.py +++ b/bedrock/pocketfeed/models.py @@ -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 diff --git a/bedrock/pocketfeed/tests/test_api.py b/bedrock/pocketfeed/tests/test_api.py index 9d8967985c..231f4d6419 100644 --- a/bedrock/pocketfeed/tests/test_api.py +++ b/bedrock/pocketfeed/tests/test_api.py @@ -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') diff --git a/bedrock/press/redirects.py b/bedrock/press/redirects.py index 1642489412..5681577a92 100644 --- a/bedrock/press/redirects.py +++ b/bedrock/press/redirects.py @@ -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-' diff --git a/bedrock/press/tests/test_base.py b/bedrock/press/tests/test_base.py index 6f7bad54bc..55f9f1a9b7 100644 --- a/bedrock/press/tests/test_base.py +++ b/bedrock/press/tests/test_base.py @@ -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): """ diff --git a/bedrock/redirects/middleware.py b/bedrock/redirects/middleware.py index 221f7da15c..011245635a 100644 --- a/bedrock/redirects/middleware.py +++ b/bedrock/redirects/middleware.py @@ -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) diff --git a/bedrock/redirects/redirects.py b/bedrock/redirects/redirects.py index 8891c00add..df23df7117 100644 --- a/bedrock/redirects/redirects.py +++ b/bedrock/redirects/redirects.py @@ -2,2011 +2,2011 @@ from .util import gone, redirect redirectpatterns = ( # from org-urls-410.txt - gone('^catalog/end-user/release$'), - gone('^help-wanted\.html$'), - gone('^projects/ui/accessibility/access-radar\.html$'), - gone('^projects/ui/accessibility/header\.html$'), - gone('^projects/ui/accessibility/unix/to-do\.html$'), - gone('^projects/user-docs/$'), - gone('^projects/user-docs/local/browserhelp/browserbanner\.html$'), - gone('^projects/user-docs/local/browserhelp/browsercont\.html$'), - gone('^projects/user-docs/local/browserhelp/browsertoc\.html$'), - gone('^projects/user-docs/local/browserhelp/browsertop\.html$'), - gone('^projects/user-docs/local/$'), - gone('^projects/user-docs/local/mailhelp/mailbanner\.html$'), - gone('^projects/user-docs/local/mailhelp/mailcont\.html$'), - gone('^projects/user-docs/local/mailhelp/mailtoc\.html$'), - gone('^projects/user-docs/local/mailhelp/mailtop\.html$'), - gone('^projects/user-docs/local/troubleshoot/troublebanner\.html$'), - gone('^projects/user-docs/local/troubleshoot/troublecont\.html$'), - gone('^projects/user-docs/local/troubleshoot/troubletoc\.html$'), - gone('^projects/user-docs/local/troubleshoot/troubletop\.html$'), - gone('^projects/user-docs/served/custhelp/custbanner\.html$'), - gone('^projects/user-docs/served/custhelp/custcont\.html$'), - gone('^projects/user-docs/served/custhelp/custtoc\.html$'), - gone('^projects/user-docs/served/custhelp/custtop\.html$'), - gone('^projects/user-docs/served/$'), - gone('^projects/user-docs/served/shophelp/shopbanner\.html$'), - gone('^projects/user-docs/served/shophelp/shopcont\.html$'), - gone('^projects/user-docs/served/shophelp/shoptoc\.html$'), - gone('^projects/user-docs/served/shophelp/shoptop\.html$'), - gone('^projects/user-docs/served/updatehelp/updatebanner\.html$'), - gone('^projects/user-docs/served/updatehelp/updatecont\.html$'), - gone('^projects/user-docs/served/updatehelp/updatetoc\.html$'), - gone('^projects/user-docs/served/updatehelp/updatetop\.html$'), - gone('^projects/user-docs/served/whatsnew/newbanner\.html$'), - gone('^projects/user-docs/served/whatsnew/newcont\.html$'), - gone('^projects/user-docs/served/whatsnew/newtoc\.html$'), - gone('^projects/user-docs/served/whatsnew/newtop\.html$'), - gone('^xpfe/xptoolkit/xbl\.html$'), + gone(r'^catalog/end-user/release$'), + gone(r'^help-wanted\.html$'), + gone(r'^projects/ui/accessibility/access-radar\.html$'), + gone(r'^projects/ui/accessibility/header\.html$'), + gone(r'^projects/ui/accessibility/unix/to-do\.html$'), + gone(r'^projects/user-docs/$'), + gone(r'^projects/user-docs/local/browserhelp/browserbanner\.html$'), + gone(r'^projects/user-docs/local/browserhelp/browsercont\.html$'), + gone(r'^projects/user-docs/local/browserhelp/browsertoc\.html$'), + gone(r'^projects/user-docs/local/browserhelp/browsertop\.html$'), + gone(r'^projects/user-docs/local/$'), + gone(r'^projects/user-docs/local/mailhelp/mailbanner\.html$'), + gone(r'^projects/user-docs/local/mailhelp/mailcont\.html$'), + gone(r'^projects/user-docs/local/mailhelp/mailtoc\.html$'), + gone(r'^projects/user-docs/local/mailhelp/mailtop\.html$'), + gone(r'^projects/user-docs/local/troubleshoot/troublebanner\.html$'), + gone(r'^projects/user-docs/local/troubleshoot/troublecont\.html$'), + gone(r'^projects/user-docs/local/troubleshoot/troubletoc\.html$'), + gone(r'^projects/user-docs/local/troubleshoot/troubletop\.html$'), + gone(r'^projects/user-docs/served/custhelp/custbanner\.html$'), + gone(r'^projects/user-docs/served/custhelp/custcont\.html$'), + gone(r'^projects/user-docs/served/custhelp/custtoc\.html$'), + gone(r'^projects/user-docs/served/custhelp/custtop\.html$'), + gone(r'^projects/user-docs/served/$'), + gone(r'^projects/user-docs/served/shophelp/shopbanner\.html$'), + gone(r'^projects/user-docs/served/shophelp/shopcont\.html$'), + gone(r'^projects/user-docs/served/shophelp/shoptoc\.html$'), + gone(r'^projects/user-docs/served/shophelp/shoptop\.html$'), + gone(r'^projects/user-docs/served/updatehelp/updatebanner\.html$'), + gone(r'^projects/user-docs/served/updatehelp/updatecont\.html$'), + gone(r'^projects/user-docs/served/updatehelp/updatetoc\.html$'), + gone(r'^projects/user-docs/served/updatehelp/updatetop\.html$'), + gone(r'^projects/user-docs/served/whatsnew/newbanner\.html$'), + gone(r'^projects/user-docs/served/whatsnew/newcont\.html$'), + gone(r'^projects/user-docs/served/whatsnew/newtoc\.html$'), + gone(r'^projects/user-docs/served/whatsnew/newtop\.html$'), + gone(r'^xpfe/xptoolkit/xbl\.html$'), # from org-urls-301.txt - redirect('^projects/firefox/build\.html$', + redirect(r'^projects/firefox/build\.html$', 'http://developer.mozilla.org/en/Build_Documentation'), - redirect('^projects/firefox/extensions/index\.html$', + redirect(r'^projects/firefox/extensions/index\.html$', 'http://developer.mozilla.org/en/Extensions'), - redirect('^projects/firefox/extensions/web-api\.html$', + redirect(r'^projects/firefox/extensions/web-api\.html$', 'http://developer.mozilla.org/en/Installing_Extensions_and_Themes_From_Web_Pages'), - redirect('^projects/firefox/extensions/packaging/extensions\.html$', + redirect(r'^projects/firefox/extensions/packaging/extensions\.html$', 'http://developer.mozilla.org/en/Extension_Packaging'), - redirect('^projects/firefox/extensions/packaging/themes\.html$', + redirect(r'^projects/firefox/extensions/packaging/themes\.html$', 'http://developer.mozilla.org/en/Theme_Packaging'), - redirect('^projects/firefox/review\.html$', 'https://wiki.mozilla.org/Firefox/Code_Review'), - redirect('^projects/toolkit/review\.html$', 'https://wiki.mozilla.org/Toolkit/Code_Review'), - redirect('^about\.html$', '/about/roles'), - redirect('^about/etiquette\.html$', '/about/forums/etiquette.html'), - redirect('^about/free\.html$', '/causes/free.html'), - redirect('^about/manifesto$', '/about/manifesto.html'), - redirect('^about/owners\.html$', 'https://wiki.mozilla.org/Modules'), - redirect('^airmozilla/?$', 'http://air.mozilla.org/'), - redirect('^binaries\.html$', '/projects/'), - redirect('^blue-sky\.html$', '/blue-sky/'), - redirect('^bonsai\.html$', 'http://developer.mozilla.org/en/Bonsai'), - redirect('^bugs\.html$', '/bugs/'), - redirect('^bugs/bug-reporting\.html$', + redirect(r'^projects/firefox/review\.html$', 'https://wiki.mozilla.org/Firefox/Code_Review'), + redirect(r'^projects/toolkit/review\.html$', 'https://wiki.mozilla.org/Toolkit/Code_Review'), + redirect(r'^about\.html$', '/about/roles'), + redirect(r'^about/etiquette\.html$', '/about/forums/etiquette.html'), + redirect(r'^about/free\.html$', '/causes/free.html'), + redirect(r'^about/manifesto$', '/about/manifesto.html'), + redirect(r'^about/owners\.html$', 'https://wiki.mozilla.org/Modules'), + redirect(r'^airmozilla/?$', 'http://air.mozilla.org/'), + redirect(r'^binaries\.html$', '/projects/'), + redirect(r'^blue-sky\.html$', '/blue-sky/'), + redirect(r'^bonsai\.html$', 'http://developer.mozilla.org/en/Bonsai'), + redirect(r'^bugs\.html$', '/bugs/'), + redirect(r'^bugs/bug-reporting\.html$', '/quality/bug-writing-guidelines.html'), - redirect('^bugs/changes\.html$', 'http://www.bugzilla.org/status/changes.html'), - redirect('^bugs/query\.html$', '/quality/bug-writing-guidelines.html'), - redirect('^bugs/report\.html$', 'http://developer.mozilla.org/en/Bug_writing_guidelines'), - redirect('^bugs/source\.html$', 'http://www.bugzilla.org/'), - redirect('^bugs/text-searching\.html$', + redirect(r'^bugs/changes\.html$', 'http://www.bugzilla.org/status/changes.html'), + redirect(r'^bugs/query\.html$', '/quality/bug-writing-guidelines.html'), + redirect(r'^bugs/report\.html$', 'http://developer.mozilla.org/en/Bug_writing_guidelines'), + redirect(r'^bugs/source\.html$', 'http://www.bugzilla.org/'), + redirect(r'^bugs/text-searching\.html$', '/quality/bug-writing-guidelines.html'), - redirect('^build/build-system\.html$', + redirect(r'^build/build-system\.html$', 'http://developer.mozilla.org/en/How_mozilla%27s_build_system_works'), - redirect('^build/configure-build\.html$', + redirect(r'^build/configure-build\.html$', 'http://developer.mozilla.org/en/Configuring_Build_Options'), - redirect('^build/cross-compiling\.html$', + redirect(r'^build/cross-compiling\.html$', 'http://developer.mozilla.org/en/Cross-Compiling_Mozilla'), - redirect('^build/cvs-tag\.html$', 'http://developer.mozilla.org/en/Creating_a_Release_Tag'), - redirect('^build/distribution\.html$', + redirect(r'^build/cvs-tag\.html$', 'http://developer.mozilla.org/en/Creating_a_Release_Tag'), + redirect(r'^build/distribution\.html$', 'http://developer.mozilla.org/en/Building_a_Mozilla_Distribution'), - redirect('^build/faq\.html$', 'http://developer.mozilla.org/en/Mozilla_Build_FAQ'), - redirect('^build/$', 'http://developer.mozilla.org/en/Build_Documentation'), - redirect('^build/jar-packaging\.html$', 'http://developer.mozilla.org/en/JAR_Packaging'), - redirect('^build/mac-build-system\.html$', + redirect(r'^build/faq\.html$', 'http://developer.mozilla.org/en/Mozilla_Build_FAQ'), + redirect(r'^build/$', 'http://developer.mozilla.org/en/Build_Documentation'), + redirect(r'^build/jar-packaging\.html$', 'http://developer.mozilla.org/en/JAR_Packaging'), + redirect(r'^build/mac-build-system\.html$', 'http://developer.mozilla.org/en/Mac_OS_X_Build_Prerequisites'), - redirect('^build/mac\.html$', 'http://developer.mozilla.org/en/Mac_OS_X_Build_Prerequisites'), - redirect('^build/make-build\.html$', 'http://developer.mozilla.org/en/Build_and_Install'), - redirect('^build/making-additions\.html$', + redirect(r'^build/mac\.html$', 'http://developer.mozilla.org/en/Mac_OS_X_Build_Prerequisites'), + redirect(r'^build/make-build\.html$', 'http://developer.mozilla.org/en/Build_and_Install'), + redirect(r'^build/making-additions\.html$', 'http://developer.mozilla.org/en/Adding_Files_to_the_Build'), - redirect('^build/release-build-notes\.html$', + redirect(r'^build/release-build-notes\.html$', 'http://developer.mozilla.org/en/Mozilla_Release_Build_Notes'), - redirect('^build/release-checklist\.html$', + redirect(r'^build/release-checklist\.html$', 'http://developer.mozilla.org/en/Mozilla_Release_Checklist'), - redirect('^build/revised-user-agent-strings\.html$', + redirect(r'^build/revised-user-agent-strings\.html$', 'http://developer.mozilla.org/en/User_Agent_Strings_Reference'), - redirect('^build/sheriff-schedule\.html$', 'http://wiki.mozilla.org/Sheriff_Schedule'), - redirect('^build/sheriff\.html$', 'http://wiki.mozilla.org/Sheriff_Duty'), - redirect('^build/sheriff/sheriff-schedule\.html$', 'http://wiki.mozilla.org/Sheriff_Schedule'), - redirect('^build/unix-cheatsheet\.html$', + redirect(r'^build/sheriff-schedule\.html$', 'http://wiki.mozilla.org/Sheriff_Schedule'), + redirect(r'^build/sheriff\.html$', 'http://wiki.mozilla.org/Sheriff_Duty'), + redirect(r'^build/sheriff/sheriff-schedule\.html$', 'http://wiki.mozilla.org/Sheriff_Schedule'), + redirect(r'^build/unix-cheatsheet\.html$', 'http://developer.mozilla.org/en/Linux_Cheat_Sheet_for_Mac_and_Windows_Programmers'), - redirect('^build/unix-details\.html$', + redirect(r'^build/unix-details\.html$', 'http://developer.mozilla.org/En/Unix_Detailed_Build_Instructions'), - redirect('^build/unix\.html$', 'http://developer.mozilla.org/en/Linux_Build_Prerequisites'), - redirect('^build/win32-debugging-faq\.html$', + redirect(r'^build/unix\.html$', 'http://developer.mozilla.org/en/Linux_Build_Prerequisites'), + redirect(r'^build/win32-debugging-faq\.html$', 'http://developer.mozilla.org/en/Debugging_Mozilla_on_Windows_FAQ'), - redirect('^build/win32\.html$', 'http://developer.mozilla.org/en/Windows_Build_Prerequisites'), - redirect('^build/windbgdlg\.html$', + redirect(r'^build/win32\.html$', 'http://developer.mozilla.org/en/Windows_Build_Prerequisites'), + redirect(r'^build/windbgdlg\.html$', 'http://developer.mozilla.org/en/' 'Automatically_Handle_Failed_Asserts_in_Debug_Builds'), - redirect('^camino$', 'http://caminobrowser.org/'), - redirect('^catalog/development/compiling/$', + redirect(r'^camino$', 'http://caminobrowser.org/'), + redirect(r'^catalog/development/compiling/$', 'http://developer.mozilla.org/en/Build_Documentation'), - redirect('^catalog/development/compiling/cvs-sourcecode\.html$', + redirect(r'^catalog/development/compiling/cvs-sourcecode\.html$', '/cvs.html'), - redirect('^catalog/development/tools/cvs-tarball\.html$', + redirect(r'^catalog/development/tools/cvs-tarball\.html$', 'http://developer.mozilla.org/en/Mozilla_Source_Code_(CVS)'), - redirect('^catalog/development/website/cvs-website\.html$', + redirect(r'^catalog/development/website/cvs-website\.html$', '/contribute/writing/cvs'), - redirect('^catalog/end-user/customizing/briefprefs\.html$', + redirect(r'^catalog/end-user/customizing/briefprefs\.html$', 'http://developer.mozilla.org/En/A_Brief_Guide_to_Mozilla_Preferences'), - redirect('^causes/access\.html$', '/about/mission.html'), - redirect('^causes/accessibility\.html$', '/causes/access.html'), - redirect('^causes/better\.html$', '/about/mission.html'), - redirect('^causes/education\.html$', '/about/mission.html'), - redirect('^causes/free\.html$', '/about/mission.html'), - redirect('^causes/openweb\.html$', '/about/mission.html'), - redirect('^causes/security\.html$', '/about/mission.html'), - redirect('^classic/nsprdesc\.html$', 'http://developer.mozilla.org/en/About_NSPR'), - redirect('^contribute/get-involved\.html$', '/contribute/'), - redirect('^contribute/writing/cvs\.html$', + redirect(r'^causes/access\.html$', '/about/mission.html'), + redirect(r'^causes/accessibility\.html$', '/causes/access.html'), + redirect(r'^causes/better\.html$', '/about/mission.html'), + redirect(r'^causes/education\.html$', '/about/mission.html'), + redirect(r'^causes/free\.html$', '/about/mission.html'), + redirect(r'^causes/openweb\.html$', '/about/mission.html'), + redirect(r'^causes/security\.html$', '/about/mission.html'), + redirect(r'^classic/nsprdesc\.html$', 'http://developer.mozilla.org/en/About_NSPR'), + redirect(r'^contribute/get-involved\.html$', '/contribute/'), + redirect(r'^contribute/writing/cvs\.html$', 'https://wiki.mozilla.org/Mozilla.org:How_to_Work_with_Site'), - redirect('^contribute/writing/how-to\.html$', + redirect(r'^contribute/writing/how-to\.html$', 'https://developer.mozilla.org/Project:en/How_to_document_Mozilla'), - redirect('^contribute/writing/process\.html$', + redirect(r'^contribute/writing/process\.html$', 'https://developer.mozilla.org/Project:en/Getting_started'), - redirect('^crypto-faq\.html$', 'http://developer.mozilla.org/en/Mozilla_Crypto_FAQ'), - redirect('^cvs-ssh-faq\.html$', 'http://developer.mozilla.org/en/Using_SSH_to_connect_to_CVS'), - redirect('^cvs\.html$', 'http://developer.mozilla.org/en/Mozilla_Source_Code_Via_CVS'), - redirect('^dejanews\.gif$', '/images/dejanews.gif'), - redirect('^docs\.html$', '/docs/'), - redirect('^docs/command-line-args\.html$', + redirect(r'^crypto-faq\.html$', 'http://developer.mozilla.org/en/Mozilla_Crypto_FAQ'), + redirect(r'^cvs-ssh-faq\.html$', 'http://developer.mozilla.org/en/Using_SSH_to_connect_to_CVS'), + redirect(r'^cvs\.html$', 'http://developer.mozilla.org/en/Mozilla_Source_Code_Via_CVS'), + redirect(r'^dejanews\.gif$', '/images/dejanews.gif'), + redirect(r'^docs\.html$', '/docs/'), + redirect(r'^docs/command-line-args\.html$', 'http://developer.mozilla.org/en/Command_Line_Options'), - redirect('^docs/contribute\.html$', '/contribute/writing/process'), - redirect('^docs/docshell/mozilla_downloads_path2\.png$', + redirect(r'^docs/contribute\.html$', '/contribute/writing/process'), + redirect(r'^docs/docshell/mozilla_downloads_path2\.png$', 'https://developer.mozilla.org/@api/deki/files/279/=Mozilla_downloads_path2.png'), - redirect('^docs/docshell/mozilla_downloads\.html$', + redirect(r'^docs/docshell/mozilla_downloads\.html$', 'http://developer.mozilla.org/en/Overview_of_how_downloads_work'), - redirect('^docs/docshell/mozilla_downloads\.png$', + redirect(r'^docs/docshell/mozilla_downloads\.png$', 'https://developer.mozilla.org/@api/deki/files/278/=Mozilla_downloads.png'), - redirect('^docs/docshell/uri-load-start\.html$', + redirect(r'^docs/docshell/uri-load-start\.html$', 'http://developer.mozilla.org/en/' 'Document_Loading_-_From_Load_Start_to_Finding_a_Handler'), - redirect('^docs/dom/about/$', + redirect(r'^docs/dom/about/$', 'http://developer.mozilla.org/en/About_the_Document_Object_Model'), - redirect('^docs/dom/dom-talk/$', + redirect(r'^docs/dom/dom-talk/$', 'http://developer.mozilla.org/en/DOM_Implementation_and_Scriptability'), - redirect('^docs/dom/domref/clientHeight\.html$', + redirect(r'^docs/dom/domref/clientHeight\.html$', 'http://developer.mozilla.org/en/DOM:element.clientHeight'), - redirect('^docs/dom/domref/clientWidth\.html$', + redirect(r'^docs/dom/domref/clientWidth\.html$', 'http://developer.mozilla.org/en/DOM:element.clientWidth'), - redirect('^docs/dom/domref/dom_doc_ref\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref\.html$', 'http://developer.mozilla.org/en/DOM:document'), - redirect('^docs/dom/domref/dom_doc_ref2\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref2\.html$', 'http://developer.mozilla.org/en/DOM:element.attributes'), - redirect('^docs/dom/domref/dom_doc_ref3\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref3\.html$', 'http://developer.mozilla.org/en/DOM:document.alinkColor'), - redirect('^docs/dom/domref/dom_doc_ref4\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref4\.html$', 'http://developer.mozilla.org/en/DOM:document.anchors'), - redirect('^docs/dom/domref/dom_doc_ref5\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref5\.html$', 'http://developer.mozilla.org/en/DOM:document.applets'), - redirect('^docs/dom/domref/dom_doc_ref6\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref6\.html$', 'http://developer.mozilla.org/en/DOM:document.bgColor'), - redirect('^docs/dom/domref/dom_doc_ref7\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref7\.html$', 'http://developer.mozilla.org/en/DOM:document.body'), - redirect('^docs/dom/domref/dom_doc_ref8\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref8\.html$', 'http://developer.mozilla.org/en/DOM:document.characterSet'), - redirect('^docs/dom/domref/dom_doc_ref9\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref9\.html$', 'http://developer.mozilla.org/en/DOM:element.childNodes'), - redirect('^docs/dom/domref/dom_doc_ref10\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref10\.html$', 'http://developer.mozilla.org/en/DOM:document.compatMode'), - redirect('^docs/dom/domref/dom_doc_ref11\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref11\.html$', 'http://developer.mozilla.org/en/DOM:document.cookie'), - redirect('^docs/dom/domref/dom_doc_ref12\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref12\.html$', 'http://developer.mozilla.org/en/DOM:document.contentWindow'), - redirect('^docs/dom/domref/dom_doc_ref13\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref13\.html$', 'http://developer.mozilla.org/en/DOM:document.doctype'), - redirect('^docs/dom/domref/dom_doc_ref14\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref14\.html$', 'http://developer.mozilla.org/en/DOM:document.documentElement'), - redirect('^docs/dom/domref/dom_doc_ref15\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref15\.html$', 'http://developer.mozilla.org/en/DOM:document.domain'), - redirect('^docs/dom/domref/dom_doc_ref16\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref16\.html$', 'http://developer.mozilla.org/en/DOM:document.embeds'), - redirect('^docs/dom/domref/dom_doc_ref17\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref17\.html$', 'http://developer.mozilla.org/en/DOM:document.fgColor'), - redirect('^docs/dom/domref/dom_doc_ref18\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref18\.html$', 'http://developer.mozilla.org/en/DOM:element.firstChild'), - redirect('^docs/dom/domref/dom_doc_ref19\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref19\.html$', 'http://developer.mozilla.org/en/DOM:document.forms'), - redirect('^docs/dom/domref/dom_doc_ref20\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref20\.html$', 'http://developer.mozilla.org/en/DOM:document.height'), - redirect('^docs/dom/domref/dom_doc_ref21\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref21\.html$', 'http://developer.mozilla.org/en/DOM:document.images'), - redirect('^docs/dom/domref/dom_doc_ref22\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref22\.html$', 'http://developer.mozilla.org/en/DOM:document.implementation'), - redirect('^docs/dom/domref/dom_doc_ref23\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref23\.html$', 'http://developer.mozilla.org/en/DOM:document.lastModified'), - redirect('^docs/dom/domref/dom_doc_ref24\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref24\.html$', 'http://developer.mozilla.org/en/DOM:document.linkColor'), - redirect('^docs/dom/domref/dom_doc_ref25\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref25\.html$', 'http://developer.mozilla.org/en/DOM:document.links'), - redirect('^docs/dom/domref/dom_doc_ref26\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref26\.html$', 'http://developer.mozilla.org/en/DOM:document.location'), - redirect('^docs/dom/domref/dom_doc_ref27\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref27\.html$', 'http://developer.mozilla.org/en/DOM:element.namespaceURI'), - redirect('^docs/dom/domref/dom_doc_ref28\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref28\.html$', 'http://developer.mozilla.org/en/DOM:element.nextSibling'), - redirect('^docs/dom/domref/dom_doc_ref29\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref29\.html$', 'http://developer.mozilla.org/en/DOM:element.nodeName'), - redirect('^docs/dom/domref/dom_doc_ref30\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref30\.html$', 'http://developer.mozilla.org/en/DOM:element.nodeType'), - redirect('^docs/dom/domref/dom_doc_ref31\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref31\.html$', 'http://developer.mozilla.org/en/DOM:element.nodeValue'), - redirect('^docs/dom/domref/dom_doc_ref32\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref32\.html$', 'http://developer.mozilla.org/en/DOM:element.ownerDocument'), - redirect('^docs/dom/domref/dom_doc_ref33\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref33\.html$', 'http://developer.mozilla.org/en/DOM:element.parentNode'), - redirect('^docs/dom/domref/dom_doc_ref34\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref34\.html$', 'http://developer.mozilla.org/en/DOM:document.plugins'), - redirect('^docs/dom/domref/dom_doc_ref35\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref35\.html$', 'http://developer.mozilla.org/en/DOM:element.previousSibling'), - redirect('^docs/dom/domref/dom_doc_ref36\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref36\.html$', 'http://developer.mozilla.org/en/DOM:document.referrer'), - redirect('^docs/dom/domref/dom_doc_ref37\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref37\.html$', 'http://developer.mozilla.org/en/DOM:document.styleSheets'), - redirect('^docs/dom/domref/dom_doc_ref38\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref38\.html$', 'http://developer.mozilla.org/en/DOM:document.title'), - redirect('^docs/dom/domref/dom_doc_ref39\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref39\.html$', 'http://developer.mozilla.org/en/DOM:document.URL'), - redirect('^docs/dom/domref/dom_doc_ref40\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref40\.html$', 'http://developer.mozilla.org/en/DOM:document.vlinkColor'), - redirect('^docs/dom/domref/dom_doc_ref41\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref41\.html$', 'http://developer.mozilla.org/en/DOM:document.width'), - redirect('^docs/dom/domref/dom_doc_ref42\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref42\.html$', 'http://developer.mozilla.org/en/DOM:document.clear'), - redirect('^docs/dom/domref/dom_doc_ref43\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref43\.html$', 'http://developer.mozilla.org/en/DOM:document.close'), - redirect('^docs/dom/domref/dom_doc_ref44\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref44\.html$', 'http://developer.mozilla.org/en/DOM:document.createAttribute'), - redirect('^docs/dom/domref/dom_doc_ref45\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref45\.html$', 'http://developer.mozilla.org/en/DOM:document.createDocumentFragment'), - redirect('^docs/dom/domref/dom_doc_ref46\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref46\.html$', 'http://developer.mozilla.org/en/DOM:document.createElement'), - redirect('^docs/dom/domref/dom_doc_ref47\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref47\.html$', 'http://developer.mozilla.org/en/DOM:document.createTextNode'), - redirect('^docs/dom/domref/dom_doc_ref48\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref48\.html$', 'http://developer.mozilla.org/en/DOM:document.getElementById'), - redirect('^docs/dom/domref/dom_doc_ref49\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref49\.html$', 'http://developer.mozilla.org/en/DOM:document.getElementsByName'), - redirect('^docs/dom/domref/dom_doc_ref50\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref50\.html$', 'http://developer.mozilla.org/en/DOM:element.getElementsByTagName'), - redirect('^docs/dom/domref/dom_doc_ref51\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref51\.html$', 'http://developer.mozilla.org/en/DOM:document.open'), - redirect('^docs/dom/domref/dom_doc_ref52\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref52\.html$', 'http://developer.mozilla.org/en/DOM:document.write'), - redirect('^docs/dom/domref/dom_doc_ref53\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref53\.html$', 'http://developer.mozilla.org/en/DOM:document.writeln'), - redirect('^docs/dom/domref/dom_doc_ref54\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref54\.html$', 'http://developer.mozilla.org/en/DOM:element.onblur'), - redirect('^docs/dom/domref/dom_doc_ref55\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref55\.html$', 'http://developer.mozilla.org/en/DOM:element.onclick'), - redirect('^docs/dom/domref/dom_doc_ref56\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref56\.html$', 'http://developer.mozilla.org/en/DOM:element.ondblclick'), - redirect('^docs/dom/domref/dom_doc_ref57\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref57\.html$', 'http://developer.mozilla.org/en/DOM:element.onfocus'), - redirect('^docs/dom/domref/dom_doc_ref58\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref58\.html$', 'http://developer.mozilla.org/en/DOM:element.onkeydown'), - redirect('^docs/dom/domref/dom_doc_ref59\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref59\.html$', 'http://developer.mozilla.org/en/DOM:element.onkeypress'), - redirect('^docs/dom/domref/dom_doc_ref60\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref60\.html$', 'http://developer.mozilla.org/en/DOM:element.onkeyup'), - redirect('^docs/dom/domref/dom_doc_ref61\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref61\.html$', 'http://developer.mozilla.org/en/DOM:element.onmousedown'), - redirect('^docs/dom/domref/dom_doc_ref62\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref62\.html$', 'http://developer.mozilla.org/en/DOM:element.onmousemove'), - redirect('^docs/dom/domref/dom_doc_ref63\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref63\.html$', 'http://developer.mozilla.org/en/DOM:element.onmouseout'), - redirect('^docs/dom/domref/dom_doc_ref64\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref64\.html$', 'http://developer.mozilla.org/en/DOM:element.onmouseover'), - redirect('^docs/dom/domref/dom_doc_ref65\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref65\.html$', 'http://developer.mozilla.org/en/DOM:element.onmouseup'), - redirect('^docs/dom/domref/dom_doc_ref66\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref66\.html$', 'http://developer.mozilla.org/en/DOM:element.onresize'), - redirect('^docs/dom/domref/dom_doc_ref67\.html$', + redirect(r'^docs/dom/domref/dom_doc_ref67\.html$', 'http://developer.mozilla.org/en/DOM:element.onresize'), - redirect('^docs/dom/domref/dom_el_ref\.html$', 'http://developer.mozilla.org/en/DOM:element'), - redirect('^docs/dom/domref/dom_el_ref2\.html$', + redirect(r'^docs/dom/domref/dom_el_ref\.html$', 'http://developer.mozilla.org/en/DOM:element'), + redirect(r'^docs/dom/domref/dom_el_ref2\.html$', 'http://developer.mozilla.org/en/DOM:element.attributes'), - redirect('^docs/dom/domref/dom_el_ref3\.html$', + redirect(r'^docs/dom/domref/dom_el_ref3\.html$', 'http://developer.mozilla.org/en/DOM:element.childNodes'), - redirect('^docs/dom/domref/dom_el_ref4\.html$', + redirect(r'^docs/dom/domref/dom_el_ref4\.html$', 'http://developer.mozilla.org/en/DOM:element.className'), - redirect('^docs/dom/domref/dom_el_ref5\.html$', + redirect(r'^docs/dom/domref/dom_el_ref5\.html$', 'http://developer.mozilla.org/en/DOM:element.dir'), - redirect('^docs/dom/domref/dom_el_ref6\.html$', + redirect(r'^docs/dom/domref/dom_el_ref6\.html$', 'http://developer.mozilla.org/en/DOM:element.firstChild'), - redirect('^docs/dom/domref/dom_el_ref7\.html$', + redirect(r'^docs/dom/domref/dom_el_ref7\.html$', 'http://developer.mozilla.org/en/DOM:element.id'), - redirect('^docs/dom/domref/dom_el_ref8\.html$', + redirect(r'^docs/dom/domref/dom_el_ref8\.html$', 'http://developer.mozilla.org/en/DOM:element.innerHTML'), - redirect('^docs/dom/domref/dom_el_ref9\.html$', + redirect(r'^docs/dom/domref/dom_el_ref9\.html$', 'http://developer.mozilla.org/en/DOM:element.lang'), - redirect('^docs/dom/domref/dom_el_ref10\.html$', + redirect(r'^docs/dom/domref/dom_el_ref10\.html$', 'http://developer.mozilla.org/en/DOM:element.lastChild'), - redirect('^docs/dom/domref/dom_el_ref11\.html$', + redirect(r'^docs/dom/domref/dom_el_ref11\.html$', 'http://developer.mozilla.org/en/DOM:element.length'), - redirect('^docs/dom/domref/dom_el_ref12\.html$', + redirect(r'^docs/dom/domref/dom_el_ref12\.html$', 'http://developer.mozilla.org/en/DOM:element.localName'), - redirect('^docs/dom/domref/dom_el_ref13\.html$', + redirect(r'^docs/dom/domref/dom_el_ref13\.html$', 'http://developer.mozilla.org/en/DOM:element.namespaceURI'), - redirect('^docs/dom/domref/dom_el_ref14\.html$', + redirect(r'^docs/dom/domref/dom_el_ref14\.html$', 'http://developer.mozilla.org/en/DOM:element.nextSibling'), - redirect('^docs/dom/domref/dom_el_ref15\.html$', + redirect(r'^docs/dom/domref/dom_el_ref15\.html$', 'http://developer.mozilla.org/en/DOM:element.nodeName'), - redirect('^docs/dom/domref/dom_el_ref16\.html$', + redirect(r'^docs/dom/domref/dom_el_ref16\.html$', 'http://developer.mozilla.org/en/DOM:element.nodeType'), - redirect('^docs/dom/domref/dom_el_ref17\.html$', + redirect(r'^docs/dom/domref/dom_el_ref17\.html$', 'http://developer.mozilla.org/en/DOM:element.nodeValue'), - redirect('^docs/dom/domref/dom_el_ref18\.html$', + redirect(r'^docs/dom/domref/dom_el_ref18\.html$', 'http://developer.mozilla.org/en/DOM:element.offsetHeight'), - redirect('^docs/dom/domref/dom_el_ref19\.html$', + redirect(r'^docs/dom/domref/dom_el_ref19\.html$', 'http://developer.mozilla.org/en/DOM:element.offsetLeft'), - redirect('^docs/dom/domref/dom_el_ref20\.html$', + redirect(r'^docs/dom/domref/dom_el_ref20\.html$', 'http://developer.mozilla.org/en/DOM:element.offsetParent'), - redirect('^docs/dom/domref/dom_el_ref21\.html$', + redirect(r'^docs/dom/domref/dom_el_ref21\.html$', 'http://developer.mozilla.org/en/DOM:element.offsetTop'), - redirect('^docs/dom/domref/dom_el_ref22\.html$', + redirect(r'^docs/dom/domref/dom_el_ref22\.html$', 'http://developer.mozilla.org/en/DOM:element.offsetWidth'), - redirect('^docs/dom/domref/dom_el_ref23\.html$', + redirect(r'^docs/dom/domref/dom_el_ref23\.html$', 'http://developer.mozilla.org/en/DOM:element.ownerDocument'), - redirect('^docs/dom/domref/dom_el_ref24\.html$', + redirect(r'^docs/dom/domref/dom_el_ref24\.html$', 'http://developer.mozilla.org/en/DOM:element.parentNode'), - redirect('^docs/dom/domref/dom_el_ref25\.html$', + redirect(r'^docs/dom/domref/dom_el_ref25\.html$', 'http://developer.mozilla.org/en/DOM:element.prefix'), - redirect('^docs/dom/domref/dom_el_ref26\.html$', + redirect(r'^docs/dom/domref/dom_el_ref26\.html$', 'http://developer.mozilla.org/en/DOM:element.previousSibling'), - redirect('^docs/dom/domref/dom_el_ref27\.html$', + redirect(r'^docs/dom/domref/dom_el_ref27\.html$', 'http://developer.mozilla.org/en/DOM:element.style'), - redirect('^docs/dom/domref/dom_el_ref28\.html$', + redirect(r'^docs/dom/domref/dom_el_ref28\.html$', 'http://developer.mozilla.org/en/DOM:element.tabIndex'), - redirect('^docs/dom/domref/dom_el_ref29\.html$', + redirect(r'^docs/dom/domref/dom_el_ref29\.html$', 'http://developer.mozilla.org/en/DOM:element.tagName'), - redirect('^docs/dom/domref/dom_el_ref30\.html$', + redirect(r'^docs/dom/domref/dom_el_ref30\.html$', 'http://developer.mozilla.org/en/DOM:document.title'), - redirect('^docs/dom/domref/dom_el_ref31\.html$', + redirect(r'^docs/dom/domref/dom_el_ref31\.html$', 'http://developer.mozilla.org/en/DOM:element.addEventListener'), - redirect('^docs/dom/domref/dom_el_ref32\.html$', + redirect(r'^docs/dom/domref/dom_el_ref32\.html$', 'http://developer.mozilla.org/en/DOM:element.appendChild'), - redirect('^docs/dom/domref/dom_el_ref33\.html$', + redirect(r'^docs/dom/domref/dom_el_ref33\.html$', 'http://developer.mozilla.org/en/DOM:element.blur'), - redirect('^docs/dom/domref/dom_el_ref34\.html$', + redirect(r'^docs/dom/domref/dom_el_ref34\.html$', 'http://developer.mozilla.org/en/DOM:element.click'), - redirect('^docs/dom/domref/dom_el_ref35\.html$', + redirect(r'^docs/dom/domref/dom_el_ref35\.html$', 'http://developer.mozilla.org/en/DOM:element.cloneNode'), - redirect('^docs/dom/domref/dom_el_ref36\.html$', + redirect(r'^docs/dom/domref/dom_el_ref36\.html$', 'http://developer.mozilla.org/en/DOM:element.dispatchEvent'), - redirect('^docs/dom/domref/dom_el_ref37\.html$', + redirect(r'^docs/dom/domref/dom_el_ref37\.html$', 'http://developer.mozilla.org/en/DOM:element.focus'), - redirect('^docs/dom/domref/dom_el_ref38\.html$', + redirect(r'^docs/dom/domref/dom_el_ref38\.html$', 'http://developer.mozilla.org/en/DOM:element.getAttribute'), - redirect('^docs/dom/domref/dom_el_ref39\.html$', + redirect(r'^docs/dom/domref/dom_el_ref39\.html$', 'http://developer.mozilla.org/en/DOM:element.getAttributeNS'), - redirect('^docs/dom/domref/dom_el_ref40\.html$', + redirect(r'^docs/dom/domref/dom_el_ref40\.html$', 'http://developer.mozilla.org/en/DOM:element.getAttributeNode'), - redirect('^docs/dom/domref/dom_el_ref41\.html$', + redirect(r'^docs/dom/domref/dom_el_ref41\.html$', 'http://developer.mozilla.org/en/DOM:element.getAttributeNodeNS'), - redirect('^docs/dom/domref/dom_el_ref42\.html$', + redirect(r'^docs/dom/domref/dom_el_ref42\.html$', 'http://developer.mozilla.org/en/DOM:element.getElementsByTagName'), - redirect('^docs/dom/domref/dom_el_ref43\.html$', + redirect(r'^docs/dom/domref/dom_el_ref43\.html$', 'http://developer.mozilla.org/en/DOM:element.hasAttribute'), - redirect('^docs/dom/domref/dom_el_ref44\.html$', + redirect(r'^docs/dom/domref/dom_el_ref44\.html$', 'http://developer.mozilla.org/en/DOM:element.hasAttributeNS'), - redirect('^docs/dom/domref/dom_el_ref45\.html$', + redirect(r'^docs/dom/domref/dom_el_ref45\.html$', 'http://developer.mozilla.org/en/DOM:element.hasAttributes'), - redirect('^docs/dom/domref/dom_el_ref46\.html$', + redirect(r'^docs/dom/domref/dom_el_ref46\.html$', 'http://developer.mozilla.org/en/DOM:element.hasChildNodes'), - redirect('^docs/dom/domref/dom_el_ref47\.html$', + redirect(r'^docs/dom/domref/dom_el_ref47\.html$', 'http://developer.mozilla.org/en/DOM:element.insertBefore'), - redirect('^docs/dom/domref/dom_el_ref48\.html$', + redirect(r'^docs/dom/domref/dom_el_ref48\.html$', 'http://developer.mozilla.org/en/DOM:element.item'), - redirect('^docs/dom/domref/dom_el_ref49\.html$', + redirect(r'^docs/dom/domref/dom_el_ref49\.html$', 'http://developer.mozilla.org/en/DOM:element.nextSibling'), - redirect('^docs/dom/domref/dom_el_ref50\.html$', + redirect(r'^docs/dom/domref/dom_el_ref50\.html$', 'http://developer.mozilla.org/en/DOM:element.normalize'), - redirect('^docs/dom/domref/dom_el_ref51\.html$', + redirect(r'^docs/dom/domref/dom_el_ref51\.html$', 'http://developer.mozilla.org/en/DOM:element.removeAttribute'), - redirect('^docs/dom/domref/dom_el_ref52\.html$', + redirect(r'^docs/dom/domref/dom_el_ref52\.html$', 'http://developer.mozilla.org/en/DOM:element.removeAttributeNS'), - redirect('^docs/dom/domref/dom_el_ref53\.html$', + redirect(r'^docs/dom/domref/dom_el_ref53\.html$', 'http://developer.mozilla.org/en/DOM:element.removeAttributeNode'), - redirect('^docs/dom/domref/dom_el_ref54\.html$', + redirect(r'^docs/dom/domref/dom_el_ref54\.html$', 'http://developer.mozilla.org/en/DOM:element.removeChild'), - redirect('^docs/dom/domref/dom_el_ref55\.html$', + redirect(r'^docs/dom/domref/dom_el_ref55\.html$', 'http://developer.mozilla.org/en/DOM:element.removeEventListener'), - redirect('^docs/dom/domref/dom_el_ref56\.html$', + redirect(r'^docs/dom/domref/dom_el_ref56\.html$', 'http://developer.mozilla.org/en/DOM:element.replaceChild'), - redirect('^docs/dom/domref/dom_el_ref57\.html$', + redirect(r'^docs/dom/domref/dom_el_ref57\.html$', 'http://developer.mozilla.org/en/DOM:element.setAttribute'), - redirect('^docs/dom/domref/dom_el_ref58\.html$', + redirect(r'^docs/dom/domref/dom_el_ref58\.html$', 'http://developer.mozilla.org/en/DOM:element.setAttributeNS'), - redirect('^docs/dom/domref/dom_el_ref59\.html$', + redirect(r'^docs/dom/domref/dom_el_ref59\.html$', 'http://developer.mozilla.org/en/DOM:element.setAttributeNode'), - redirect('^docs/dom/domref/dom_el_ref60\.html$', + redirect(r'^docs/dom/domref/dom_el_ref60\.html$', 'http://developer.mozilla.org/en/DOM:element.setAttributeNodeNS'), - redirect('^docs/dom/domref/dom_el_ref61\.html$', + redirect(r'^docs/dom/domref/dom_el_ref61\.html$', 'http://developer.mozilla.org/en/DOM:element.supports'), - redirect('^docs/dom/domref/dom_el_ref62\.html$', + redirect(r'^docs/dom/domref/dom_el_ref62\.html$', 'http://developer.mozilla.org/en/DOM:element.onblur'), - redirect('^docs/dom/domref/dom_el_ref63\.html$', + redirect(r'^docs/dom/domref/dom_el_ref63\.html$', 'http://developer.mozilla.org/en/DOM:element.onclick'), - redirect('^docs/dom/domref/dom_el_ref64\.html$', + redirect(r'^docs/dom/domref/dom_el_ref64\.html$', 'http://developer.mozilla.org/en/DOM:element.ondblclick'), - redirect('^docs/dom/domref/dom_el_ref65\.html$', + redirect(r'^docs/dom/domref/dom_el_ref65\.html$', 'http://developer.mozilla.org/en/DOM:element.onfocus'), - redirect('^docs/dom/domref/dom_el_ref66\.html$', + redirect(r'^docs/dom/domref/dom_el_ref66\.html$', 'http://developer.mozilla.org/en/DOM:element.onkeydown'), - redirect('^docs/dom/domref/dom_el_ref67\.html$', + redirect(r'^docs/dom/domref/dom_el_ref67\.html$', 'http://developer.mozilla.org/en/DOM:element.onkeypress'), - redirect('^docs/dom/domref/dom_el_ref68\.html$', + redirect(r'^docs/dom/domref/dom_el_ref68\.html$', 'http://developer.mozilla.org/en/DOM:element.onkeyup'), - redirect('^docs/dom/domref/dom_el_ref69\.html$', + redirect(r'^docs/dom/domref/dom_el_ref69\.html$', 'http://developer.mozilla.org/en/DOM:element.onmousedown'), - redirect('^docs/dom/domref/dom_el_ref70\.html$', + redirect(r'^docs/dom/domref/dom_el_ref70\.html$', 'http://developer.mozilla.org/en/DOM:element.onmousemove'), - redirect('^docs/dom/domref/dom_el_ref71\.html$', + redirect(r'^docs/dom/domref/dom_el_ref71\.html$', 'http://developer.mozilla.org/en/DOM:element.onmouseout'), - redirect('^docs/dom/domref/dom_el_ref72\.html$', + redirect(r'^docs/dom/domref/dom_el_ref72\.html$', 'http://developer.mozilla.org/en/DOM:element.onmouseover'), - redirect('^docs/dom/domref/dom_el_ref73\.html$', + redirect(r'^docs/dom/domref/dom_el_ref73\.html$', 'http://developer.mozilla.org/en/DOM:element.onmouseup'), - redirect('^docs/dom/domref/dom_el_ref74\.html$', + redirect(r'^docs/dom/domref/dom_el_ref74\.html$', 'http://developer.mozilla.org/en/DOM:element.onresize'), - redirect('^docs/dom/domref/dom_event_ref\.html$', 'http://developer.mozilla.org/en/DOM:event'), - redirect('^docs/dom/domref/dom_event_ref2\.html$', + redirect(r'^docs/dom/domref/dom_event_ref\.html$', 'http://developer.mozilla.org/en/DOM:event'), + redirect(r'^docs/dom/domref/dom_event_ref2\.html$', 'http://developer.mozilla.org/en/DOM:event.altKey'), - redirect('^docs/dom/domref/dom_event_ref3\.html$', + redirect(r'^docs/dom/domref/dom_event_ref3\.html$', 'http://developer.mozilla.org/en/DOM:event.bubbles'), - redirect('^docs/dom/domref/dom_event_ref4\.html$', + redirect(r'^docs/dom/domref/dom_event_ref4\.html$', 'http://developer.mozilla.org/en/DOM:event.cancelBubble'), - redirect('^docs/dom/domref/dom_event_ref5\.html$', + redirect(r'^docs/dom/domref/dom_event_ref5\.html$', 'http://developer.mozilla.org/en/DOM:event.cancelable'), - redirect('^docs/dom/domref/dom_event_ref6\.html$', + redirect(r'^docs/dom/domref/dom_event_ref6\.html$', 'http://developer.mozilla.org/en/DOM:event.charCode'), - redirect('^docs/dom/domref/dom_event_ref7\.html$', + redirect(r'^docs/dom/domref/dom_event_ref7\.html$', 'http://developer.mozilla.org/en/DOM:event.clientX'), - redirect('^docs/dom/domref/dom_event_ref8\.html$', + redirect(r'^docs/dom/domref/dom_event_ref8\.html$', 'http://developer.mozilla.org/en/DOM:event.clientY'), - redirect('^docs/dom/domref/dom_event_ref9\.html$', + redirect(r'^docs/dom/domref/dom_event_ref9\.html$', 'http://developer.mozilla.org/en/DOM:event.ctrlKey'), - redirect('^docs/dom/domref/dom_event_ref10\.html$', + redirect(r'^docs/dom/domref/dom_event_ref10\.html$', 'http://developer.mozilla.org/en/DOM:event.currentTarget'), - redirect('^docs/dom/domref/dom_event_ref11\.html$', + redirect(r'^docs/dom/domref/dom_event_ref11\.html$', 'http://developer.mozilla.org/en/DOM:event.detail'), - redirect('^docs/dom/domref/dom_event_ref12\.html$', + redirect(r'^docs/dom/domref/dom_event_ref12\.html$', 'http://developer.mozilla.org/en/DOM:event.eventPhase'), - redirect('^docs/dom/domref/dom_event_ref13\.html$', + redirect(r'^docs/dom/domref/dom_event_ref13\.html$', 'http://developer.mozilla.org/en/DOM:event.isChar'), - redirect('^docs/dom/domref/dom_event_ref14\.html$', + redirect(r'^docs/dom/domref/dom_event_ref14\.html$', 'http://developer.mozilla.org/en/DOM:event.keyCode'), - redirect('^docs/dom/domref/dom_event_ref15\.html$', + redirect(r'^docs/dom/domref/dom_event_ref15\.html$', 'http://developer.mozilla.org/en/DOM:event.layerX'), - redirect('^docs/dom/domref/dom_event_ref16\.html$', + redirect(r'^docs/dom/domref/dom_event_ref16\.html$', 'http://developer.mozilla.org/en/DOM:event.layerY'), - redirect('^docs/dom/domref/dom_event_ref17\.html$', + redirect(r'^docs/dom/domref/dom_event_ref17\.html$', 'http://developer.mozilla.org/en/DOM:event.metaKey'), - redirect('^docs/dom/domref/dom_event_ref18\.html$', + redirect(r'^docs/dom/domref/dom_event_ref18\.html$', 'http://developer.mozilla.org/en/DOM:event.pageX'), - redirect('^docs/dom/domref/dom_event_ref19\.html$', + redirect(r'^docs/dom/domref/dom_event_ref19\.html$', 'http://developer.mozilla.org/en/DOM:event.pageY'), - redirect('^docs/dom/domref/dom_event_ref20\.html$', + redirect(r'^docs/dom/domref/dom_event_ref20\.html$', 'http://developer.mozilla.org/en/DOM:event.relatedTarget'), - redirect('^docs/dom/domref/dom_event_ref21\.html$', + redirect(r'^docs/dom/domref/dom_event_ref21\.html$', 'http://developer.mozilla.org/en/DOM:event.screenX'), - redirect('^docs/dom/domref/dom_event_ref22\.html$', + redirect(r'^docs/dom/domref/dom_event_ref22\.html$', 'http://developer.mozilla.org/en/DOM:event.screenY'), - redirect('^docs/dom/domref/dom_event_ref23\.html$', + redirect(r'^docs/dom/domref/dom_event_ref23\.html$', 'http://developer.mozilla.org/en/DOM:event.shiftKey'), - redirect('^docs/dom/domref/dom_event_ref24\.html$', + redirect(r'^docs/dom/domref/dom_event_ref24\.html$', 'http://developer.mozilla.org/en/DOM:event.target'), - redirect('^docs/dom/domref/dom_event_ref25\.html$', + redirect(r'^docs/dom/domref/dom_event_ref25\.html$', 'http://developer.mozilla.org/en/DOM:event.timeStamp'), - redirect('^docs/dom/domref/dom_event_ref26\.html$', + redirect(r'^docs/dom/domref/dom_event_ref26\.html$', 'http://developer.mozilla.org/en/DOM:event.type'), - redirect('^docs/dom/domref/dom_event_ref27\.html$', + redirect(r'^docs/dom/domref/dom_event_ref27\.html$', 'http://developer.mozilla.org/en/DOM:event.view'), - redirect('^docs/dom/domref/dom_event_ref28\.html$', + redirect(r'^docs/dom/domref/dom_event_ref28\.html$', 'http://developer.mozilla.org/en/DOM:event.initEvent'), - redirect('^docs/dom/domref/dom_event_ref29\.html$', + redirect(r'^docs/dom/domref/dom_event_ref29\.html$', 'http://developer.mozilla.org/en/DOM:event.initMouseEvent'), - redirect('^docs/dom/domref/dom_event_ref30\.html$', + redirect(r'^docs/dom/domref/dom_event_ref30\.html$', 'http://developer.mozilla.org/en/DOM:event.initUIEvent'), - redirect('^docs/dom/domref/dom_event_ref31\.html$', + redirect(r'^docs/dom/domref/dom_event_ref31\.html$', 'http://developer.mozilla.org/en/DOM:event.preventDefault'), - redirect('^docs/dom/domref/dom_event_ref32\.html$', + redirect(r'^docs/dom/domref/dom_event_ref32\.html$', 'http://developer.mozilla.org/en/DOM:event.stopPropagation'), - redirect('^docs/dom/domref/dom_html_ref2\.html$', + redirect(r'^docs/dom/domref/dom_html_ref2\.html$', 'http://developer.mozilla.org/en/DOM:form.elements'), - redirect('^docs/dom/domref/dom_html_ref3\.html$', + redirect(r'^docs/dom/domref/dom_html_ref3\.html$', 'http://developer.mozilla.org/en/DOM:form.length'), - redirect('^docs/dom/domref/dom_html_ref4\.html$', + redirect(r'^docs/dom/domref/dom_html_ref4\.html$', 'http://developer.mozilla.org/en/DOM:form.name'), - redirect('^docs/dom/domref/dom_html_ref5\.html$', + redirect(r'^docs/dom/domref/dom_html_ref5\.html$', 'http://developer.mozilla.org/en/DOM:form.acceptCharset'), - redirect('^docs/dom/domref/dom_html_ref6\.html$', + redirect(r'^docs/dom/domref/dom_html_ref6\.html$', 'http://developer.mozilla.org/en/DOM:form.action'), - redirect('^docs/dom/domref/dom_html_ref7\.html$', + redirect(r'^docs/dom/domref/dom_html_ref7\.html$', 'http://developer.mozilla.org/en/DOM:form.enctype'), - redirect('^docs/dom/domref/dom_html_ref8\.html$', + redirect(r'^docs/dom/domref/dom_html_ref8\.html$', 'http://developer.mozilla.org/en/DOM:form.encoding'), - redirect('^docs/dom/domref/dom_html_ref9\.html$', + redirect(r'^docs/dom/domref/dom_html_ref9\.html$', 'http://developer.mozilla.org/en/DOM:form.method'), - redirect('^docs/dom/domref/dom_html_ref10\.html$', + redirect(r'^docs/dom/domref/dom_html_ref10\.html$', 'http://developer.mozilla.org/en/DOM:form.target'), - redirect('^docs/dom/domref/dom_html_ref11\.html$', + redirect(r'^docs/dom/domref/dom_html_ref11\.html$', 'http://developer.mozilla.org/en/DOM:form.submit'), - redirect('^docs/dom/domref/dom_html_ref12\.html$', + redirect(r'^docs/dom/domref/dom_html_ref12\.html$', 'http://developer.mozilla.org/en/DOM:table'), - redirect('^docs/dom/domref/dom_html_ref13\.html$', + redirect(r'^docs/dom/domref/dom_html_ref13\.html$', 'http://developer.mozilla.org/en/DOM:table.caption'), - redirect('^docs/dom/domref/dom_html_ref14\.html$', + redirect(r'^docs/dom/domref/dom_html_ref14\.html$', 'http://developer.mozilla.org/en/DOM:table.tHead'), - redirect('^docs/dom/domref/dom_html_ref15\.html$', + redirect(r'^docs/dom/domref/dom_html_ref15\.html$', 'http://developer.mozilla.org/en/DOM:table.tFoot'), - redirect('^docs/dom/domref/dom_html_ref16\.html$', + redirect(r'^docs/dom/domref/dom_html_ref16\.html$', 'http://developer.mozilla.org/en/DOM:table.rows'), - redirect('^docs/dom/domref/dom_html_ref17\.html$', + redirect(r'^docs/dom/domref/dom_html_ref17\.html$', 'http://developer.mozilla.org/en/DOM:table.tBodies'), - redirect('^docs/dom/domref/dom_html_ref18\.html$', + redirect(r'^docs/dom/domref/dom_html_ref18\.html$', 'http://developer.mozilla.org/en/DOM:table.align'), - redirect('^docs/dom/domref/dom_html_ref19\.html$', + redirect(r'^docs/dom/domref/dom_html_ref19\.html$', 'http://developer.mozilla.org/en/DOM:table.bgColor'), - redirect('^docs/dom/domref/dom_html_ref20\.html$', + redirect(r'^docs/dom/domref/dom_html_ref20\.html$', 'http://developer.mozilla.org/en/DOM:table.border'), - redirect('^docs/dom/domref/dom_html_ref21\.html$', + redirect(r'^docs/dom/domref/dom_html_ref21\.html$', 'http://developer.mozilla.org/en/DOM:table.cellPadding'), - redirect('^docs/dom/domref/dom_html_ref22\.html$', + redirect(r'^docs/dom/domref/dom_html_ref22\.html$', 'http://developer.mozilla.org/en/DOM:table.frame'), - redirect('^docs/dom/domref/dom_html_ref23\.html$', + redirect(r'^docs/dom/domref/dom_html_ref23\.html$', 'http://developer.mozilla.org/en/DOM:table.rules'), - redirect('^docs/dom/domref/dom_html_ref24\.html$', + redirect(r'^docs/dom/domref/dom_html_ref24\.html$', 'http://developer.mozilla.org/en/DOM:table.summary'), - redirect('^docs/dom/domref/dom_html_ref25\.html$', + redirect(r'^docs/dom/domref/dom_html_ref25\.html$', 'http://developer.mozilla.org/en/DOM:table.width'), - redirect('^docs/dom/domref/dom_html_ref26\.html$', + redirect(r'^docs/dom/domref/dom_html_ref26\.html$', 'http://developer.mozilla.org/en/DOM:table.deleteTHead'), - redirect('^docs/dom/domref/dom_html_ref27\.html$', + redirect(r'^docs/dom/domref/dom_html_ref27\.html$', 'http://developer.mozilla.org/en/DOM:table.createTFoot'), - redirect('^docs/dom/domref/dom_html_ref28\.html$', + redirect(r'^docs/dom/domref/dom_html_ref28\.html$', 'http://developer.mozilla.org/en/DOM:table.deleteTFoot'), - redirect('^docs/dom/domref/dom_html_ref29\.html$', + redirect(r'^docs/dom/domref/dom_html_ref29\.html$', 'http://developer.mozilla.org/en/DOM:table.createCaption'), - redirect('^docs/dom/domref/dom_html_ref30\.html$', + redirect(r'^docs/dom/domref/dom_html_ref30\.html$', 'http://developer.mozilla.org/en/DOM:table.deleteCaption'), - redirect('^docs/dom/domref/dom_html_ref31\.html$', + redirect(r'^docs/dom/domref/dom_html_ref31\.html$', 'http://developer.mozilla.org/en/DOM:table.insertRow'), - redirect('^docs/dom/domref/dom_html_ref32\.html$', + redirect(r'^docs/dom/domref/dom_html_ref32\.html$', 'http://developer.mozilla.org/en/DOM:table.deleteRow'), - redirect('^docs/dom/domref/dom_html_ref33\.html$', + redirect(r'^docs/dom/domref/dom_html_ref33\.html$', 'http://developer.mozilla.org/en/DOM:table.insertRow'), - redirect('^docs/dom/domref/dom_html_ref34\.html$', + redirect(r'^docs/dom/domref/dom_html_ref34\.html$', 'http://developer.mozilla.org/en/DOM:table.deleteRow'), - redirect('^docs/dom/domref/dom_intro\.html$', + redirect(r'^docs/dom/domref/dom_intro\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference/Introduction'), - redirect('^docs/dom/domref/dom_range_ref\.html$', 'http://developer.mozilla.org/en/DOM:range'), - redirect('^docs/dom/domref/dom_range_ref2\.html$', + redirect(r'^docs/dom/domref/dom_range_ref\.html$', 'http://developer.mozilla.org/en/DOM:range'), + redirect(r'^docs/dom/domref/dom_range_ref2\.html$', 'http://developer.mozilla.org/en/DOM:range.collapsed'), - redirect('^docs/dom/domref/dom_range_ref3\.html$', + redirect(r'^docs/dom/domref/dom_range_ref3\.html$', 'http://developer.mozilla.org/en/DOM:range.commonAncestorContainer'), - redirect('^docs/dom/domref/dom_range_ref4\.html$', + redirect(r'^docs/dom/domref/dom_range_ref4\.html$', 'http://developer.mozilla.org/en/DOM:range.endContainer'), - redirect('^docs/dom/domref/dom_range_ref5\.html$', + redirect(r'^docs/dom/domref/dom_range_ref5\.html$', 'http://developer.mozilla.org/en/DOM:range.endOffset'), - redirect('^docs/dom/domref/dom_range_ref6\.html$', + redirect(r'^docs/dom/domref/dom_range_ref6\.html$', 'http://developer.mozilla.org/en/DOM:range.startContainer'), - redirect('^docs/dom/domref/dom_range_ref7\.html$', + redirect(r'^docs/dom/domref/dom_range_ref7\.html$', 'http://developer.mozilla.org/en/DOM:range.startOffset'), - redirect('^docs/dom/domref/dom_range_ref8\.html$', + redirect(r'^docs/dom/domref/dom_range_ref8\.html$', 'http://developer.mozilla.org/en/DOM:document.createRange'), - redirect('^docs/dom/domref/dom_range_ref9\.html$', + redirect(r'^docs/dom/domref/dom_range_ref9\.html$', 'http://developer.mozilla.org/en/DOM:range.setStart'), - redirect('^docs/dom/domref/dom_range_ref10\.html$', + redirect(r'^docs/dom/domref/dom_range_ref10\.html$', 'http://developer.mozilla.org/en/DOM:range.setEnd'), - redirect('^docs/dom/domref/dom_range_ref11\.html$', + redirect(r'^docs/dom/domref/dom_range_ref11\.html$', 'http://developer.mozilla.org/en/DOM:range.setStartBefore'), - redirect('^docs/dom/domref/dom_range_ref12\.html$', + redirect(r'^docs/dom/domref/dom_range_ref12\.html$', 'http://developer.mozilla.org/en/DOM:range.setStartAfter'), - redirect('^docs/dom/domref/dom_range_ref13\.html$', + redirect(r'^docs/dom/domref/dom_range_ref13\.html$', 'http://developer.mozilla.org/en/DOM:range.setEndBefore'), - redirect('^docs/dom/domref/dom_range_ref14\.html$', + redirect(r'^docs/dom/domref/dom_range_ref14\.html$', 'http://developer.mozilla.org/en/DOM:range.setEndAfter'), - redirect('^docs/dom/domref/dom_range_ref15\.html$', + redirect(r'^docs/dom/domref/dom_range_ref15\.html$', 'http://developer.mozilla.org/en/DOM:range.selectNode'), - redirect('^docs/dom/domref/dom_range_ref16\.html$', + redirect(r'^docs/dom/domref/dom_range_ref16\.html$', 'http://developer.mozilla.org/en/DOM:range.selectNodeContents'), - redirect('^docs/dom/domref/dom_range_ref17\.html$', + redirect(r'^docs/dom/domref/dom_range_ref17\.html$', 'http://developer.mozilla.org/en/DOM:range.collapse'), - redirect('^docs/dom/domref/dom_range_ref18\.html$', + redirect(r'^docs/dom/domref/dom_range_ref18\.html$', 'http://developer.mozilla.org/en/DOM:range.cloneContents'), - redirect('^docs/dom/domref/dom_range_ref19\.html$', + redirect(r'^docs/dom/domref/dom_range_ref19\.html$', 'http://developer.mozilla.org/en/DOM:range.deleteContents'), - redirect('^docs/dom/domref/dom_range_ref20\.html$', + redirect(r'^docs/dom/domref/dom_range_ref20\.html$', 'http://developer.mozilla.org/en/DOM:range.extractContents'), - redirect('^docs/dom/domref/dom_range_ref21\.html$', + redirect(r'^docs/dom/domref/dom_range_ref21\.html$', 'http://developer.mozilla.org/en/DOM:range.insertNode'), - redirect('^docs/dom/domref/dom_range_ref22\.html$', + redirect(r'^docs/dom/domref/dom_range_ref22\.html$', 'http://developer.mozilla.org/en/DOM:range.surroundContents'), - redirect('^docs/dom/domref/dom_range_ref23\.html$', + redirect(r'^docs/dom/domref/dom_range_ref23\.html$', 'http://developer.mozilla.org/en/DOM:range.compareBoundaryPoints'), - redirect('^docs/dom/domref/dom_range_ref24\.html$', + redirect(r'^docs/dom/domref/dom_range_ref24\.html$', 'http://developer.mozilla.org/en/DOM:range.cloneRange'), - redirect('^docs/dom/domref/dom_range_ref25\.html$', + redirect(r'^docs/dom/domref/dom_range_ref25\.html$', 'http://developer.mozilla.org/en/DOM:range.detach'), - redirect('^docs/dom/domref/dom_range_ref26\.html$', + redirect(r'^docs/dom/domref/dom_range_ref26\.html$', 'http://developer.mozilla.org/en/DOM:range.toString'), - redirect('^docs/dom/domref/dom_shortTOC\.html$', + redirect(r'^docs/dom/domref/dom_shortTOC\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference'), - redirect('^docs/dom/domref/dom_style_ref\.html$', 'http://developer.mozilla.org/en/DOM:style'), - redirect('^docs/dom/domref/dom_style_ref2\.html$', + redirect(r'^docs/dom/domref/dom_style_ref\.html$', 'http://developer.mozilla.org/en/DOM:style'), + redirect(r'^docs/dom/domref/dom_style_ref2\.html$', 'http://developer.mozilla.org/en/DOM:style.media'), - redirect('^docs/dom/domref/dom_style_ref3\.html$', + redirect(r'^docs/dom/domref/dom_style_ref3\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet'), - redirect('^docs/dom/domref/dom_style_ref4\.html$', + redirect(r'^docs/dom/domref/dom_style_ref4\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.cssRules'), - redirect('^docs/dom/domref/dom_style_ref5\.html$', + redirect(r'^docs/dom/domref/dom_style_ref5\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.disabled'), - redirect('^docs/dom/domref/dom_style_ref6\.html$', + redirect(r'^docs/dom/domref/dom_style_ref6\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.href'), - redirect('^docs/dom/domref/dom_style_ref7\.html$', + redirect(r'^docs/dom/domref/dom_style_ref7\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.media'), - redirect('^docs/dom/domref/dom_style_ref8\.html$', + redirect(r'^docs/dom/domref/dom_style_ref8\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.ownerNode'), - redirect('^docs/dom/domref/dom_style_ref9\.html$', + redirect(r'^docs/dom/domref/dom_style_ref9\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.ownerRule'), - redirect('^docs/dom/domref/dom_style_ref10\.html$', + redirect(r'^docs/dom/domref/dom_style_ref10\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.parentStyleSheet'), - redirect('^docs/dom/domref/dom_style_ref11\.html$', + redirect(r'^docs/dom/domref/dom_style_ref11\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.title'), - redirect('^docs/dom/domref/dom_style_ref12\.html$', + redirect(r'^docs/dom/domref/dom_style_ref12\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.type'), - redirect('^docs/dom/domref/dom_style_ref13\.html$', + redirect(r'^docs/dom/domref/dom_style_ref13\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.deleteRule'), - redirect('^docs/dom/domref/dom_style_ref14\.html$', + redirect(r'^docs/dom/domref/dom_style_ref14\.html$', 'http://developer.mozilla.org/en/DOM:stylesheet.insertRule'), - redirect('^docs/dom/domref/dom_style_ref15\.html$', + redirect(r'^docs/dom/domref/dom_style_ref15\.html$', 'http://developer.mozilla.org/en/DOM:cssRule.cssText'), - redirect('^docs/dom/domref/dom_style_ref16\.html$', + redirect(r'^docs/dom/domref/dom_style_ref16\.html$', 'http://developer.mozilla.org/en/DOM:cssRule.parentStyleSheet'), - redirect('^docs/dom/domref/dom_style_ref17\.html$', + redirect(r'^docs/dom/domref/dom_style_ref17\.html$', 'http://developer.mozilla.org/en/DOM:cssRule.selectorText'), - redirect('^docs/dom/domref/dom_style_ref18\.html$', + redirect(r'^docs/dom/domref/dom_style_ref18\.html$', 'http://developer.mozilla.org/en/DOM:cssRule.style'), - redirect('^docs/dom/domref/dom_style_ref19\.html$', + redirect(r'^docs/dom/domref/dom_style_ref19\.html$', 'http://developer.mozilla.org/en/DOM:cssRule.parentStyleSheet'), - redirect('^docs/dom/domref/dom_style_ref20\.html$', + redirect(r'^docs/dom/domref/dom_style_ref20\.html$', 'http://developer.mozilla.org/en/DOM:cssRule.selectorText'), - redirect('^docs/dom/domref/dom_style_ref21\.html$', + redirect(r'^docs/dom/domref/dom_style_ref21\.html$', 'http://developer.mozilla.org/en/DOM:cssRule.style'), - redirect('^docs/dom/domref/dom_style_ref22\.html$', 'http://developer.mozilla.org/en/DOM:CSS'), - redirect('^docs/dom/domref/dom_window_ref\.html$', + redirect(r'^docs/dom/domref/dom_style_ref22\.html$', 'http://developer.mozilla.org/en/DOM:CSS'), + redirect(r'^docs/dom/domref/dom_window_ref\.html$', 'http://developer.mozilla.org/en/DOM:window'), - redirect('^docs/dom/domref/dom_window_ref2\.html$', + redirect(r'^docs/dom/domref/dom_window_ref2\.html$', 'http://developer.mozilla.org/en/DOM:window.alert'), - redirect('^docs/dom/domref/dom_window_ref3\.html$', + redirect(r'^docs/dom/domref/dom_window_ref3\.html$', 'http://developer.mozilla.org/en/DOM:window.content'), - redirect('^docs/dom/domref/dom_window_ref4\.html$', + redirect(r'^docs/dom/domref/dom_window_ref4\.html$', 'http://developer.mozilla.org/en/DOM:window.back'), - redirect('^docs/dom/domref/dom_window_ref5\.html$', + redirect(r'^docs/dom/domref/dom_window_ref5\.html$', 'http://developer.mozilla.org/en/DOM:window.blur'), - redirect('^docs/dom/domref/dom_window_ref6\.html$', + redirect(r'^docs/dom/domref/dom_window_ref6\.html$', 'http://developer.mozilla.org/en/DOM:window.captureEvents'), - redirect('^docs/dom/domref/dom_window_ref7\.html$', + redirect(r'^docs/dom/domref/dom_window_ref7\.html$', 'http://developer.mozilla.org/en/DOM:window.clearInterval'), - redirect('^docs/dom/domref/dom_window_ref8\.html$', + redirect(r'^docs/dom/domref/dom_window_ref8\.html$', 'http://developer.mozilla.org/en/DOM:window.clearTimeout'), - redirect('^docs/dom/domref/dom_window_ref9\.html$', + redirect(r'^docs/dom/domref/dom_window_ref9\.html$', 'http://developer.mozilla.org/en/DOM:window.close'), - redirect('^docs/dom/domref/dom_window_ref10\.html$', + redirect(r'^docs/dom/domref/dom_window_ref10\.html$', 'http://developer.mozilla.org/en/DOM:window.closed'), - redirect('^docs/dom/domref/dom_window_ref11\.html$', + redirect(r'^docs/dom/domref/dom_window_ref11\.html$', 'http://developer.mozilla.org/en/DOM:window.Components'), - redirect('^docs/dom/domref/dom_window_ref12\.html$', + redirect(r'^docs/dom/domref/dom_window_ref12\.html$', 'http://developer.mozilla.org/en/DOM:window.confirm'), - redirect('^docs/dom/domref/dom_window_ref13\.html$', + redirect(r'^docs/dom/domref/dom_window_ref13\.html$', 'http://developer.mozilla.org/en/DOM:window.controllers'), - redirect('^docs/dom/domref/dom_window_ref14\.html$', + redirect(r'^docs/dom/domref/dom_window_ref14\.html$', 'http://developer.mozilla.org/en/DOM:window.crypto'), - redirect('^docs/dom/domref/dom_window_ref15\.html$', + redirect(r'^docs/dom/domref/dom_window_ref15\.html$', 'http://developer.mozilla.org/en/DOM:window.defaultStatus'), - redirect('^docs/dom/domref/dom_window_ref16\.html$', + redirect(r'^docs/dom/domref/dom_window_ref16\.html$', 'http://developer.mozilla.org/en/DOM:window.directories'), - redirect('^docs/dom/domref/dom_window_ref17\.html$', + redirect(r'^docs/dom/domref/dom_window_ref17\.html$', 'http://developer.mozilla.org/en/DOM:window.document'), - redirect('^docs/dom/domref/dom_window_ref18\.html$', + redirect(r'^docs/dom/domref/dom_window_ref18\.html$', 'http://developer.mozilla.org/en/DOM:window.dump'), - redirect('^docs/dom/domref/dom_window_ref19\.html$', + redirect(r'^docs/dom/domref/dom_window_ref19\.html$', 'http://developer.mozilla.org/en/DOM:window.escape'), - redirect('^docs/dom/domref/dom_window_ref20\.html$', + redirect(r'^docs/dom/domref/dom_window_ref20\.html$', 'http://developer.mozilla.org/en/DOM:window.focus'), - redirect('^docs/dom/domref/dom_window_ref21\.html$', + redirect(r'^docs/dom/domref/dom_window_ref21\.html$', 'http://developer.mozilla.org/en/DOM:window.forward'), - redirect('^docs/dom/domref/dom_window_ref22\.html$', + redirect(r'^docs/dom/domref/dom_window_ref22\.html$', 'http://developer.mozilla.org/en/DOM:window.frames'), - redirect('^docs/dom/domref/dom_window_ref23\.html$', + redirect(r'^docs/dom/domref/dom_window_ref23\.html$', 'http://developer.mozilla.org/en/DOM:window.getAttention'), - redirect('^docs/dom/domref/dom_window_ref24\.html$', + redirect(r'^docs/dom/domref/dom_window_ref24\.html$', 'http://developer.mozilla.org/en/DOM:window.getSelection'), - redirect('^docs/dom/domref/dom_window_ref25\.html$', + redirect(r'^docs/dom/domref/dom_window_ref25\.html$', 'http://developer.mozilla.org/en/DOM:window.history'), - redirect('^docs/dom/domref/dom_window_ref26\.html$', + redirect(r'^docs/dom/domref/dom_window_ref26\.html$', 'http://developer.mozilla.org/en/DOM:window.home'), - redirect('^docs/dom/domref/dom_window_ref27\.html$', + redirect(r'^docs/dom/domref/dom_window_ref27\.html$', 'http://developer.mozilla.org/en/DOM:window.innerHeight'), - redirect('^docs/dom/domref/dom_window_ref28\.html$', + redirect(r'^docs/dom/domref/dom_window_ref28\.html$', 'http://developer.mozilla.org/en/DOM:window.innerWidth'), - redirect('^docs/dom/domref/dom_window_ref29\.html$', + redirect(r'^docs/dom/domref/dom_window_ref29\.html$', 'http://developer.mozilla.org/en/DOM:window.length'), - redirect('^docs/dom/domref/dom_window_ref30\.html$', + redirect(r'^docs/dom/domref/dom_window_ref30\.html$', 'http://developer.mozilla.org/en/DOM:window.location'), - redirect('^docs/dom/domref/dom_window_ref31\.html$', + redirect(r'^docs/dom/domref/dom_window_ref31\.html$', 'http://developer.mozilla.org/en/DOM:window.locationbar'), - redirect('^docs/dom/domref/dom_window_ref32\.html$', + redirect(r'^docs/dom/domref/dom_window_ref32\.html$', 'http://developer.mozilla.org/en/DOM:window.menubar'), - redirect('^docs/dom/domref/dom_window_ref33\.html$', + redirect(r'^docs/dom/domref/dom_window_ref33\.html$', 'http://developer.mozilla.org/en/DOM:window.moveBy'), - redirect('^docs/dom/domref/dom_window_ref34\.html$', + redirect(r'^docs/dom/domref/dom_window_ref34\.html$', 'http://developer.mozilla.org/en/DOM:window.moveTo'), - redirect('^docs/dom/domref/dom_window_ref35\.html$', + redirect(r'^docs/dom/domref/dom_window_ref35\.html$', 'http://developer.mozilla.org/en/DOM:window.name'), - redirect('^docs/dom/domref/dom_window_ref36\.html$', + redirect(r'^docs/dom/domref/dom_window_ref36\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator'), - redirect('^docs/dom/domref/dom_window_ref37\.html$', + redirect(r'^docs/dom/domref/dom_window_ref37\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.appCodeName'), - redirect('^docs/dom/domref/dom_window_ref38\.html$', + redirect(r'^docs/dom/domref/dom_window_ref38\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.appName'), - redirect('^docs/dom/domref/dom_window_ref39\.html$', + redirect(r'^docs/dom/domref/dom_window_ref39\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.appVersion'), - redirect('^docs/dom/domref/dom_window_ref40\.html$', + redirect(r'^docs/dom/domref/dom_window_ref40\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.cookieEnabled'), - redirect('^docs/dom/domref/dom_window_ref41\.html$', + redirect(r'^docs/dom/domref/dom_window_ref41\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.javaEnabled'), - redirect('^docs/dom/domref/dom_window_ref42\.html$', + redirect(r'^docs/dom/domref/dom_window_ref42\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.language'), - redirect('^docs/dom/domref/dom_window_ref43\.html$', + redirect(r'^docs/dom/domref/dom_window_ref43\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.mimeTypes'), - redirect('^docs/dom/domref/dom_window_ref44\.html$', + redirect(r'^docs/dom/domref/dom_window_ref44\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.oscpu'), - redirect('^docs/dom/domref/dom_window_ref45\.html$', + redirect(r'^docs/dom/domref/dom_window_ref45\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.platform'), - redirect('^docs/dom/domref/dom_window_ref46\.html$', + redirect(r'^docs/dom/domref/dom_window_ref46\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.plugins'), - redirect('^docs/dom/domref/dom_window_ref47\.html$', + redirect(r'^docs/dom/domref/dom_window_ref47\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.product'), - redirect('^docs/dom/domref/dom_window_ref48\.html$', + redirect(r'^docs/dom/domref/dom_window_ref48\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.productSub'), - redirect('^docs/dom/domref/dom_window_ref49\.html$', + redirect(r'^docs/dom/domref/dom_window_ref49\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.userAgent'), - redirect('^docs/dom/domref/dom_window_ref50\.html$', + redirect(r'^docs/dom/domref/dom_window_ref50\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.vendor'), - redirect('^docs/dom/domref/dom_window_ref51\.html$', + redirect(r'^docs/dom/domref/dom_window_ref51\.html$', 'http://developer.mozilla.org/en/DOM:window.navigator.vendorSub'), - redirect('^docs/dom/domref/dom_window_ref52\.html$', + redirect(r'^docs/dom/domref/dom_window_ref52\.html$', 'http://developer.mozilla.org/en/DOM:window.onabort'), - redirect('^docs/dom/domref/dom_window_ref53\.html$', + redirect(r'^docs/dom/domref/dom_window_ref53\.html$', 'http://developer.mozilla.org/en/DOM:window.onblur'), - redirect('^docs/dom/domref/dom_window_ref54\.html$', + redirect(r'^docs/dom/domref/dom_window_ref54\.html$', 'http://developer.mozilla.org/en/DOM:window.onchange'), - redirect('^docs/dom/domref/dom_window_ref55\.html$', + redirect(r'^docs/dom/domref/dom_window_ref55\.html$', 'http://developer.mozilla.org/en/DOM:window.onclick'), - redirect('^docs/dom/domref/dom_window_ref56\.html$', + redirect(r'^docs/dom/domref/dom_window_ref56\.html$', 'http://developer.mozilla.org/en/DOM:window.onclose'), - redirect('^docs/dom/domref/dom_window_ref57\.html$', + redirect(r'^docs/dom/domref/dom_window_ref57\.html$', 'http://developer.mozilla.org/en/DOM:window.ondragdrop'), - redirect('^docs/dom/domref/dom_window_ref58\.html$', + redirect(r'^docs/dom/domref/dom_window_ref58\.html$', 'http://developer.mozilla.org/en/DOM:window.onerror'), - redirect('^docs/dom/domref/dom_window_ref59\.html$', + redirect(r'^docs/dom/domref/dom_window_ref59\.html$', 'http://developer.mozilla.org/en/DOM:window.onfocus'), - redirect('^docs/dom/domref/dom_window_ref60\.html$', + redirect(r'^docs/dom/domref/dom_window_ref60\.html$', 'http://developer.mozilla.org/en/DOM:window.onkeydown'), - redirect('^docs/dom/domref/dom_window_ref61\.html$', + redirect(r'^docs/dom/domref/dom_window_ref61\.html$', 'http://developer.mozilla.org/en/DOM:window.onkeypress'), - redirect('^docs/dom/domref/dom_window_ref62\.html$', + redirect(r'^docs/dom/domref/dom_window_ref62\.html$', 'http://developer.mozilla.org/en/DOM:window.onkeyup'), - redirect('^docs/dom/domref/dom_window_ref63\.html$', + redirect(r'^docs/dom/domref/dom_window_ref63\.html$', 'http://developer.mozilla.org/en/DOM:window.onload'), - redirect('^docs/dom/domref/dom_window_ref64\.html$', + redirect(r'^docs/dom/domref/dom_window_ref64\.html$', 'http://developer.mozilla.org/en/DOM:window.onmousedown'), - redirect('^docs/dom/domref/dom_window_ref65\.html$', + redirect(r'^docs/dom/domref/dom_window_ref65\.html$', 'http://developer.mozilla.org/en/DOM:window.onmousemove'), - redirect('^docs/dom/domref/dom_window_ref66\.html$', + redirect(r'^docs/dom/domref/dom_window_ref66\.html$', 'http://developer.mozilla.org/en/DOM:window.onmouseout'), - redirect('^docs/dom/domref/dom_window_ref67\.html$', + redirect(r'^docs/dom/domref/dom_window_ref67\.html$', 'http://developer.mozilla.org/en/DOM:window.onmouseover'), - redirect('^docs/dom/domref/dom_window_ref68\.html$', + redirect(r'^docs/dom/domref/dom_window_ref68\.html$', 'http://developer.mozilla.org/en/DOM:window.onmouseup'), - redirect('^docs/dom/domref/dom_window_ref69\.html$', + redirect(r'^docs/dom/domref/dom_window_ref69\.html$', 'http://developer.mozilla.org/en/DOM:window.onpaint'), - redirect('^docs/dom/domref/dom_window_ref70\.html$', + redirect(r'^docs/dom/domref/dom_window_ref70\.html$', 'http://developer.mozilla.org/en/DOM:window.onreset'), - redirect('^docs/dom/domref/dom_window_ref71\.html$', + redirect(r'^docs/dom/domref/dom_window_ref71\.html$', 'http://developer.mozilla.org/en/DOM:window.onresize'), - redirect('^docs/dom/domref/dom_window_ref72\.html$', + redirect(r'^docs/dom/domref/dom_window_ref72\.html$', 'http://developer.mozilla.org/en/DOM:window.onscroll'), - redirect('^docs/dom/domref/dom_window_ref73\.html$', + redirect(r'^docs/dom/domref/dom_window_ref73\.html$', 'http://developer.mozilla.org/en/DOM:window.onselect'), - redirect('^docs/dom/domref/dom_window_ref74\.html$', + redirect(r'^docs/dom/domref/dom_window_ref74\.html$', 'http://developer.mozilla.org/en/DOM:window.onsubmit'), - redirect('^docs/dom/domref/dom_window_ref75\.html$', + redirect(r'^docs/dom/domref/dom_window_ref75\.html$', 'http://developer.mozilla.org/en/DOM:window.onunload'), - redirect('^docs/dom/domref/dom_window_ref76\.html$', + redirect(r'^docs/dom/domref/dom_window_ref76\.html$', 'http://developer.mozilla.org/en/DOM:window.open'), - redirect('^docs/dom/domref/dom_window_ref77\.html$', + redirect(r'^docs/dom/domref/dom_window_ref77\.html$', 'http://developer.mozilla.org/en/DOM:window.opener'), - redirect('^docs/dom/domref/dom_window_ref78\.html$', + redirect(r'^docs/dom/domref/dom_window_ref78\.html$', 'http://developer.mozilla.org/en/DOM:window.outerHeight'), - redirect('^docs/dom/domref/dom_window_ref79\.html$', + redirect(r'^docs/dom/domref/dom_window_ref79\.html$', 'http://developer.mozilla.org/en/DOM:window.outerWidth'), - redirect('^docs/dom/domref/dom_window_ref80\.html$', + redirect(r'^docs/dom/domref/dom_window_ref80\.html$', 'http://developer.mozilla.org/en/DOM:window.pageXOffset'), - redirect('^docs/dom/domref/dom_window_ref81\.html$', + redirect(r'^docs/dom/domref/dom_window_ref81\.html$', 'http://developer.mozilla.org/en/DOM:window.pageYOffset'), - redirect('^docs/dom/domref/dom_window_ref82\.html$', + redirect(r'^docs/dom/domref/dom_window_ref82\.html$', 'http://developer.mozilla.org/en/DOM:window.parent'), - redirect('^docs/dom/domref/dom_window_ref83\.html$', + redirect(r'^docs/dom/domref/dom_window_ref83\.html$', 'http://developer.mozilla.org/en/DOM:window.personalbar'), - redirect('^docs/dom/domref/dom_window_ref84\.html$', + redirect(r'^docs/dom/domref/dom_window_ref84\.html$', 'http://developer.mozilla.org/en/DOM:window.pkcs11'), - redirect('^docs/dom/domref/dom_window_ref85\.html$', + redirect(r'^docs/dom/domref/dom_window_ref85\.html$', 'http://developer.mozilla.org/en/DOM:window.print'), - redirect('^docs/dom/domref/dom_window_ref86\.html$', + redirect(r'^docs/dom/domref/dom_window_ref86\.html$', 'http://developer.mozilla.org/en/DOM:window.prompt'), - redirect('^docs/dom/domref/dom_window_ref87\.html$', + redirect(r'^docs/dom/domref/dom_window_ref87\.html$', 'http://developer.mozilla.org/en/DOM:window.prompter'), - redirect('^docs/dom/domref/dom_window_ref88\.html$', + redirect(r'^docs/dom/domref/dom_window_ref88\.html$', 'http://developer.mozilla.org/en/DOM:window.releaseEvents'), - redirect('^docs/dom/domref/dom_window_ref89\.html$', + redirect(r'^docs/dom/domref/dom_window_ref89\.html$', 'http://developer.mozilla.org/en/DOM:window.resizeBy'), - redirect('^docs/dom/domref/dom_window_ref90\.html$', + redirect(r'^docs/dom/domref/dom_window_ref90\.html$', 'http://developer.mozilla.org/en/DOM:window.resizeTo'), - redirect('^docs/dom/domref/dom_window_ref91\.html$', + redirect(r'^docs/dom/domref/dom_window_ref91\.html$', 'http://developer.mozilla.org/en/DOM:window.screen'), - redirect('^docs/dom/domref/dom_window_ref92\.html$', + redirect(r'^docs/dom/domref/dom_window_ref92\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.availHeight'), - redirect('^docs/dom/domref/dom_window_ref93\.html$', + redirect(r'^docs/dom/domref/dom_window_ref93\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.availLeft'), - redirect('^docs/dom/domref/dom_window_ref94\.html$', + redirect(r'^docs/dom/domref/dom_window_ref94\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.availTop'), - redirect('^docs/dom/domref/dom_window_ref95\.html$', + redirect(r'^docs/dom/domref/dom_window_ref95\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.availWidth'), - redirect('^docs/dom/domref/dom_window_ref96\.html$', + redirect(r'^docs/dom/domref/dom_window_ref96\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.colorDepth'), - redirect('^docs/dom/domref/dom_window_ref97\.html$', + redirect(r'^docs/dom/domref/dom_window_ref97\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.height'), - redirect('^docs/dom/domref/dom_window_ref98\.html$', + redirect(r'^docs/dom/domref/dom_window_ref98\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.left'), - redirect('^docs/dom/domref/dom_window_ref99\.html$', + redirect(r'^docs/dom/domref/dom_window_ref99\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.pixelDepth'), - redirect('^docs/dom/domref/dom_window_ref100\.html$', + redirect(r'^docs/dom/domref/dom_window_ref100\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.top'), - redirect('^docs/dom/domref/dom_window_ref101\.html$', + redirect(r'^docs/dom/domref/dom_window_ref101\.html$', 'http://developer.mozilla.org/en/DOM:window.screen.width'), - redirect('^docs/dom/domref/dom_window_ref102\.html$', + redirect(r'^docs/dom/domref/dom_window_ref102\.html$', 'http://developer.mozilla.org/en/DOM:window.screenX'), - redirect('^docs/dom/domref/dom_window_ref103\.html$', + redirect(r'^docs/dom/domref/dom_window_ref103\.html$', 'http://developer.mozilla.org/en/DOM:window.screenY'), - redirect('^docs/dom/domref/dom_window_ref104\.html$', + redirect(r'^docs/dom/domref/dom_window_ref104\.html$', 'http://developer.mozilla.org/en/DOM:window.scrollbars'), - redirect('^docs/dom/domref/dom_window_ref105\.html$', + redirect(r'^docs/dom/domref/dom_window_ref105\.html$', 'http://developer.mozilla.org/en/DOM:window.scroll'), - redirect('^docs/dom/domref/dom_window_ref106\.html$', + redirect(r'^docs/dom/domref/dom_window_ref106\.html$', 'http://developer.mozilla.org/en/DOM:window.scrollBy'), - redirect('^docs/dom/domref/dom_window_ref107\.html$', + redirect(r'^docs/dom/domref/dom_window_ref107\.html$', 'http://developer.mozilla.org/en/DOM:window.scrollByLines'), - redirect('^docs/dom/domref/dom_window_ref108\.html$', + redirect(r'^docs/dom/domref/dom_window_ref108\.html$', 'http://developer.mozilla.org/en/DOM:window.scrollByPages'), - redirect('^docs/dom/domref/dom_window_ref109\.html$', + redirect(r'^docs/dom/domref/dom_window_ref109\.html$', 'http://developer.mozilla.org/en/DOM:window.scrollTo'), - redirect('^docs/dom/domref/dom_window_ref110\.html$', + redirect(r'^docs/dom/domref/dom_window_ref110\.html$', 'http://developer.mozilla.org/en/DOM:window.scrollX'), - redirect('^docs/dom/domref/dom_window_ref111\.html$', + redirect(r'^docs/dom/domref/dom_window_ref111\.html$', 'http://developer.mozilla.org/en/DOM:window.scrollY'), - redirect('^docs/dom/domref/dom_window_ref112\.html$', + redirect(r'^docs/dom/domref/dom_window_ref112\.html$', 'http://developer.mozilla.org/en/DOM:window.self'), - redirect('^docs/dom/domref/dom_window_ref113\.html$', + redirect(r'^docs/dom/domref/dom_window_ref113\.html$', 'http://developer.mozilla.org/en/DOM:window.setCursor'), - redirect('^docs/dom/domref/dom_window_ref114\.html$', + redirect(r'^docs/dom/domref/dom_window_ref114\.html$', 'http://developer.mozilla.org/en/DOM:window.setInterval'), - redirect('^docs/dom/domref/dom_window_ref115\.html$', + redirect(r'^docs/dom/domref/dom_window_ref115\.html$', 'http://developer.mozilla.org/en/DOM:window.setTimeout'), - redirect('^docs/dom/domref/dom_window_ref116\.html$', + redirect(r'^docs/dom/domref/dom_window_ref116\.html$', 'http://developer.mozilla.org/en/DOM:window.sidebar'), - redirect('^docs/dom/domref/dom_window_ref117\.html$', + redirect(r'^docs/dom/domref/dom_window_ref117\.html$', 'http://developer.mozilla.org/en/DOM:window.sizeToContent'), - redirect('^docs/dom/domref/dom_window_ref118\.html$', + redirect(r'^docs/dom/domref/dom_window_ref118\.html$', 'http://developer.mozilla.org/en/DOM:window.status'), - redirect('^docs/dom/domref/dom_window_ref119\.html$', + redirect(r'^docs/dom/domref/dom_window_ref119\.html$', 'http://developer.mozilla.org/en/DOM:window.statusbar'), - redirect('^docs/dom/domref/dom_window_ref120\.html$', + redirect(r'^docs/dom/domref/dom_window_ref120\.html$', 'http://developer.mozilla.org/en/DOM:window.stop'), - redirect('^docs/dom/domref/dom_window_ref121\.html$', + redirect(r'^docs/dom/domref/dom_window_ref121\.html$', 'http://developer.mozilla.org/en/DOM:window.toolbar'), - redirect('^docs/dom/domref/dom_window_ref122\.html$', + redirect(r'^docs/dom/domref/dom_window_ref122\.html$', 'http://developer.mozilla.org/en/DOM:window.top'), - redirect('^docs/dom/domref/dom_window_ref123\.html$', + redirect(r'^docs/dom/domref/dom_window_ref123\.html$', 'http://developer.mozilla.org/en/DOM:window.unescape'), - redirect('^docs/dom/domref/dom_window_ref124\.html$', + redirect(r'^docs/dom/domref/dom_window_ref124\.html$', 'http://developer.mozilla.org/en/DOM:window.updateCommands'), - redirect('^docs/dom/domref/dom_window_ref125\.html$', + redirect(r'^docs/dom/domref/dom_window_ref125\.html$', 'http://developer.mozilla.org/en/DOM:window.window'), - redirect('^docs/dom/domref/dom_window_ref126\.html$', + redirect(r'^docs/dom/domref/dom_window_ref126\.html$', 'http://developer.mozilla.org/en/DOM:window.window'), - redirect('^docs/dom/domref/dom_window_ref127\.html$', + redirect(r'^docs/dom/domref/dom_window_ref127\.html$', 'http://developer.mozilla.org/en/DOM:window.window'), - redirect('^docs/dom/domref/dom_window_refa2\.html$', + redirect(r'^docs/dom/domref/dom_window_refa2\.html$', 'http://developer.mozilla.org/en/DOM:window.alert'), - redirect('^docs/dom/domref/dom_window_refa3\.html$', + redirect(r'^docs/dom/domref/dom_window_refa3\.html$', 'http://developer.mozilla.org/en/DOM:window.content'), - redirect('^docs/dom/domref/examples\.html$', + redirect(r'^docs/dom/domref/examples\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Examples'), - redirect('^docs/dom/domref/examples2\.html$', + redirect(r'^docs/dom/domref/examples2\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Examples'), - redirect('^docs/dom/domref/examples3\.html$', + redirect(r'^docs/dom/domref/examples3\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Examples'), - redirect('^docs/dom/domref/examples4\.html$', + redirect(r'^docs/dom/domref/examples4\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Examples'), - redirect('^docs/dom/domref/examples5\.html$', + redirect(r'^docs/dom/domref/examples5\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Examples'), - redirect('^docs/dom/domref/examples6\.html$', + redirect(r'^docs/dom/domref/examples6\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Examples'), - redirect('^docs/dom/domref/examples7_res\.html$', + redirect(r'^docs/dom/domref/examples7_res\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Examples'), - redirect('^docs/dom/domref/examples7\.html$', + redirect(r'^docs/dom/domref/examples7\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Examples'), - redirect('^docs/dom/domref/examples8\.html$', + redirect(r'^docs/dom/domref/examples8\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Examples'), - redirect('^docs/dom/domref/images/alert\.gif$', + redirect(r'^docs/dom/domref/images/alert\.gif$', 'http://developer.mozilla.org/wiki-images/en/6/6c/domref-alert.gif'), - redirect('^docs/dom/domref/images/backgrnd\.gif$', + redirect(r'^docs/dom/domref/images/backgrnd\.gif$', 'http://developer.mozilla.org/wiki-images/en/f/fe/domref-backgrnd.gif'), - redirect('^docs/dom/domref/images/cat\.jpg$', + redirect(r'^docs/dom/domref/images/cat\.jpg$', 'http://developer.mozilla.org/wiki-images/en/1/17/domref-cat.jpg'), - redirect('^docs/dom/domref/images/clientHeight\.png$', + redirect(r'^docs/dom/domref/images/clientHeight\.png$', 'http://developer.mozilla.org/wiki-images/en/0/09/domref-clientHeight.png'), - redirect('^docs/dom/domref/images/clientWidth\.png$', + redirect(r'^docs/dom/domref/images/clientWidth\.png$', 'http://developer.mozilla.org/wiki-images/en/a/a3/domref-clientWidth.png'), - redirect('^docs/dom/domref/images/confirm\.gif$', + redirect(r'^docs/dom/domref/images/confirm\.gif$', 'http://developer.mozilla.org/wiki-images/en/6/64/domref-confirm.gif'), - redirect('^docs/dom/domref/images/dom_window_ref2\.gif$', + redirect(r'^docs/dom/domref/images/dom_window_ref2\.gif$', 'http://developer.mozilla.org/wiki-images/en/2/20/domref-dom_window_ref2.gif'), - redirect('^docs/dom/domref/images/dom_window_ref3\.gif$', + redirect(r'^docs/dom/domref/images/dom_window_ref3\.gif$', 'http://developer.mozilla.org/wiki-images/en/6/65/domref-dom_window_ref3.gif'), - redirect('^docs/dom/domref/images/dom_window_refa\.gif$', + redirect(r'^docs/dom/domref/images/dom_window_refa\.gif$', 'http://developer.mozilla.org/wiki-images/en/0/0f/domref-dom_window_refa.gif'), - redirect('^docs/dom/domref/images/dom_window_refa2\.gif$', + redirect(r'^docs/dom/domref/images/dom_window_refa2\.gif$', 'http://developer.mozilla.org/wiki-images/en/2/2b/domref-dom_window_refa2.gif'), - redirect('^docs/dom/domref/images/dom_window_refa3\.gif$', + redirect(r'^docs/dom/domref/images/dom_window_refa3\.gif$', 'http://developer.mozilla.org/wiki-images/en/5/53/domref-dom_window_refa3.gif'), - redirect('^docs/dom/domref/images/domref\.gif$', + redirect(r'^docs/dom/domref/images/domref\.gif$', 'http://developer.mozilla.org/wiki-images/en/e/ed/domref.gif'), - redirect('^docs/dom/domref/images/navidx\.gif$', + redirect(r'^docs/dom/domref/images/navidx\.gif$', 'http://developer.mozilla.org/wiki-images/en/f/f7/domref-navidx.gif'), - redirect('^docs/dom/domref/images/navidxx\.gif$', + redirect(r'^docs/dom/domref/images/navidxx\.gif$', 'http://developer.mozilla.org/wiki-images/en/6/66/domref-navidxx.gif'), - redirect('^docs/dom/domref/images/navnext\.gif$', + redirect(r'^docs/dom/domref/images/navnext\.gif$', 'http://developer.mozilla.org/wiki-images/en/5/5a/domref-navnext.gif'), - redirect('^docs/dom/domref/images/navnextx\.gif$', + redirect(r'^docs/dom/domref/images/navnextx\.gif$', 'http://developer.mozilla.org/wiki-images/en/e/e2/domref-navnextx.gif'), - redirect('^docs/dom/domref/images/navprev\.gif$', + redirect(r'^docs/dom/domref/images/navprev\.gif$', 'http://developer.mozilla.org/wiki-images/en/2/20/domref-navprev.gif'), - redirect('^docs/dom/domref/images/navprevx\.gif$', + redirect(r'^docs/dom/domref/images/navprevx\.gif$', 'http://developer.mozilla.org/wiki-images/en/4/47/domref-navprevx.gif'), - redirect('^docs/dom/domref/images/navtoc\.gif$', + redirect(r'^docs/dom/domref/images/navtoc\.gif$', 'http://developer.mozilla.org/wiki-images/en/0/0f/domref-navtoc.gif'), - redirect('^docs/dom/domref/images/navtocx\.gif$', + redirect(r'^docs/dom/domref/images/navtocx\.gif$', 'http://developer.mozilla.org/wiki-images/en/6/63/domref-navtocx.gif'), - redirect('^docs/dom/domref/images/offsetHeight\.png$', + redirect(r'^docs/dom/domref/images/offsetHeight\.png$', 'http://developer.mozilla.org/wiki-images/en/3/35/domref-offsetHeight.png'), - redirect('^docs/dom/domref/images/offsetWidth\.png$', + redirect(r'^docs/dom/domref/images/offsetWidth\.png$', 'http://developer.mozilla.org/wiki-images/en/0/08/domref-offsetWidth.png'), - redirect('^docs/dom/domref/images/pdf\.gif$', + redirect(r'^docs/dom/domref/images/pdf\.gif$', 'http://developer.mozilla.org/wiki-images/en/3/36/domref-pdf.gif'), - redirect('^docs/dom/domref/images/preface2\.gif$', + redirect(r'^docs/dom/domref/images/preface2\.gif$', 'http://developer.mozilla.org/wiki-images/en/3/3d/domref-preface2.gif'), - redirect('^docs/dom/domref/images/prefacea\.gif$', + redirect(r'^docs/dom/domref/images/prefacea\.gif$', 'http://developer.mozilla.org/wiki-images/en/4/4e/domref-prefacea.gif'), - redirect('^docs/dom/domref/images/prompt\.gif$', + redirect(r'^docs/dom/domref/images/prompt\.gif$', 'http://developer.mozilla.org/wiki-images/en/8/84/domref-prompt.gif'), - redirect('^docs/dom/domref/images/scrollHeight\.png$', + redirect(r'^docs/dom/domref/images/scrollHeight\.png$', 'http://developer.mozilla.org/wiki-images/en/8/8c/domref-scrollHeight.png'), - redirect('^docs/dom/domref/images/scrollTop\.png$', + redirect(r'^docs/dom/domref/images/scrollTop\.png$', 'http://developer.mozilla.org/wiki-images/en/6/68/domref-scrollTop.png'), - redirect('^docs/dom/domref/images/test_page\.gif$', + redirect(r'^docs/dom/domref/images/test_page\.gif$', 'http://developer.mozilla.org/wiki-images/en/5/53/domref-test_page.gif'), - redirect('^docs/dom/domref/images/webworks\.gif$', + redirect(r'^docs/dom/domref/images/webworks\.gif$', 'http://developer.mozilla.org/wiki-images/en/f/fe/domref-webworks.gif'), - redirect('^docs/dom/domref/images/window-chrome\.gif$', + redirect(r'^docs/dom/domref/images/window-chrome\.gif$', 'http://developer.mozilla.org/wiki-images/en/0/08/domref-window-chrome.gif'), - redirect('^docs/dom/domref/preface\.html$', + redirect(r'^docs/dom/domref/preface\.html$', 'http://developer.mozilla.org/en/Gecko_DOM_Reference:Preface'), - redirect('^docs/dom/domref/scrollHeight\.html$', + redirect(r'^docs/dom/domref/scrollHeight\.html$', 'http://developer.mozilla.org/en/DOM:element.scrollHeight'), - redirect('^docs/dom/domref/scrollTop\.html$', + redirect(r'^docs/dom/domref/scrollTop\.html$', 'http://developer.mozilla.org/en/DOM:element.scrollTop'), - redirect('^docs/dom/$', 'http://developer.mozilla.org/en/DOM'), - redirect('^docs/dom/mozilla/hacking\.html$', + redirect(r'^docs/dom/$', 'http://developer.mozilla.org/en/DOM'), + redirect(r'^docs/dom/mozilla/hacking\.html$', 'http://developer.mozilla.org/en/Mozilla_DOM_Hacking_Guide'), - redirect('^docs/dom/mozilla/protodoc\.html$', + redirect(r'^docs/dom/mozilla/protodoc\.html$', 'http://developer.mozilla.org/en/JavaScript-DOM_Prototypes_in_Mozilla'), - redirect('^docs/dom/mozilla/xpcomintro\.html$', + redirect(r'^docs/dom/mozilla/xpcomintro\.html$', 'http://developer.mozilla.org/en/Introduction_to_XPCOM_for_the_DOM'), - redirect('^docs/dom/reference/javascript\.html$', + redirect(r'^docs/dom/reference/javascript\.html$', 'http://developer.mozilla.org/en/The_DOM_and_JavaScript'), - redirect('^docs/dom/reference/levels\.html$', 'http://developer.mozilla.org/en/DOM_Levels'), - redirect('^docs/dom/technote/intro/example\.html$', + redirect(r'^docs/dom/reference/levels\.html$', 'http://developer.mozilla.org/en/DOM_Levels'), + redirect(r'^docs/dom/technote/intro/example\.html$', 'http://developer.mozilla.org/@api/deki/files/2866/=example.html'), - redirect('^docs/dom/technote/intro/$', + redirect(r'^docs/dom/technote/intro/$', 'http://developer.mozilla.org/en/Using_the_W3C_DOM_Level_1_Core'), - redirect('^docs/dom/technote/tn-dom-table/$', + redirect(r'^docs/dom/technote/tn-dom-table/$', 'http://developer.mozilla.org/en/' 'Traversing_an_HTML_table_with_JavaScript_and_DOM_Interfaces'), - redirect('^docs/dom/technote/whitespace/$', + redirect(r'^docs/dom/technote/whitespace/$', 'http://developer.mozilla.org/en/Whitespace_in_the_DOM'), - redirect('^docs/extendmoz\.html$', 'https://developer.mozilla.org/En/Plugins'), - redirect('^docs/how-to-document\.html$', '/contribute/writing/how-to'), - redirect('^docs/hybrid-cd\.html$', 'http://developer.mozilla.org/en/Creating_a_hybrid_CD'), - redirect('^docs/jargon\.html$', 'http://developer.mozilla.org/en/Glossary'), - redirect('^docs/mdp/$', '/contribute/writing/'), - redirect('^docs/modunote\.htm$', 'http://developer.mozilla.org/en/Modularization_Techniques'), - redirect('^docs/mozilla-faq\.html$', 'http://developer.mozilla.org/en/Mozilla_Release_FAQ'), - redirect('^docs/netlib/(necko|new-handler)\.html$', + redirect(r'^docs/extendmoz\.html$', 'https://developer.mozilla.org/En/Plugins'), + redirect(r'^docs/how-to-document\.html$', '/contribute/writing/how-to'), + redirect(r'^docs/hybrid-cd\.html$', 'http://developer.mozilla.org/en/Creating_a_hybrid_CD'), + redirect(r'^docs/jargon\.html$', 'http://developer.mozilla.org/en/Glossary'), + redirect(r'^docs/mdp/$', '/contribute/writing/'), + redirect(r'^docs/modunote\.htm$', 'http://developer.mozilla.org/en/Modularization_Techniques'), + redirect(r'^docs/mozilla-faq\.html$', 'http://developer.mozilla.org/en/Mozilla_Release_FAQ'), + redirect(r'^docs/netlib/(necko|new-handler)\.html$', 'https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Necko'), - redirect('^docs/plugin\.html$', 'https://developer.mozilla.org/En/Plugins'), - redirect('^docs/refList/refNSPR/$', '/projects/nspr/reference/html/'), - redirect('^docs/scripting-plugins\.html$', + redirect(r'^docs/plugin\.html$', 'https://developer.mozilla.org/En/Plugins'), + redirect(r'^docs/refList/refNSPR/$', '/projects/nspr/reference/html/'), + redirect(r'^docs/scripting-plugins\.html$', 'http://developer.mozilla.org/en/Scripting_Plugins_in_Mozilla'), - redirect('^docs/source-directories-overview\.html$', + redirect(r'^docs/source-directories-overview\.html$', 'http://developer.mozilla.org/en/Source_code_directories_overview'), - redirect('^docs/tplist/catBuild/portable-cpp\.html$', + redirect(r'^docs/tplist/catBuild/portable-cpp\.html$', '/hacking/portable-cpp.html'), - redirect('^docs/tplist/catFAQ$', '/classic'), - redirect('^docs/tplist/tplist\.html$', 'https://developer.mozilla.org/'), - redirect('^docs/tutorials/sitenav/$', 'http://developer.mozilla.org/en/Using_Remote_XUL'), - redirect('^docs/tutorials/tinderstatus/tinderstatus\.xpi$', + redirect(r'^docs/tplist/catFAQ$', '/classic'), + redirect(r'^docs/tplist/tplist\.html$', 'https://developer.mozilla.org/'), + redirect(r'^docs/tutorials/sitenav/$', 'http://developer.mozilla.org/en/Using_Remote_XUL'), + redirect(r'^docs/tutorials/tinderstatus/tinderstatus\.xpi$', 'https://addons.mozilla.org/en-US/seamonkey/addon/832'), - redirect('^docs/url_load\.dia$', + redirect(r'^docs/url_load\.dia$', 'http://developer.mozilla.org/@api/deki/files/2893/=url_load.dia'), - redirect('^docs/url_load\.gif$', + redirect(r'^docs/url_load\.gif$', 'http://developer.mozilla.org/@api/deki/files/920/=Url_load.gif'), - redirect('^docs/url_load\.html$', + redirect(r'^docs/url_load\.html$', 'http://developer.mozilla.org/en/The_life_of_an_HTML_HTTP_request'), - redirect('^docs/web-developer/faq\.html$', + redirect(r'^docs/web-developer/faq\.html$', 'http://developer.mozilla.org/en/Mozilla_Web_Developer_FAQ'), - redirect('^docs/web-developer/mimetypes\.html$', + redirect(r'^docs/web-developer/mimetypes\.html$', 'http://developer.mozilla.org/en/How_Mozilla_determines_MIME_Types'), - redirect('^docs/web-developer/quirks/quirklist\.html$', + redirect(r'^docs/web-developer/quirks/quirklist\.html$', 'http://developer.mozilla.org/en/Mozilla_Quirks_Mode_Behavior'), - redirect('^docs/web-developer/quirks/$', + redirect(r'^docs/web-developer/quirks/$', 'http://developer.mozilla.org/en/Mozilla%27s_Quirks_Mode'), - redirect('^docs/web-developer/quirks/doctypes\.html$', + redirect(r'^docs/web-developer/quirks/doctypes\.html$', 'http://developer.mozilla.org/en/Mozilla%27s_DOCTYPE_sniffing'), - redirect('^docs/web-developer/sniffer/browser_type\.html$', + redirect(r'^docs/web-developer/sniffer/browser_type\.html$', 'https://developer.mozilla.org/En/Browser_Detection_and_Cross_Browser_Support'), - redirect('^docs/web-developer/upgrade_2\.html$', + redirect(r'^docs/web-developer/upgrade_2\.html$', 'http://developer.mozilla.org/en/Using_Web_Standards_in_your_Web_Pages'), - redirect('^docs/xul/xulnotes/bubble\.xul$', + redirect(r'^docs/xul/xulnotes/bubble\.xul$', 'http://developer.mozilla.org/@api/deki/files/2865/=bubble.xul'), - redirect('^docs/xul/xulnotes/template-bindings\.html$', + redirect(r'^docs/xul/xulnotes/template-bindings\.html$', 'http://developer.mozilla.org/en/XUL_Template_Primer_-_Bindings'), - redirect('^docs/xul/xulnotes/xulnote_beasts\.html$', + redirect(r'^docs/xul/xulnotes/xulnote_beasts\.html$', 'http://developer.mozilla.org/en/A_XUL_Bestiary'), - redirect('^docs/xul/xulnotes/xulnote_diagnostic\.html$', + redirect(r'^docs/xul/xulnotes/xulnote_diagnostic\.html$', 'http://developer.mozilla.org/en/XUL_Parser_in_Python'), - redirect('^docs/xul/xulnotes/xulnote_events\.html$', + redirect(r'^docs/xul/xulnotes/xulnote_events\.html$', 'http://developer.mozilla.org/en/XUL_Event_Propagation'), - redirect('^docs/xul/xulnotes/xulnote_oven\.html$', + redirect(r'^docs/xul/xulnotes/xulnote_oven\.html$', 'http://developer.mozilla.org/en/My_Chrome_Oven:_Generating_XUL_with_Python'), - redirect('^docs/xul/xulnotes/xulnote_packages\.html$', + redirect(r'^docs/xul/xulnotes/xulnote_packages\.html$', 'http://developer.mozilla.org/en/Creating_XPI_Installer_Modules'), - redirect('^docs/xul/xulnotes/xulnote_skins\.html$', + redirect(r'^docs/xul/xulnotes/xulnote_skins\.html$', 'http://developer.mozilla.org/en/Skinning_XUL_Files_by_Hand'), - redirect('^docs/xul/xulnotes/xulnote_xml\.html$', + redirect(r'^docs/xul/xulnotes/xulnote_xml\.html$', 'http://developer.mozilla.org/en/XUL_Genealogy:_XML'), - redirect('^docs/xul/xulnotes/xulnote_xpconnect\.html$', + redirect(r'^docs/xul/xulnotes/xulnote_xpconnect\.html$', 'http://developer.mozilla.org/en/Fun_With_XBL_and_XPConnect'), - redirect('^donate_faq\.html$', 'https://wiki.mozilla.org/Donate'), - redirect('^donate_form\.pdf$', 'https://donate.mozilla.org/'), - redirect('^donate\.html$', 'https://donate.mozilla.org/'), - redirect('^download-mozilla\.html$', + redirect(r'^donate_faq\.html$', 'https://wiki.mozilla.org/Donate'), + redirect(r'^donate_form\.pdf$', 'https://donate.mozilla.org/'), + redirect(r'^donate\.html$', 'https://donate.mozilla.org/'), + redirect(r'^download-mozilla\.html$', 'http://developer.mozilla.org/en/Download_Mozilla_Source_Code'), - redirect('^feedback\.html$', '/contact/'), - redirect('^firebird$', 'http://www.firefox.com'), - redirect('^get-involved\.html$', '/contribute/'), - redirect('^foundation/mocosc/$', '/foundation/moco/'), - redirect('^glimpsesearch\.html$', 'https://dxr.mozilla.org/'), - redirect('^hacking/bonsai\.html$', 'http://developer.mozilla.org/en/Hacking_with_Bonsai'), - redirect('^hacking/code-review-faq\.html$', 'http://developer.mozilla.org/en/Code_Review_FAQ'), - redirect('^hacking/coding-introduction\.html$', + redirect(r'^feedback\.html$', '/contact/'), + redirect(r'^firebird$', 'http://www.firefox.com'), + redirect(r'^get-involved\.html$', '/contribute/'), + redirect(r'^foundation/mocosc/$', '/foundation/moco/'), + redirect(r'^glimpsesearch\.html$', 'https://dxr.mozilla.org/'), + redirect(r'^hacking/bonsai\.html$', 'http://developer.mozilla.org/en/Hacking_with_Bonsai'), + redirect(r'^hacking/code-review-faq\.html$', 'http://developer.mozilla.org/en/Code_Review_FAQ'), + redirect(r'^hacking/coding-introduction\.html$', 'http://developer.mozilla.org/en/Mozilla_Hacker%27s_Getting_Started_Guide'), - redirect('^hacking/cvs_over_ssh_plan\.html$', + redirect(r'^hacking/cvs_over_ssh_plan\.html$', 'http://developer.mozilla.org/En/Using_SSH_to_connect_to_CVS'), - redirect('^hacking/development-strategies\.html$', + redirect(r'^hacking/development-strategies\.html$', 'http://developer.mozilla.org/en/Mozilla_Development_Strategies'), - redirect('^hacking/life-cycle\.html$', 'http://developer.mozilla.org/en/Hacking_Mozilla'), - redirect('^hacking/mozilla-style-guide\.html$', + redirect(r'^hacking/life-cycle\.html$', 'http://developer.mozilla.org/en/Hacking_Mozilla'), + redirect(r'^hacking/mozilla-style-guide\.html$', 'http://developer.mozilla.org/En/Mozilla_Coding_Style_Guide'), - redirect('^hacking/new-features\.html$', + redirect(r'^hacking/new-features\.html$', 'http://developer.mozilla.org/en/Developing_New_Mozilla_Features'), - redirect('^index2\.html$', '/'), - redirect('^js/spidermonkey/apidoc/complete-frameset\.html$', + redirect(r'^index2\.html$', '/'), + redirect(r'^js/spidermonkey/apidoc/complete-frameset\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/guide\.html$', + redirect(r'^js/spidermonkey/apidoc/guide\.html$', 'http://developer.mozilla.org/en/Embedding_SpiderMonkey'), - redirect('^js/spidermonkey/apidoc/jsguide\.html$', + redirect(r'^js/spidermonkey/apidoc/jsguide\.html$', 'http://developer.mozilla.org/en/JavaScript_C_Engine_Embedder%27s_Guide'), - redirect('^js/spidermonkey/apidoc/jsref\.htm$', + redirect(r'^js/spidermonkey/apidoc/jsref\.htm$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/sparse-frameset\.html$', + redirect(r'^js/spidermonkey/apidoc/sparse-frameset\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/gen/api-BOOLEAN_TO_JSVAL\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-BOOLEAN_TO_JSVAL\.html$', 'http://developer.mozilla.org/en/BOOLEAN_TO_JSVAL'), - redirect('^js/spidermonkey/apidoc/gen/api-DOUBLE_TO_JSVAL\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-DOUBLE_TO_JSVAL\.html$', 'http://developer.mozilla.org/en/DOUBLE_TO_JSVAL'), - redirect('^js/spidermonkey/apidoc/gen/api-INT_FITS_IN_JSVAL\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-INT_FITS_IN_JSVAL\.html$', 'http://developer.mozilla.org/en/INT_FITS_IN_JSVAL'), - redirect('^js/spidermonkey/apidoc/gen/api-INT_TO_JSVAL\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-INT_TO_JSVAL\.html$', 'http://developer.mozilla.org/en/INT_TO_JSVAL'), - redirect('^js/spidermonkey/apidoc/gen/api-JSCLASS_HAS_PRIVATE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSCLASS_HAS_PRIVATE\.html$', 'http://developer.mozilla.org/en/JSCLASS_HAS_PRIVATE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSCLASS_NEW_ENUMERATE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSCLASS_NEW_ENUMERATE\.html$', 'http://developer.mozilla.org/en/JSCLASS_NEW_ENUMERATE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSCLASS_NEW_RESOLVE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSCLASS_NEW_RESOLVE\.html$', 'http://developer.mozilla.org/en/JSCLASS_NEW_RESOLVE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSClass\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSClass\.html$', 'http://developer.mozilla.org/en/JSClass'), - redirect('^js/spidermonkey/apidoc/gen/api-JSConstDoubleSpec\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSConstDoubleSpec\.html$', 'http://developer.mozilla.org/en/JSConstDoubleSpec'), - redirect('^js/spidermonkey/apidoc/gen/api-JSErrorReport\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSErrorReport\.html$', 'http://developer.mozilla.org/en/JSErrorReport'), - redirect('^js/spidermonkey/apidoc/gen/api-JSFUN_BOUND_METHOD\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSFUN_BOUND_METHOD\.html$', 'http://developer.mozilla.org/en/JSFUN_BOUND_METHOD'), - redirect('^js/spidermonkey/apidoc/gen/api-JSFUN_GLOBAL_PARENT\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSFUN_GLOBAL_PARENT\.html$', 'http://developer.mozilla.org/en/JSFUN_GLOBAL_PARENT'), - redirect('^js/spidermonkey/apidoc/gen/api-JSFunctionSpec\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSFunctionSpec\.html$', 'http://developer.mozilla.org/en/JSFunctionSpec'), - redirect('^js/spidermonkey/apidoc/gen/api-JSIdArray\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSIdArray\.html$', 'http://developer.mozilla.org/en/JSIdArray'), - redirect('^js/spidermonkey/apidoc/gen/api-JSObjectOps\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSObjectOps\.html$', 'http://developer.mozilla.org/en/JSObjectOps'), - redirect('^js/spidermonkey/apidoc/gen/api-JSPRINCIPALS_DROP\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSPRINCIPALS_DROP\.html$', 'http://developer.mozilla.org/en/JSPRINCIPALS_DROP'), - redirect('^js/spidermonkey/apidoc/gen/api-JSPRINCIPALS_HOLD\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSPRINCIPALS_HOLD\.html$', 'http://developer.mozilla.org/en/JSPRINCIPALS_HOLD'), - redirect('^js/spidermonkey/apidoc/gen/api-JSPROP_ENUMERATE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSPROP_ENUMERATE\.html$', 'http://developer.mozilla.org/en/JSPROP_ENUMERATE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSPROP_EXPORTED\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSPROP_EXPORTED\.html$', 'http://developer.mozilla.org/en/JSPROP_EXPORTED'), - redirect('^js/spidermonkey/apidoc/gen/api-JSPROP_INDEX\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSPROP_INDEX\.html$', 'http://developer.mozilla.org/en/JSPROP_INDEX'), - redirect('^js/spidermonkey/apidoc/gen/api-JSPROP_PERMANENT\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSPROP_PERMANENT\.html$', 'http://developer.mozilla.org/en/JSPROP_PERMANENT'), - redirect('^js/spidermonkey/apidoc/gen/api-JSPROP_READONLY\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSPROP_READONLY\.html$', 'http://developer.mozilla.org/en/JSPROP_READONLY'), - redirect('^js/spidermonkey/apidoc/gen/api-JSPrincipals\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSPrincipals\.html$', 'http://developer.mozilla.org/en/JSPrincipals'), - redirect('^js/spidermonkey/apidoc/gen/api-JSProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSProperty\.html$', 'http://developer.mozilla.org/en/JSProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JSPropertySpec\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSPropertySpec\.html$', 'http://developer.mozilla.org/en/JSPropertySpec'), - redirect('^js/spidermonkey/apidoc/gen/api-JSRESOLVE_ASSIGNING\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSRESOLVE_ASSIGNING\.html$', 'http://developer.mozilla.org/en/JSRESOLVE_ASSIGNING'), - redirect('^js/spidermonkey/apidoc/gen/api-JSRESOLVE_QUALIFIED\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSRESOLVE_QUALIFIED\.html$', 'http://developer.mozilla.org/en/JSRESOLVE_QUALIFIED'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_FALSE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_FALSE\.html$', 'http://developer.mozilla.org/en/JSVAL_FALSE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_BOOLEAN\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_BOOLEAN\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_BOOLEAN'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_DOUBLE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_DOUBLE\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_DOUBLE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_GCTHING\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_GCTHING\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_GCTHING'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_INT\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_INT\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_INT'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_NULL\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_NULL\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_NULL'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_NUMBER\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_NUMBER\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_NUMBER'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_OBJECT\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_OBJECT\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_OBJECT'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_PRIMITIVE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_PRIMITIVE\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_PRIMITIVE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_STRING\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_STRING\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_STRING'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_IS_VOID\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_IS_VOID\.html$', 'http://developer.mozilla.org/en/JSVAL_IS_VOID'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_LOCK\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_LOCK\.html$', 'http://developer.mozilla.org/en/JSVAL_LOCK'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_NULL\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_NULL\.html$', 'http://developer.mozilla.org/en/JSVAL_NULL'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_ONE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_ONE\.html$', 'http://developer.mozilla.org/en/JSVAL_ONE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_TO_BOOLEAN\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_TO_BOOLEAN\.html$', 'http://developer.mozilla.org/en/JSVAL_TO_BOOLEAN'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_TO_DOUBLE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_TO_DOUBLE\.html$', 'http://developer.mozilla.org/en/JSVAL_TO_DOUBLE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_TO_GCTHING\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_TO_GCTHING\.html$', 'http://developer.mozilla.org/en/JSVAL_TO_GCTHING'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_TO_INT\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_TO_INT\.html$', 'http://developer.mozilla.org/en/JSVAL_TO_INT'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_TO_OBJECT\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_TO_OBJECT\.html$', 'http://developer.mozilla.org/en/JSVAL_TO_OBJECT'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_TO_PRIVATE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_TO_PRIVATE\.html$', 'http://developer.mozilla.org/en/JSVAL_TO_PRIVATE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_TO_STRING\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_TO_STRING\.html$', 'http://developer.mozilla.org/en/JSVAL_TO_STRING'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_TRUE\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_TRUE\.html$', 'http://developer.mozilla.org/en/JSVAL_TRUE'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_UNLOCK\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_UNLOCK\.html$', 'http://developer.mozilla.org/en/JSVAL_UNLOCK'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_VOID\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_VOID\.html$', 'http://developer.mozilla.org/en/JSVAL_VOID'), - redirect('^js/spidermonkey/apidoc/gen/api-JSVAL_ZERO\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JSVAL_ZERO\.html$', 'http://developer.mozilla.org/en/JSVAL_ZERO'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_AddNamedRoot\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_AddNamedRoot\.html$', 'http://developer.mozilla.org/en/JS_AddNamedRoot'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_AddRoot\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_AddRoot\.html$', 'http://developer.mozilla.org/en/JS_AddRoot'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_AliasElement\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_AliasElement\.html$', 'http://developer.mozilla.org/en/JS_AliasElement'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_AliasProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_AliasProperty\.html$', 'http://developer.mozilla.org/en/JS_AliasProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_BeginRequest\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_BeginRequest\.html$', 'http://developer.mozilla.org/en/JS_BeginRequest'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CallFunction\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CallFunction\.html$', 'http://developer.mozilla.org/en/JS_CallFunction'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CallFunctionName\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CallFunctionName\.html$', 'http://developer.mozilla.org/en/JS_CallFunctionName'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CallFunctionValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CallFunctionValue\.html$', 'http://developer.mozilla.org/en/JS_CallFunctionValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CheckAccess\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CheckAccess\.html$', 'http://developer.mozilla.org/en/JS_CheckAccess'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ClearContextThread\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ClearContextThread\.html$', 'http://developer.mozilla.org/en/JS_ClearContextThread'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ClearScope\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ClearScope\.html$', 'http://developer.mozilla.org/en/JS_ClearScope'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CloneFunctionObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CloneFunctionObject\.html$', 'http://developer.mozilla.org/en/JS_CloneFunctionObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompareStrings\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompareStrings\.html$', 'http://developer.mozilla.org/en/JS_CompareStrings'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompileFile\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompileFile\.html$', 'http://developer.mozilla.org/en/JS_CompileFile'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompileFunction\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompileFunction\.html$', 'http://developer.mozilla.org/en/JS_CompileFunction'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompileFunctionForPrincipals\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompileFunctionForPrincipals\.html$', 'http://developer.mozilla.org/en/JS_CompileFunctionForPrincipals'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompileScript\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompileScript\.html$', 'http://developer.mozilla.org/en/JS_CompileScript'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompileScriptForPrincipals\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompileScriptForPrincipals\.html$', 'http://developer.mozilla.org/en/JS_CompileScriptForPrincipals'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompileUCFunction\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompileUCFunction\.html$', 'http://developer.mozilla.org/en/JS_CompileUCFunction'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompileUCFunctionForPrincipals\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompileUCFunctionForPrincipals\.html$', 'http://developer.mozilla.org/en/JS_CompileUCFunctionForPrincipals'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompileUCScript\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompileUCScript\.html$', 'http://developer.mozilla.org/en/JS_CompileUCScript'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_CompileUCScriptForPrincipals\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_CompileUCScriptForPrincipals\.html$', 'http://developer.mozilla.org/en/JS_CompileUCScriptForPrincipals'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ConstructObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ConstructObject\.html$', 'http://developer.mozilla.org/en/JS_ConstructObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ContextIterator\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ContextIterator\.html$', 'http://developer.mozilla.org/en/JS_ContextIterator'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ConvertArguments\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ConvertArguments\.html$', 'http://developer.mozilla.org/en/JS_ConvertArguments'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ConvertStub\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ConvertStub\.html$', 'http://developer.mozilla.org/en/JS_ConvertStub'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ConvertValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ConvertValue\.html$', 'http://developer.mozilla.org/en/JS_ConvertValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DecompileFunction\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DecompileFunction\.html$', 'http://developer.mozilla.org/en/JS_DecompileFunction'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DecompileFunctionBody\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DecompileFunctionBody\.html$', 'http://developer.mozilla.org/en/JS_DecompileFunctionBody'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DecompileScript\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DecompileScript\.html$', 'http://developer.mozilla.org/en/JS_DecompileScript'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefineConstDoubles\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefineConstDoubles\.html$', 'http://developer.mozilla.org/en/JS_DefineConstDoubles'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefineElement\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefineElement\.html$', 'http://developer.mozilla.org/en/JS_DefineElement'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefineFunction\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefineFunction\.html$', 'http://developer.mozilla.org/en/JS_DefineFunction'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefineFunctions\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefineFunctions\.html$', 'http://developer.mozilla.org/en/JS_DefineFunctions'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefineObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefineObject\.html$', 'http://developer.mozilla.org/en/JS_DefineObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefineProperties\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefineProperties\.html$', 'http://developer.mozilla.org/en/JS_DefineProperties'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefineProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefineProperty\.html$', 'http://developer.mozilla.org/en/JS_DefineProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefinePropertyWithTinyId\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefinePropertyWithTinyId\.html$', 'http://developer.mozilla.org/en/JS_DefinePropertyWithTinyId'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefineUCProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefineUCProperty\.html$', 'http://developer.mozilla.org/en/JS_DefineUCProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DefineUCPropertyWithTinyID\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DefineUCPropertyWithTinyID\.html$', 'http://developer.mozilla.org/en/JS_DefineUCPropertyWithTinyID'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DeleteElement\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DeleteElement\.html$', 'http://developer.mozilla.org/en/JS_DeleteElement'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DeleteElement2\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DeleteElement2\.html$', 'http://developer.mozilla.org/en/JS_DeleteElement2'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DeleteProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DeleteProperty\.html$', 'http://developer.mozilla.org/en/JS_DeleteProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DeleteProperty2\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DeleteProperty2\.html$', 'http://developer.mozilla.org/en/JS_DeleteProperty2'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DeleteUCProperty2\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DeleteUCProperty2\.html$', 'http://developer.mozilla.org/en/JS_DeleteUCProperty2'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DestroyContext\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DestroyContext\.html$', 'http://developer.mozilla.org/en/JS_DestroyContext'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DestroyIdArray\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DestroyIdArray\.html$', 'http://developer.mozilla.org/en/JS_DestroyIdArray'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DestroyRuntime\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DestroyRuntime\.html$', 'http://developer.mozilla.org/en/JS_DestroyRuntime'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DestroyScript\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DestroyScript\.html$', 'http://developer.mozilla.org/en/JS_DestroyScript'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_DumpNamedRoots\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_DumpNamedRoots\.html$', 'http://developer.mozilla.org/en/JS_DumpNamedRoots'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_EndRequest\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_EndRequest\.html$', 'http://developer.mozilla.org/en/JS_EndRequest'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_Enumerate\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_Enumerate\.html$', 'http://developer.mozilla.org/en/JS_Enumerate'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_EnumerateStub\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_EnumerateStub\.html$', 'http://developer.mozilla.org/en/JS_EnumerateStub'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_EvaluateScript\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_EvaluateScript\.html$', 'http://developer.mozilla.org/en/JS_EvaluateScript'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_EvaluateScriptForPrincipals\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_EvaluateScriptForPrincipals\.html$', 'http://developer.mozilla.org/en/JS_EvaluateScriptForPrincipals'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_EvaluateUCScript\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_EvaluateUCScript\.html$', 'http://developer.mozilla.org/en/JS_EvaluateUCScript'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_EvaluateUCScriptForPrincipals\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_EvaluateUCScriptForPrincipals\.html$', 'http://developer.mozilla.org/en/JS_EvaluateUCScriptForPrincipals'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ExecuteScript\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ExecuteScript\.html$', 'http://developer.mozilla.org/en/JS_ExecuteScript'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_FinalizeStub\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_FinalizeStub\.html$', 'http://developer.mozilla.org/en/JS_FinalizeStub'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_Finish\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_Finish\.html$', 'http://developer.mozilla.org/en/JS_Finish'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GC\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GC\.html$', 'http://developer.mozilla.org/en/JS_GC'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetArrayLength\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetArrayLength\.html$', 'http://developer.mozilla.org/en/JS_GetArrayLength'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetClass\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetClass\.html$', 'http://developer.mozilla.org/en/JS_GetClass'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetConstructor\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetConstructor\.html$', 'http://developer.mozilla.org/en/JS_GetConstructor'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetContextPrivate\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetContextPrivate\.html$', 'http://developer.mozilla.org/en/JS_GetContextPrivate'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetContextThread\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetContextThread\.html$', 'http://developer.mozilla.org/en/JS_GetContextThread'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetElement\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetElement\.html$', 'http://developer.mozilla.org/en/JS_GetElement'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetEmptyStringValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetEmptyStringValue\.html$', 'http://developer.mozilla.org/en/JS_GetEmptyStringValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetFunctionName\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetFunctionName\.html$', 'http://developer.mozilla.org/en/JS_GetFunctionName'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetFunctionObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetFunctionObject\.html$', 'http://developer.mozilla.org/en/JS_GetFunctionObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetGlobalObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetGlobalObject\.html$', 'http://developer.mozilla.org/en/JS_GetGlobalObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetImplementationVersion\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetImplementationVersion\.html$', 'http://developer.mozilla.org/en/JS_GetImplementationVersion'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetInstancePrivate\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetInstancePrivate\.html$', 'http://developer.mozilla.org/en/JS_GetInstancePrivate'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetNaNValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetNaNValue\.html$', 'http://developer.mozilla.org/en/JS_GetNaNValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetNegativeInfinityValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetNegativeInfinityValue\.html$', 'http://developer.mozilla.org/en/JS_GetNegativeInfinityValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetParent\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetParent\.html$', 'http://developer.mozilla.org/en/JS_GetParent'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetPositiveInfinityValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetPositiveInfinityValue\.html$', 'http://developer.mozilla.org/en/JS_GetPositiveInfinityValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetPrivate\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetPrivate\.html$', 'http://developer.mozilla.org/en/JS_GetPrivate'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetProperty\.html$', 'http://developer.mozilla.org/en/JS_GetProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetPropertyAttributes\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetPropertyAttributes\.html$', 'http://developer.mozilla.org/en/JS_GetPropertyAttributes'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetPrototype\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetPrototype\.html$', 'http://developer.mozilla.org/en/JS_GetPrototype'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetRuntime\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetRuntime\.html$', 'http://developer.mozilla.org/en/JS_GetRuntime'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetScopeChain\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetScopeChain\.html$', 'http://developer.mozilla.org/en/JS_GetScopeChain'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetStringBytes\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetStringBytes\.html$', 'http://developer.mozilla.org/en/JS_GetStringBytes'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetStringChars\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetStringChars\.html$', 'http://developer.mozilla.org/en/JS_GetStringChars'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetStringLength\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetStringLength\.html$', 'http://developer.mozilla.org/en/JS_GetStringLength'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetTypeName\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetTypeName\.html$', 'http://developer.mozilla.org/en/JS_GetTypeName'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetUCProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetUCProperty\.html$', 'http://developer.mozilla.org/en/JS_GetUCProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_GetVersion\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_GetVersion\.html$', 'http://developer.mozilla.org/en/JS_GetVersion'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_HasArrayLength\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_HasArrayLength\.html$', 'http://developer.mozilla.org/en/JS_HasArrayLength'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_IdToValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_IdToValue\.html$', 'http://developer.mozilla.org/en/JS_IdToValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_Init\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_Init\.html$', 'http://developer.mozilla.org/en/JS_Init'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_InitClass\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_InitClass\.html$', 'http://developer.mozilla.org/en/JS_InitClass'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_InitStandardClasses\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_InitStandardClasses\.html$', 'http://developer.mozilla.org/en/JS_InitStandardClasses'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_InstanceOf\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_InstanceOf\.html$', 'http://developer.mozilla.org/en/JS_InstanceOf'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_InternString\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_InternString\.html$', 'http://developer.mozilla.org/en/JS_InternString'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_InternUCString\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_InternUCString\.html$', 'http://developer.mozilla.org/en/JS_InternUCString'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_InternUCStringN\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_InternUCStringN\.html$', 'http://developer.mozilla.org/en/JS_InternUCStringN'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_IsArrayObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_IsArrayObject\.html$', 'http://developer.mozilla.org/en/JS_IsArrayObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_IsConstructing\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_IsConstructing\.html$', 'http://developer.mozilla.org/en/JS_IsConstructing'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_IsRunning\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_IsRunning\.html$', 'http://developer.mozilla.org/en/JS_IsRunning'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_Lock\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_Lock\.html$', 'http://developer.mozilla.org/en/JS_Lock'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_LockGCThing\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_LockGCThing\.html$', 'http://developer.mozilla.org/en/JS_LockGCThing'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_LookupElement\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_LookupElement\.html$', 'http://developer.mozilla.org/en/JS_LookupElement'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_LookupProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_LookupProperty\.html$', 'http://developer.mozilla.org/en/JS_LookupProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_LookupUCProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_LookupUCProperty\.html$', 'http://developer.mozilla.org/en/JS_LookupUCProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_MaybeGC\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_MaybeGC\.html$', 'http://developer.mozilla.org/en/JS_MaybeGC'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewArrayObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewArrayObject\.html$', 'http://developer.mozilla.org/en/JS_NewArrayObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewContext\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewContext\.html$', 'http://developer.mozilla.org/en/JS_NewContext'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewDouble\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewDouble\.html$', 'http://developer.mozilla.org/en/JS_NewDouble'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewDoubleValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewDoubleValue\.html$', 'http://developer.mozilla.org/en/JS_NewDoubleValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewFunction\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewFunction\.html$', 'http://developer.mozilla.org/en/JS_NewFunction'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewIdArray\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewIdArray\.html$', 'http://developer.mozilla.org/en/JS_NewIdArray'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewNumberValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewNumberValue\.html$', 'http://developer.mozilla.org/en/JS_NewNumberValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewObject\.html$', 'http://developer.mozilla.org/en/JS_NewObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewRuntime\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewRuntime\.html$', 'http://developer.mozilla.org/en/JS_NewRuntime'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewScriptObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewScriptObject\.html$', 'http://developer.mozilla.org/en/JS_NewScriptObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewString\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewString\.html$', 'http://developer.mozilla.org/en/JS_NewString'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewStringCopyN\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewStringCopyN\.html$', 'http://developer.mozilla.org/en/JS_NewStringCopyN'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewStringCopyZ\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewStringCopyZ\.html$', 'http://developer.mozilla.org/en/JS_NewStringCopyZ'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewUCString\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewUCString\.html$', 'http://developer.mozilla.org/en/JS_NewUCString'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewUCStringCopyN\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewUCStringCopyN\.html$', 'http://developer.mozilla.org/en/JS_NewUCStringCopyN'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_NewUCStringCopyZ\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_NewUCStringCopyZ\.html$', 'http://developer.mozilla.org/en/JS_NewUCStringCopyZ'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_PropertyStub\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_PropertyStub\.html$', 'http://developer.mozilla.org/en/JS_PropertyStub'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_RemoveRoot\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_RemoveRoot\.html$', 'http://developer.mozilla.org/en/JS_RemoveRoot'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ReportError\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ReportError\.html$', 'http://developer.mozilla.org/en/JS_ReportError'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ReportOutOfMemory\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ReportOutOfMemory\.html$', 'http://developer.mozilla.org/en/JS_ReportOutOfMemory'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ResolveStub\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ResolveStub\.html$', 'http://developer.mozilla.org/en/JS_ResolveStub'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ResumeRequest\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ResumeRequest\.html$', 'http://developer.mozilla.org/en/JS_ResumeRequest'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetArrayLength\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetArrayLength\.html$', 'http://developer.mozilla.org/en/JS_SetArrayLength'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetBranchCallback\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetBranchCallback\.html$', 'http://developer.mozilla.org/en/JS_SetBranchCallback'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetContextPrivate\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetContextPrivate\.html$', 'http://developer.mozilla.org/en/JS_SetContextPrivate'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetContextThread\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetContextThread\.html$', 'http://developer.mozilla.org/en/JS_SetContextThread'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetElement\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetElement\.html$', 'http://developer.mozilla.org/en/JS_SetElement'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetErrorReporter\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetErrorReporter\.html$', 'http://developer.mozilla.org/en/JS_SetErrorReporter'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetGCCallback\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetGCCallback\.html$', 'http://developer.mozilla.org/en/JS_SetGCCallback'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetGlobalObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetGlobalObject\.html$', 'http://developer.mozilla.org/en/JS_SetGlobalObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetParent\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetParent\.html$', 'http://developer.mozilla.org/en/JS_SetParent'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetPrivate\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetPrivate\.html$', 'http://developer.mozilla.org/en/JS_SetPrivate'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetProperty\.html$', 'http://developer.mozilla.org/en/JS_SetProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetPropertyAttributes\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetPropertyAttributes\.html$', 'http://developer.mozilla.org/en/JS_SetPropertyAttributes'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetPrototype\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetPrototype\.html$', 'http://developer.mozilla.org/en/JS_SetPrototype'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetUCProperty\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetUCProperty\.html$', 'http://developer.mozilla.org/en/JS_SetUCProperty'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SetVersion\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SetVersion\.html$', 'http://developer.mozilla.org/en/JS_SetVersion'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_SuspendRequest\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_SuspendRequest\.html$', 'http://developer.mozilla.org/en/JS_SuspendRequest'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_TypeOfValue\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_TypeOfValue\.html$', 'http://developer.mozilla.org/en/JS_TypeOfValue'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_Unlock\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_Unlock\.html$', 'http://developer.mozilla.org/en/JS_Unlock'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_UnlockGCThing\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_UnlockGCThing\.html$', 'http://developer.mozilla.org/en/JS_UnlockGCThing'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToBoolean\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToBoolean\.html$', 'http://developer.mozilla.org/en/JS_ValueToBoolean'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToECMAInt32\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToECMAInt32\.html$', 'http://developer.mozilla.org/en/JS_ValueToECMAInt32'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToECMAUint32\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToECMAUint32\.html$', 'http://developer.mozilla.org/en/JS_ValueToECMAUint32'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToFunction\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToFunction\.html$', 'http://developer.mozilla.org/en/JS_ValueToFunction'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToId\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToId\.html$', 'http://developer.mozilla.org/en/JS_ValueToId'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToInt32\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToInt32\.html$', 'http://developer.mozilla.org/en/JS_ValueToInt32'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToNumber\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToNumber\.html$', 'http://developer.mozilla.org/en/JS_ValueToNumber'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToObject\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToObject\.html$', 'http://developer.mozilla.org/en/JS_ValueToObject'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToString\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToString\.html$', 'http://developer.mozilla.org/en/JS_ValueToString'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_ValueToUint16\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_ValueToUint16\.html$', 'http://developer.mozilla.org/en/JS_ValueToUint16'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_free\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_free\.html$', 'http://developer.mozilla.org/en/JS_free'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_malloc\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_malloc\.html$', 'http://developer.mozilla.org/en/JS_malloc'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_realloc\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_realloc\.html$', 'http://developer.mozilla.org/en/JS_realloc'), - redirect('^js/spidermonkey/apidoc/gen/api-JS_strdup\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-JS_strdup\.html$', 'http://developer.mozilla.org/en/JS_strdup'), - redirect('^js/spidermonkey/apidoc/gen/api-OBJECT_TO_JSVAL\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-OBJECT_TO_JSVAL\.html$', 'http://developer.mozilla.org/en/OBJECT_TO_JSVAL'), - redirect('^js/spidermonkey/apidoc/gen/api-PRIVATE_TO_JSVAL\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-PRIVATE_TO_JSVAL\.html$', 'http://developer.mozilla.org/en/PRIVATE_TO_JSVAL'), - redirect('^js/spidermonkey/apidoc/gen/api-STRING_TO_JSVAL\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/api-STRING_TO_JSVAL\.html$', 'http://developer.mozilla.org/en/STRING_TO_JSVAL'), - redirect('^js/spidermonkey/apidoc/gen/complete-toc-abc\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/complete-toc-abc\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/gen/complete-toc-grp\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/complete-toc-grp\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/gen/complete-toc\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/complete-toc\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/gen/complete\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/complete\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/gen/sidebar-toc\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/sidebar-toc\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/gen/sparse-toc-abc\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/sparse-toc-abc\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/gen/sparse-toc-grp\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/sparse-toc-grp\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/apidoc/gen/sparse-toc\.html$', + redirect(r'^js/spidermonkey/apidoc/gen/sparse-toc\.html$', 'http://developer.mozilla.org/en/JSAPI_Reference'), - redirect('^js/spidermonkey/gctips\.html$', + redirect(r'^js/spidermonkey/gctips\.html$', 'http://developer.mozilla.org/en/SpiderMonkey_Garbage_Collection_Tips'), - redirect('^mailman$', 'https://mail.mozilla.org'), - redirect('^mailnews/ABSyncClientDesign\.html$', + redirect(r'^mailman$', 'https://mail.mozilla.org'), + redirect(r'^mailnews/ABSyncClientDesign\.html$', 'https://developer.mozilla.org/en/Thunderbird/Address_book_sync_client_design'), - redirect('^mailnews/arch/ABSyncClientDesign\.html$', + redirect(r'^mailnews/arch/ABSyncClientDesign\.html$', 'https://developer.mozilla.org/en/Thunderbird/Address_book_sync_client_design'), - redirect('^mailnews/arch/accountmanager\.html$', + redirect(r'^mailnews/arch/accountmanager\.html$', 'https://developer.mozilla.org/en/Thunderbird/Using_the_Multiple_Accounts_API'), - redirect('^mailnews/arch/addrbook/hiddenprefs\.html$', + redirect(r'^mailnews/arch/addrbook/hiddenprefs\.html$', 'https://developer.mozilla.org/en/Thunderbird/Hidden_address_book_prefs'), - redirect('^mailnews/arch/compose-backend\.html$', + redirect(r'^mailnews/arch/compose-backend\.html$', 'https://developer.mozilla.org/en/Thunderbird/Mail_composition_back_end'), - redirect('^mailnews/arch/compose/cached\.html$', + redirect(r'^mailnews/arch/compose/cached\.html$', 'https://developer.mozilla.org/en/Thunderbird/Cached_compose_window_FAQ'), - redirect('^mailnews/arch/compose/hiddenprefs\.html$', + redirect(r'^mailnews/arch/compose/hiddenprefs\.html$', 'https://developer.mozilla.org/en/Thunderbird/Hidden_prefs'), - redirect('^mailnews/arch/events\.html$', + redirect(r'^mailnews/arch/events\.html$', 'https://developer.mozilla.org/en/Thunderbird/Mail_event_system'), - redirect('^mailnews/arch/hiddenprefs\.html$', + redirect(r'^mailnews/arch/hiddenprefs\.html$', 'https://developer.mozilla.org/en/Thunderbird/Hidden_prefs'), - redirect('^mailnews/arch/libmime-content-type-handlers\.html$', + redirect(r'^mailnews/arch/libmime-content-type-handlers\.html$', 'https://developer.mozilla.org/en/Thunderbird/libmime_content_type_handlers'), - redirect('^mailnews/arch/libmime-description\.html$', + redirect(r'^mailnews/arch/libmime-description\.html$', 'https://developer.mozilla.org/en/Thunderbird/The_libmime_module'), - redirect('^mailnews/arch/overview\.html$', + redirect(r'^mailnews/arch/overview\.html$', 'https://developer.mozilla.org/en/Thunderbird/Mail_client_architecture_overview'), - redirect('^mailnews/arch/rdf\.html$', + redirect(r'^mailnews/arch/rdf\.html$', 'https://developer.mozilla.org/en/Thunderbird/Mail_and_RDF'), - redirect('^mailnews/arch/spam/$', + redirect(r'^mailnews/arch/spam/$', 'https://developer.mozilla.org/en/Thunderbird/Spam_filtering'), - redirect('^mailnews/compose-backend\.html$', + redirect(r'^mailnews/compose-backend\.html$', 'https://developer.mozilla.org/en/Thunderbird/Mail_composition_back_end'), - redirect('^mailnews/libmime-content-type-handlers\.html$', + redirect(r'^mailnews/libmime-content-type-handlers\.html$', 'https://developer.mozilla.org/en/Thunderbird/libmime_content_type_handlers'), - redirect('^mailnews/libmime-description\.html$', + redirect(r'^mailnews/libmime-description\.html$', 'https://developer.mozilla.org/en/Thunderbird/The_libmime_module'), - redirect('^mailnews/review-mail\.html$', + redirect(r'^mailnews/review-mail\.html$', 'https://developer.mozilla.org/en/Mailnews_and_Mail_code_review_requirements'), - redirect('^mailnews/review\.html$', + redirect(r'^mailnews/review\.html$', 'https://developer.mozilla.org/en/Mailnews_and_Mail_code_review_requirements'), - redirect('^mirroring\.html$', 'http://www-archive.mozilla.org/mirroring.html'), - redirect('^mirrors\.html$', 'http://www-archive.mozilla.org/mirrors.html'), - redirect('^mission\.html$', '/mission/'), - redirect('^mozilla1\.x$', '/firefox/'), - redirect('^my-mozilla\.html$', '/'), - redirect('^newlayout/bugathon\.html$', 'http://developer.mozilla.org/en/Gecko_BugAThon'), - redirect('^newlayout/codestock$', '/docs/codestock99'), - redirect('^newlayout/codestock/slides\.html$', '/docs/codestock99/'), - redirect('^newlayout/faq\.html$', 'http://developer.mozilla.org/en/Gecko_FAQ'), - redirect('^newlayout/glossary\.html$', 'https://developer.mozilla.org/en/Gecko_Glossary'), - redirect('^newlayout/$', 'http://developer.mozilla.org/en/Gecko'), - redirect('^newlayout/regress\.html$', + redirect(r'^mirroring\.html$', 'http://www-archive.mozilla.org/mirroring.html'), + redirect(r'^mirrors\.html$', 'http://www-archive.mozilla.org/mirrors.html'), + redirect(r'^mission\.html$', '/mission/'), + redirect(r'^mozilla1\.x$', '/firefox/'), + redirect(r'^my-mozilla\.html$', '/'), + redirect(r'^newlayout/bugathon\.html$', 'http://developer.mozilla.org/en/Gecko_BugAThon'), + redirect(r'^newlayout/codestock$', '/docs/codestock99'), + redirect(r'^newlayout/codestock/slides\.html$', '/docs/codestock99/'), + redirect(r'^newlayout/faq\.html$', 'http://developer.mozilla.org/en/Gecko_FAQ'), + redirect(r'^newlayout/glossary\.html$', 'https://developer.mozilla.org/en/Gecko_Glossary'), + redirect(r'^newlayout/$', 'http://developer.mozilla.org/en/Gecko'), + redirect(r'^newlayout/regress\.html$', '/newlayout/doc/regression_tests.html'), - redirect('^newlayout/xml/$', 'http://developer.mozilla.org/en/XML_in_Mozilla'), - redirect('^newsfeeds\.html$', '/about/forums/'), - redirect('^nglayout$', 'https://developer.mozilla.org/en/Gecko'), - redirect('^NPL$', '/MPL/NPL/1.1/'), - redirect('^old-roadmap\.html$', 'https://wiki.mozilla.org/Roadmap_Scratchpad'), - redirect('^other-projects\.html$', '/projects/other-projects.html'), - redirect('^owners-js\.html$', 'https://wiki.mozilla.org/Modules'), - redirect('^owners\.html$', 'https://wiki.mozilla.org/Modules'), - redirect('^performance/tinderbox-tests\.html$', + redirect(r'^newlayout/xml/$', 'http://developer.mozilla.org/en/XML_in_Mozilla'), + redirect(r'^newsfeeds\.html$', '/about/forums/'), + redirect(r'^nglayout$', 'https://developer.mozilla.org/en/Gecko'), + redirect(r'^NPL$', '/MPL/NPL/1.1/'), + redirect(r'^old-roadmap\.html$', 'https://wiki.mozilla.org/Roadmap_Scratchpad'), + redirect(r'^other-projects\.html$', '/projects/other-projects.html'), + redirect(r'^owners-js\.html$', 'https://wiki.mozilla.org/Modules'), + redirect(r'^owners\.html$', 'https://wiki.mozilla.org/Modules'), + redirect(r'^performance/tinderbox-tests\.html$', 'http://wiki.mozilla.org/Performance:Tinderbox_Tests'), - redirect('^performance/leak-brownbag\.html$', + redirect(r'^performance/leak-brownbag\.html$', 'http://wiki.mozilla.org/Performance:Leak_Tools'), - redirect('^privacy-policy(\.html|/.*)?$', '/privacy/websites/'), - redirect('^projects/calendar/$', 'https://www.thunderbird.net/calendar/'), - redirect('^projects/calendar/holidays/$', 'https://www.thunderbird.net/calendar/holidays/'), + redirect(r'^privacy-policy(\.html|/.*)?$', '/privacy/websites/'), + redirect(r'^projects/calendar/$', 'https://www.thunderbird.net/calendar/'), + redirect(r'^projects/calendar/holidays/$', 'https://www.thunderbird.net/calendar/holidays/'), # bug 885799, 952429 redirect(r'^projects/calendar/holidays\.html$', 'https://www.thunderbird.net/calendar/holidays/'), - redirect('^products/camino/badges/$', 'http://caminobrowser.org/community/promotion/'), - redirect('^products/camino/features/searchCustomization\.html$', + redirect(r'^products/camino/badges/$', 'http://caminobrowser.org/community/promotion/'), + redirect(r'^products/camino/features/searchCustomization\.html$', 'http://caminobrowser.org/help/'), - redirect('^products/camino/features/tipsTricks\.html$', 'http://caminobrowser.org/help/'), - redirect('^products/camino/$', 'http://caminobrowser.org/'), - redirect('^products/camino/releases/0\.8\.1\.html$', + redirect(r'^products/camino/features/tipsTricks\.html$', 'http://caminobrowser.org/help/'), + redirect(r'^products/camino/$', 'http://caminobrowser.org/'), + redirect(r'^products/camino/releases/0\.8\.1\.html$', 'http://caminobrowser.org/releases/0.8.1/'), - redirect('^products/camino/releases/0\.8\.2\.html$', + redirect(r'^products/camino/releases/0\.8\.2\.html$', 'http://caminobrowser.org/releases/0.8.2/'), - redirect('^products/camino/releases/0\.8\.3\.html$', + redirect(r'^products/camino/releases/0\.8\.3\.html$', 'http://caminobrowser.org/releases/0.8.3/'), - redirect('^products/camino/releases/0\.8\.4\.html$', + redirect(r'^products/camino/releases/0\.8\.4\.html$', 'http://caminobrowser.org/releases/0.8.4/'), - redirect('^products/camino/releases/0\.8\.5\.html$', + redirect(r'^products/camino/releases/0\.8\.5\.html$', 'http://caminobrowser.org/releases/0.8.5/'), - redirect('^products/camino/releases/0\.8\.html$', 'http://caminobrowser.org/releases/0.8/'), - redirect('^products/camino/releases/0\.8b\.html$', 'http://caminobrowser.org/releases/0.8b/'), - redirect('^products/camino/releases/0\.9a1\.html$', + redirect(r'^products/camino/releases/0\.8\.html$', 'http://caminobrowser.org/releases/0.8/'), + redirect(r'^products/camino/releases/0\.8b\.html$', 'http://caminobrowser.org/releases/0.8b/'), + redirect(r'^products/camino/releases/0\.9a1\.html$', 'http://caminobrowser.org/releases/0.9a1/'), - redirect('^products/camino/releases/0\.9a2\.html$', + redirect(r'^products/camino/releases/0\.9a2\.html$', 'http://caminobrowser.org/releases/0.9a2/'), - redirect('^products/camino/releases/1\.0\.1\.html$', + redirect(r'^products/camino/releases/1\.0\.1\.html$', 'http://caminobrowser.org/releases/1.0.1/'), - redirect('^products/camino/releases/1\.0\.2\.html$', + redirect(r'^products/camino/releases/1\.0\.2\.html$', 'http://caminobrowser.org/releases/1.0.2/'), - redirect('^products/camino/releases/1\.0\.3\.html$', + redirect(r'^products/camino/releases/1\.0\.3\.html$', 'http://caminobrowser.org/releases/1.0.3/'), - redirect('^products/camino/releases/1\.0\.4\.html$', + redirect(r'^products/camino/releases/1\.0\.4\.html$', 'http://caminobrowser.org/releases/1.0.4/'), - redirect('^products/camino/releases/1\.0\.5\.html$', + redirect(r'^products/camino/releases/1\.0\.5\.html$', 'http://caminobrowser.org/releases/1.0.5/'), - redirect('^products/camino/releases/1\.0\.6\.html$', + redirect(r'^products/camino/releases/1\.0\.6\.html$', 'http://caminobrowser.org/releases/1.0.6/'), - redirect('^products/camino/releases/1\.0\.html$', 'http://caminobrowser.org/releases/1.0/'), - redirect('^products/camino/releases/1\.0a1\.html$', + redirect(r'^products/camino/releases/1\.0\.html$', 'http://caminobrowser.org/releases/1.0/'), + redirect(r'^products/camino/releases/1\.0a1\.html$', 'http://caminobrowser.org/releases/1.0a1/'), - redirect('^products/camino/releases/1\.0b1\.html$', + redirect(r'^products/camino/releases/1\.0b1\.html$', 'http://caminobrowser.org/releases/1.0b1/'), - redirect('^products/camino/releases/1\.0b2\.html$', + redirect(r'^products/camino/releases/1\.0b2\.html$', 'http://caminobrowser.org/releases/1.0b2/'), - redirect('^products/camino/releases/1\.0rc1\.html$', + redirect(r'^products/camino/releases/1\.0rc1\.html$', 'http://caminobrowser.org/releases/1.0rc1/'), - redirect('^products/camino/support/$', 'http://caminobrowser.org/help/'), - redirect('^products/choosing-products\.html$', '/projects/'), - redirect('^products/thunderbird/all-beta\.html$', '/thunderbird/all/'), - redirect('^products/thunderbird/all\.html$', '/thunderbird/all/'), - redirect('^products/thunderbird/global-inbox\.html$', + redirect(r'^products/camino/support/$', 'http://caminobrowser.org/help/'), + redirect(r'^products/choosing-products\.html$', '/projects/'), + redirect(r'^products/thunderbird/all-beta\.html$', '/thunderbird/all/'), + redirect(r'^products/thunderbird/all\.html$', '/thunderbird/all/'), + redirect(r'^products/thunderbird/global-inbox\.html$', 'http://kb.mozillazine.org/Global_Inbox'), - redirect('^products/thunderbird/junkmail\.html$', + redirect(r'^products/thunderbird/junkmail\.html$', 'http://kb.mozillazine.org/Junk_Mail_Controls'), - redirect('^products/thunderbird/message-grouping\.html$', + redirect(r'^products/thunderbird/message-grouping\.html$', 'http://kb.mozillazine.org/Message_Grouping'), - redirect('^products/thunderbird/privacy-protection\.html$', + redirect(r'^products/thunderbird/privacy-protection\.html$', 'http://kb.mozillazine.org/Privacy_basics_%28Thunderbird%29'), - redirect('^products/thunderbird/releases(/.*)?$', '/thunderbird/releases/'), - redirect('^products/thunderbird/rss\.html$', + redirect(r'^products/thunderbird/releases(/.*)?$', '/thunderbird/releases/'), + redirect(r'^products/thunderbird/rss\.html$', 'http://kb.mozillazine.org/RSS_basics_%28Thunderbird%29'), - redirect('^products/thunderbird/search-folders\.html$', + redirect(r'^products/thunderbird/search-folders\.html$', 'http://kb.mozillazine.org/Saved_Search'), - redirect('^products/thunderbird/sysreq\.html$', '/thunderbird/system-requirements/'), - redirect('^products/thunderbird(/.*)?$', '/thunderbird/'), - redirect('^profilemanager/isp-rdf-info\.txt$', 'https://developer.mozilla.org/docs/Isp_Data'), - redirect('^projects\.html$', 'https://www.mozilla.org/projects/'), - redirect('^projects/browsers\.html$', '/firefox/new/'), - redirect('^projects/bugzilla$', 'https://www.bugzilla.org'), - redirect('^projects/camino/damagedBookmarks\.html$', + redirect(r'^products/thunderbird/sysreq\.html$', '/thunderbird/system-requirements/'), + redirect(r'^products/thunderbird(/.*)?$', '/thunderbird/'), + redirect(r'^profilemanager/isp-rdf-info\.txt$', 'https://developer.mozilla.org/docs/Isp_Data'), + redirect(r'^projects\.html$', 'https://www.mozilla.org/projects/'), + redirect(r'^projects/browsers\.html$', '/firefox/new/'), + redirect(r'^projects/bugzilla$', 'https://www.bugzilla.org'), + redirect(r'^projects/camino/damagedBookmarks\.html$', 'http://wiki.caminobrowser.org/QA:Damaged_Bookmarks'), - redirect('^projects/camino/development\.html$', 'http://caminobrowser.org/contribute/'), - redirect('^projects/camino/docs/$', 'http://caminobrowser.org/help/'), - redirect('^projects/camino/docs/miscfeatures\.html$', + redirect(r'^projects/camino/development\.html$', 'http://caminobrowser.org/contribute/'), + redirect(r'^projects/camino/docs/$', 'http://caminobrowser.org/help/'), + redirect(r'^projects/camino/docs/miscfeatures\.html$', 'http://caminobrowser.org/documentation/'), - redirect('^projects/camino/docs/profileprefs\.html$', + redirect(r'^projects/camino/docs/profileprefs\.html$', 'http://caminobrowser.org/documentation/hiddenprefs/'), - redirect('^projects/camino/docs/proxies\.html$', + redirect(r'^projects/camino/docs/proxies\.html$', 'http://caminobrowser.org/documentation/proxy/'), - redirect('^projects/camino/feedback\.html$', 'http://caminobrowser.org/contact/'), - redirect('^projects/camino/homepage\.html$', 'http://caminobrowser.org/start/'), - redirect('^projects/camino/releasenotes\.html$', 'http://caminobrowser.org/releases/'), - redirect('^projects/camino/shortcuts\.html$', + redirect(r'^projects/camino/feedback\.html$', 'http://caminobrowser.org/contact/'), + redirect(r'^projects/camino/homepage\.html$', 'http://caminobrowser.org/start/'), + redirect(r'^projects/camino/releasenotes\.html$', 'http://caminobrowser.org/releases/'), + redirect(r'^projects/camino/shortcuts\.html$', 'http://caminobrowser.org/documentation/shortcuts/'), - redirect('^projects/camino/welcome\.html$', 'http://caminobrowser.org/welcome/'), - redirect('^projects/deerpark/alpha2\.html$', '/projects/firefox'), - redirect('^projects/deerpark/deerpark-icon\.png$', + redirect(r'^projects/camino/welcome\.html$', 'http://caminobrowser.org/welcome/'), + redirect(r'^projects/deerpark/alpha2\.html$', '/projects/firefox'), + redirect(r'^projects/deerpark/deerpark-icon\.png$', '/images/deerpark-icon.png'), - redirect('^projects/distros\.html$', '/projects/mozilla-based.html'), - redirect('^projects/embedding/faq\.html$', + redirect(r'^projects/distros\.html$', '/projects/mozilla-based.html'), + redirect(r'^projects/embedding/faq\.html$', 'http://developer.mozilla.org/en/Mozilla_Embedding_FAQ'), - redirect('^projects/firebird/0\.1-release-notes\.html$', + redirect(r'^projects/firebird/0\.1-release-notes\.html$', '/products/firefox/releases/0.1.html'), - redirect('^projects/firebird/0\.2-release-notes\.html$', + redirect(r'^projects/firebird/0\.2-release-notes\.html$', '/products/firefox/releases/0.2.html'), - redirect('^projects/firebird/0\.3-release-notes\.html$', + redirect(r'^projects/firebird/0\.3-release-notes\.html$', '/products/firefox/releases/0.3.html'), - redirect('^projects/firebird/0\.4-release-notes\.html$', + redirect(r'^projects/firebird/0\.4-release-notes\.html$', '/products/firefox/releases/0.4.html'), - redirect('^projects/firebird/0\.5-release-notes\.html$', + redirect(r'^projects/firebird/0\.5-release-notes\.html$', '/products/firefox/releases/0.5.html'), - redirect('^projects/firebird/0\.6-release-notes\.html$', + redirect(r'^projects/firebird/0\.6-release-notes\.html$', '/products/firefox/releases/0.6.html'), - redirect('^projects/firebird/0\.6\.1-release-notes\.html$', + redirect(r'^projects/firebird/0\.6\.1-release-notes\.html$', '/products/firefox/releases/0.6.1.html'), - redirect('^projects/firebird/0\.7-release-notes\.html$', + redirect(r'^projects/firebird/0\.7-release-notes\.html$', '/products/firefox/releases/0.7.html'), - redirect('^projects/firebird/0\.7\.1-release-notes\.html$', + redirect(r'^projects/firebird/0\.7\.1-release-notes\.html$', '/products/firefox/releases/0.7.1.html'), - redirect('^projects/firebird/build\.html$', + redirect(r'^projects/firebird/build\.html$', '/projects/firefox/build.html'), - redirect('^projects/firebird/charter\.html$', + redirect(r'^projects/firebird/charter\.html$', '/projects/firefox/charter.html'), - redirect('^projects/firebird/$', '/projects/firefox/'), - redirect('^projects/firebird/installer/build\.html$', + redirect(r'^projects/firebird/$', '/projects/firefox/'), + redirect(r'^projects/firebird/installer/build\.html$', '/projects/firefox/installer/build.html'), - redirect('^projects/firebird/qa/downloads\.html$', + redirect(r'^projects/firebird/qa/downloads\.html$', '/projects/firefox/qa/downloads.html'), - redirect('^projects/firebird/qa/$', '/projects/firefox/qa/'), - redirect('^projects/firebird/release-notes\.html$', + redirect(r'^projects/firebird/qa/$', '/projects/firefox/qa/'), + redirect(r'^projects/firebird/release-notes\.html$', '/products/firefox/releases/'), - redirect('^projects/firebird/releases\.html$', 'http://texturizer.net/firebird/download.html'), - redirect('^projects/firebird/review\.html$', + redirect(r'^projects/firebird/releases\.html$', 'http://texturizer.net/firebird/download.html'), + redirect(r'^projects/firebird/review\.html$', '/projects/firefox/review.html'), - redirect('^projects/firebird/roadmap\.html$', + redirect(r'^projects/firebird/roadmap\.html$', '/projects/firefox/roadmap.html'), - redirect('^projects/firebird/ue/downloads/$', + redirect(r'^projects/firebird/ue/downloads/$', '/projects/firefox/ue/downloads/'), - redirect('^projects/firebird/ue/$', '/projects/firefox/ue/'), - redirect('^projects/firebird/ue/installer/$', + redirect(r'^projects/firebird/ue/$', '/projects/firefox/ue/'), + redirect(r'^projects/firebird/ue/installer/$', '/projects/firefox/ue/installer/'), - redirect('^projects/firebird/ue/migration/$', + redirect(r'^projects/firebird/ue/migration/$', '/projects/firefox/ue/migration/'), - redirect('^projects/firebird/ue/philosophy/realities\.html$', + redirect(r'^projects/firebird/ue/philosophy/realities\.html$', '/projects/firefox/ue/philosophy/realities.html'), - redirect('^projects/firebird/why/$', '/firefox/'), - redirect('^projects/firefox/extensions/em-changes\.html$', + redirect(r'^projects/firebird/why/$', '/firefox/'), + redirect(r'^projects/firefox/extensions/em-changes\.html$', 'http://developer.mozilla.org/en/Enhanced_Extension_Installation'), - redirect('^projects/firefox/extensions/update\.html$', + redirect(r'^projects/firefox/extensions/update\.html$', 'http://developer.mozilla.org/en/Extension_Versioning%2C_Update_and_Compatibility'), - redirect('^projects/firefox/l10n/$', 'https://wiki.mozilla.org/L10n'), - redirect('^projects/firefox/l10n/installer-encodings\.html$', + redirect(r'^projects/firefox/l10n/$', 'https://wiki.mozilla.org/L10n'), + redirect(r'^projects/firefox/l10n/installer-encodings\.html$', 'http://developer.mozilla.org/en/Encodings_for_localization_files'), - redirect('^projects/firefox/l10n/l10n-step-by-step\.html$', 'https://wiki.mozilla.org/L10n'), - redirect('^projects/firefox/l10n/localize-release\.html$', 'https://wiki.mozilla.org/L10n'), - redirect('^projects/firefox/l10n/using-cvs\.html$', 'https://wiki.mozilla.org/L10n'), - redirect('^projects/foundation/$', '/foundation/'), - redirect('^projects/inspector/faq\.html$', + redirect(r'^projects/firefox/l10n/l10n-step-by-step\.html$', 'https://wiki.mozilla.org/L10n'), + redirect(r'^projects/firefox/l10n/localize-release\.html$', 'https://wiki.mozilla.org/L10n'), + redirect(r'^projects/firefox/l10n/using-cvs\.html$', 'https://wiki.mozilla.org/L10n'), + redirect(r'^projects/foundation/$', '/foundation/'), + redirect(r'^projects/inspector/faq\.html$', 'https://developer.mozilla.org/en/DOM_Inspector_FAQ'), - redirect('^projects/intl/xul-how2l10n\.html$', + redirect(r'^projects/intl/xul-how2l10n\.html$', '/projects/l10n/mlp_status.html'), - redirect('^projects/intl/xul-l10n\.html$', + redirect(r'^projects/intl/xul-l10n\.html$', '/projects/l10n/xul-l10n.html'), - redirect('^projects/intl/xul-styleguide\.html$', + redirect(r'^projects/intl/xul-styleguide\.html$', '/projects/l10n/xul-styleguide.html'), - redirect('^projects/intl/fonts\.html$', 'http://wiki.mozilla.org/Font_selection'), - redirect('^projects/l10n/customizable-code\.html$', + redirect(r'^projects/intl/fonts\.html$', 'http://wiki.mozilla.org/Font_selection'), + redirect(r'^projects/l10n/customizable-code\.html$', 'https://developer.mozilla.org/en/Writing_localizable_code'), - redirect('^projects/l10n/mlp_docs\.html$', + redirect(r'^projects/l10n/mlp_docs\.html$', 'https://wiki.mozilla.org/L10n:Localization_Process'), - redirect('^projects/l10n/mlp_howto_Firefox\.html$', + redirect(r'^projects/l10n/mlp_howto_Firefox\.html$', 'https://wiki.mozilla.org/L10n:Localization_Process'), - redirect('^projects/l10n/mlp_status\.html$', + redirect(r'^projects/l10n/mlp_status\.html$', 'https://wiki.mozilla.org/L10n:Localization_Teams'), - redirect('^projects/l10n/mlp_tools\.html$', 'https://wiki.mozilla.org/L10n:Tools'), - redirect('^projects/list\.html$', '/projects/'), - redirect('^projects/marketing/banners\.html$', + redirect(r'^projects/l10n/mlp_tools\.html$', 'https://wiki.mozilla.org/L10n:Tools'), + redirect(r'^projects/list\.html$', '/projects/'), + redirect(r'^projects/marketing/banners\.html$', 'http://www.spreadfirefox.com/affiliates/homepage'), - redirect('^projects/marketing/buttons\.html$', + redirect(r'^projects/marketing/buttons\.html$', 'http://www.spreadfirefox.com/affiliates/homepage'), - redirect('^projects/mathml/authoring\.html$', + redirect(r'^projects/mathml/authoring\.html$', 'https://developer.mozilla.org/en/Mozilla_MathML_Project/Authoring'), - redirect('^projects/mathml/start\.xhtml$', '/projects/mathml/start.html'), - redirect('^projects/mathml/start-hebrew\.xhtml$', '/projects/mathml/start-hebrew.html'), - redirect('^projects/mathml/start-thai\.xhtml$', '/projects/mathml/start-thai.html'), - redirect('^projects/mathml/update\.html$', + redirect(r'^projects/mathml/start\.xhtml$', '/projects/mathml/start.html'), + redirect(r'^projects/mathml/start-hebrew\.xhtml$', '/projects/mathml/start-hebrew.html'), + redirect(r'^projects/mathml/start-thai\.xhtml$', '/projects/mathml/start-thai.html'), + redirect(r'^projects/mathml/update\.html$', 'https://developer.mozilla.org/en/Mozilla_MathML_Project/Status'), - redirect('^projects/mathml/demo/basics\.xhtml$', '/projects/mathml/demo/basics.html'), - redirect('^projects/mathml/demo/extras\.xhtml$', '/projects/mathml/demo/extras.html'), - redirect('^projects/mathml/demo/mfrac\.xhtml$', '/projects/mathml/demo/mfrac.html'), - redirect('^projects/mathml/demo/mmultiscripts\.xhtml$', + redirect(r'^projects/mathml/demo/basics\.xhtml$', '/projects/mathml/demo/basics.html'), + redirect(r'^projects/mathml/demo/extras\.xhtml$', '/projects/mathml/demo/extras.html'), + redirect(r'^projects/mathml/demo/mfrac\.xhtml$', '/projects/mathml/demo/mfrac.html'), + redirect(r'^projects/mathml/demo/mmultiscripts\.xhtml$', '/projects/mathml/demo/mmultiscripts.html'), - redirect('^projects/mathml/demo/mo\.xhtml$', '/projects/mathml/demo/mo.html'), - redirect('^projects/mathml/demo/mspace\.xhtml$', '/projects/mathml/demo/mspace.html'), - redirect('^projects/mathml/demo/mtable\.xhtml$', '/projects/mathml/demo/mtable.html'), - redirect('^projects/mathml/demo/texvsmml\.xhtml$', '/projects/mathml/demo/texvsmml.html'), - redirect('^projects/mathml/demo/roots\.xhtml$', '/projects/mathml/demo/roots.html'), - redirect('^projects/minefield/releases/$', '/projects/minefield/'), - redirect('^projects/mstone$', 'http://mstone.sourceforge.net/'), - redirect('^projects/other-projects\.html$', + redirect(r'^projects/mathml/demo/mo\.xhtml$', '/projects/mathml/demo/mo.html'), + redirect(r'^projects/mathml/demo/mspace\.xhtml$', '/projects/mathml/demo/mspace.html'), + redirect(r'^projects/mathml/demo/mtable\.xhtml$', '/projects/mathml/demo/mtable.html'), + redirect(r'^projects/mathml/demo/texvsmml\.xhtml$', '/projects/mathml/demo/texvsmml.html'), + redirect(r'^projects/mathml/demo/roots\.xhtml$', '/projects/mathml/demo/roots.html'), + redirect(r'^projects/minefield/releases/$', '/projects/minefield/'), + redirect(r'^projects/mstone$', 'http://mstone.sourceforge.net/'), + redirect(r'^projects/other-projects\.html$', '/projects/mozilla-based.html'), - redirect('^projects/phoenix/0\.1-release-notes\.html$', + redirect(r'^projects/phoenix/0\.1-release-notes\.html$', '/products/firefox/releases/0.1.html'), - redirect('^projects/phoenix/0\.2-release-notes\.html$', + redirect(r'^projects/phoenix/0\.2-release-notes\.html$', '/products/firefox/releases/0.2.html'), - redirect('^projects/phoenix/0\.3-release-notes\.html$', + redirect(r'^projects/phoenix/0\.3-release-notes\.html$', '/products/firefox/releases/0.3.html'), - redirect('^projects/phoenix/0\.4-release-notes\.html$', + redirect(r'^projects/phoenix/0\.4-release-notes\.html$', '/products/firefox/releases/0.5.html'), - redirect('^projects/phoenix/0\.5-release-notes\.html$', + redirect(r'^projects/phoenix/0\.5-release-notes\.html$', '/products/firefox/releases/0.5.html'), - redirect('^projects/phoenix/0\.6-release-notes\.html$', + redirect(r'^projects/phoenix/0\.6-release-notes\.html$', '/products/firefox/releases/0.6.html'), - redirect('^projects/phoenix/extensions/$', 'http://texturizer.net/firefox/extensions/'), - redirect('^projects/phoenix/$', '/projects/firefox/'), - redirect('^projects/phoenix/phoenix-advantages\.html$', + redirect(r'^projects/phoenix/extensions/$', 'http://texturizer.net/firefox/extensions/'), + redirect(r'^projects/phoenix/$', '/projects/firefox/'), + redirect(r'^projects/phoenix/phoenix-advantages\.html$', '/products/firefox/releases/'), - redirect('^projects/phoenix/phoenix-roadmap\.html$', + redirect(r'^projects/phoenix/phoenix-roadmap\.html$', '/projects/firefox/roadmap.html'), - redirect('^projects/phoenix/releases\.html$', '/products/firefox/'), - redirect('^projects/phoenix/why/$', '/firefox/'), - redirect('^projects/sunbird$', '/projects/calendar/sunbird/'), - redirect('^projects/svg/$', 'https://developer.mozilla.org/en/SVG'), - redirect('^projects/svg/status\.html$', 'https://developer.mozilla.org/en/Mozilla_SVG_Status'), - redirect('^projects/svg/build\.html$', + redirect(r'^projects/phoenix/releases\.html$', '/products/firefox/'), + redirect(r'^projects/phoenix/why/$', '/firefox/'), + redirect(r'^projects/sunbird$', '/projects/calendar/sunbird/'), + redirect(r'^projects/svg/$', 'https://developer.mozilla.org/en/SVG'), + redirect(r'^projects/svg/status\.html$', 'https://developer.mozilla.org/en/Mozilla_SVG_Status'), + redirect(r'^projects/svg/build\.html$', 'https://developer.mozilla.org/en/Developer_Guide/Build_Instructions'), - redirect('^projects/thunderbird/?$', 'https://wiki.mozilla.org/Thunderbird:Home_Page'), - redirect('^projects/thunderbird/build\.html$', + redirect(r'^projects/thunderbird/?$', 'https://wiki.mozilla.org/Thunderbird:Home_Page'), + redirect(r'^projects/thunderbird/build\.html$', 'http://developer.mozilla.org/docs/Build_Documentation'), - redirect('^projects/ui/accessibility/$', '/access/'), - redirect('^projects/ui/accessibility/access-today\.html$', + redirect(r'^projects/ui/accessibility/$', '/access/'), + redirect(r'^projects/ui/accessibility/access-today\.html$', '/access/today'), - redirect('^projects/ui/accessibility/slides/moz_accslides\.html$', + redirect(r'^projects/ui/accessibility/slides/moz_accslides\.html$', '/access/slideshow/'), - redirect('^projects/ui/accessibility/slides$', '/access/slideshow'), - redirect('^projects/ui/accessibility/moz_accslides_text_version\.html$', + redirect(r'^projects/ui/accessibility/slides$', '/access/slideshow'), + redirect(r'^projects/ui/accessibility/moz_accslides_text_version\.html$', '/access/slideshow/text'), - redirect('^projects/ui/accessibility/index-users\.html$', + redirect(r'^projects/ui/accessibility/index-users\.html$', '/access/users'), - redirect('^projects/ui/accessibility/Accessibility_Features_in_Mozilla\.html$', + redirect(r'^projects/ui/accessibility/Accessibility_Features_in_Mozilla\.html$', '/access/features'), - redirect('^projects/ui/accessibility/index-procurement\.html$', + redirect(r'^projects/ui/accessibility/index-procurement\.html$', '/access/evaluators'), - redirect('^projects/ui/accessibility/index-authors\.html$', + redirect(r'^projects/ui/accessibility/index-authors\.html$', '/access/authors'), - redirect('^projects/ui/accessibility/dynamic-accessibility\.html$', + redirect(r'^projects/ui/accessibility/dynamic-accessibility\.html$', '/access/dynamic-content'), - redirect('^projects/ui/accessibility/index-atvendors\.html$', + redirect(r'^projects/ui/accessibility/index-atvendors\.html$', '/access/at-vendors'), - redirect('^projects/ui/accessibility/index-qa-ui\.html$', '/access/qa'), - redirect('^projects/ui/accessibility/index-frontend-coders\.html$', + redirect(r'^projects/ui/accessibility/index-qa-ui\.html$', '/access/qa'), + redirect(r'^projects/ui/accessibility/index-frontend-coders\.html$', '/access/ui-developers'), - redirect('^projects/ui/accessibility/index-core-hackers\.html$', + redirect(r'^projects/ui/accessibility/index-core-hackers\.html$', '/access/core-developers'), - redirect('^projects/ui/accessibility/index-external-developers\.html$', + redirect(r'^projects/ui/accessibility/index-external-developers\.html$', '/access/external-developers'), - redirect('^projects/ui/accessibility/toolkit-checklist\.html$', + redirect(r'^projects/ui/accessibility/toolkit-checklist\.html$', '/access/toolkit-checklist'), - redirect('^projects/ui/accessibility/msaa-server-impl\.html$', + redirect(r'^projects/ui/accessibility/msaa-server-impl\.html$', '/access/windows/msaa-server'), - redirect('^projects/ui/accessibility/accessible-xul-authoring\.html$', + redirect(r'^projects/ui/accessibility/accessible-xul-authoring\.html$', '/access/xul-guidelines'), - redirect('^projects/ui/accessibility/window-eyes-status\.html$', + redirect(r'^projects/ui/accessibility/window-eyes-status\.html$', '/access/windows/window-eyes'), - redirect('^projects/ui/accessibility/typeaheadfind\.html$', + redirect(r'^projects/ui/accessibility/typeaheadfind\.html$', '/access/type-ahead'), - redirect('^projects/ui/accessibility/planning-ahead-for-accessibility\.html$', + redirect(r'^projects/ui/accessibility/planning-ahead-for-accessibility\.html$', '/access/planning'), - redirect('^projects/ui/accessibility/zoomtext-status\.html$', + redirect(r'^projects/ui/accessibility/zoomtext-status\.html$', '/access/windows/zoomtext'), - redirect('^projects/ui/accessibility/keyboard-status\.html$', + redirect(r'^projects/ui/accessibility/keyboard-status\.html$', '/access/keyboard/testing'), - redirect('^projects/ui/accessibility/vendors-win\.html$', + redirect(r'^projects/ui/accessibility/vendors-win\.html$', '/access/windows/at-apis'), - redirect('^projects/ui/accessibility/section508\.html$', + redirect(r'^projects/ui/accessibility/section508\.html$', '/access/section508'), - redirect('^projects/ui/accessibility/w3c-uaag\.html$', + redirect(r'^projects/ui/accessibility/w3c-uaag\.html$', '/access/w3c-uaag'), - redirect('^projects/ui/accessibility/span-checkbox\.html$', + redirect(r'^projects/ui/accessibility/span-checkbox\.html$', '/access/samples/span-checkbox.html'), - redirect('^projects/ui/accessibility/ISimpleDOMNode\.idl$', + redirect(r'^projects/ui/accessibility/ISimpleDOMNode\.idl$', 'https://dxr.mozilla.org/seamonkey/source/' 'accessible/public/msaa/ISimpleDOMNode.idl?raw=1'), - redirect('^projects/ui/accessibility/ISimpleDOMText\.idl$', + redirect(r'^projects/ui/accessibility/ISimpleDOMText\.idl$', 'https://dxr.mozilla.org/seamonkey/source/' 'accessible/public/msaa/ISimpleDOMText.idl?raw=1'), - redirect('^projects/ui/accessibility/ISimpleDOMDocument\.idl$', + redirect(r'^projects/ui/accessibility/ISimpleDOMDocument\.idl$', 'https://dxr.mozilla.org/seamonkey/source/' 'accessible/public/msaa/ISimpleDOMDocument.idl?raw=1'), - redirect('^projects/ui/accessibility/accesskey\.html$', + redirect(r'^projects/ui/accessibility/accesskey\.html$', '/access/keyboard/accesskey'), - redirect('^projects/ui/accessibility/mozkeyintro\.html$', + redirect(r'^projects/ui/accessibility/mozkeyintro\.html$', '/access/keyboard/'), - redirect('^projects/ui/accessibility/Javascript-nsIAccessible\.html$', + redirect(r'^projects/ui/accessibility/Javascript-nsIAccessible\.html$', '/access/samples/js-nsIAccessible'), - redirect('^projects/ui/accessibility/Javascript-nsIAccessible\.js$', + redirect(r'^projects/ui/accessibility/Javascript-nsIAccessible\.js$', '/access/samples/js-nsIAccessible.js'), - redirect('^projects/ui/accessibility/mozkeyplan\.html$', + redirect(r'^projects/ui/accessibility/mozkeyplan\.html$', '/access/keyboard/interactive'), - redirect('^projects/ui/accessibility/mozkeylist\.html$', + redirect(r'^projects/ui/accessibility/mozkeylist\.html$', '/access/keyboard/mozilla'), - redirect('^projects/ui/accessibility/mozkeyboard\.html$', + redirect(r'^projects/ui/accessibility/mozkeyboard\.html$', '/access/keyboard/layout'), - redirect('^projects/ui/accessibility/Javascript-nsIAccessible-notes\.html$', + redirect(r'^projects/ui/accessibility/Javascript-nsIAccessible-notes\.html$', '/access/samples/js-nsIAccessible-notes'), - redirect('^projects/ui/accessibility/accessible-architecture\.html$', + redirect(r'^projects/ui/accessibility/accessible-architecture\.html$', '/access/architecture'), - redirect('^projects/ui/accessibility/accessible-events\.html$', + redirect(r'^projects/ui/accessibility/accessible-events\.html$', '/access/event-flow'), - redirect('^projects/ui/accessibility/cross-ref-apis\.html$', + redirect(r'^projects/ui/accessibility/cross-ref-apis\.html$', '/access/platform-apis'), - redirect('^projects/ui/accessibility/resources\.html$', + redirect(r'^projects/ui/accessibility/resources\.html$', '/access/resources'), - redirect('^projects/ui/accessibility/access-mozilla\.png$', + redirect(r'^projects/ui/accessibility/access-mozilla\.png$', '/access/access-mozilla.png'), - redirect('^projects/ui/accessibility/powerbraille\.jpg$', + redirect(r'^projects/ui/accessibility/powerbraille\.jpg$', '/access/powerbraille.jpg'), - redirect('^projects/ui/accessibility/vpduo2\.jpg$', + redirect(r'^projects/ui/accessibility/vpduo2\.jpg$', '/access/vpduo2.jpg'), - redirect('^projects/ui/accessibility/qa/taf_acceptance\.html$', + redirect(r'^projects/ui/accessibility/qa/taf_acceptance\.html$', '/access/type-ahead/basic'), - redirect('^projects/ui/accessibility/qa/taf_functional\.html$', + redirect(r'^projects/ui/accessibility/qa/taf_functional\.html$', '/access/type-ahead/full'), - redirect('^projects/ui/accessibility/qa/taf_qa\.html$', + redirect(r'^projects/ui/accessibility/qa/taf_qa\.html$', '/access/type-ahead/testing'), - redirect('^projects/ui/accessibility/tabindex\.html$', + redirect(r'^projects/ui/accessibility/tabindex\.html$', '/access/keyboard/tabindex'), - redirect('^projects/ui/accessibility/embedaccess\.html$', + redirect(r'^projects/ui/accessibility/embedaccess\.html$', '/access/prefs-and-apis'), - redirect('^projects/ui/accessibility/accessible-hierarchy\.html$', + redirect(r'^projects/ui/accessibility/accessible-hierarchy\.html$', '/projects/accessibility/images/accessible-hierarchy.jpg'), - redirect('^projects/ui/accessibility/unix/$', '/access/unix/'), - redirect('^projects/ui/accessibility/unix/faq\.html$', + redirect(r'^projects/ui/accessibility/unix/$', '/access/unix/'), + redirect(r'^projects/ui/accessibility/unix/faq\.html$', '/access/unix/faq'), - redirect('^projects/ui/accessibility/unix/introduction\.html$', + redirect(r'^projects/ui/accessibility/unix/introduction\.html$', '/access/unix/team/'), - redirect('^projects/ui/accessibility/unix/photos/$', + redirect(r'^projects/ui/accessibility/unix/photos/$', '/access/unix/team/photos'), - redirect('^projects/ui/accessibility/unix/photos$', '/access/unix/team'), - redirect('^projects/ui/accessibility/unix/architecture\.html$', + redirect(r'^projects/ui/accessibility/unix/photos$', '/access/unix/team'), + redirect(r'^projects/ui/accessibility/unix/architecture\.html$', '/access/unix/architecture'), - redirect('^projects/xmlextras/$', 'http://developer.mozilla.org/en/XML_Extras'), - redirect('^raptor/$', 'http://developer.mozilla.org/en/Gecko'), - redirect('^README-cvs\.html$', '/contribute/writing/cvs'), - redirect('^README-style\.html$', '/contribute/writing/guidelines'), - redirect('^rdf/50-words\.html$', 'http://developer.mozilla.org/en/RDF_in_Fifty_Words_or_Less'), - redirect('^rdf/doc/aggregate\.html$', + redirect(r'^projects/xmlextras/$', 'http://developer.mozilla.org/en/XML_Extras'), + redirect(r'^raptor/$', 'http://developer.mozilla.org/en/Gecko'), + redirect(r'^README-cvs\.html$', '/contribute/writing/cvs'), + redirect(r'^README-style\.html$', '/contribute/writing/guidelines'), + redirect(r'^rdf/50-words\.html$', 'http://developer.mozilla.org/en/RDF_in_Fifty_Words_or_Less'), + redirect(r'^rdf/doc/aggregate\.html$', 'http://developer.mozilla.org/en/Aggregating_the_In-Memory_Datasource'), - redirect('^rdf/doc/faq\.html$', 'http://developer.mozilla.org/en/RDF_in_Mozilla_FAQ'), - redirect('^releases/cvstags\.html$', 'http://developer.mozilla.org/en/CVS_Tags'), - redirect('^releases/faq\.html$', '/projects/'), - redirect('^releases/$', '/projects/'), - redirect('^releases/mozilla1\.8b$', '/releases/mozilla1.8b1'), - redirect('^releases/stable\.html$', '/projects/'), - redirect('^report\.html$', 'http://bugzilla.mozilla.org/enter_bug.cgi?format=guided'), - redirect('^roadmap\.html$', 'https://wiki.mozilla.org/Roadmap_Scratchpad'), - redirect('^scriptable/agnostic\.html$', + redirect(r'^rdf/doc/faq\.html$', 'http://developer.mozilla.org/en/RDF_in_Mozilla_FAQ'), + redirect(r'^releases/cvstags\.html$', 'http://developer.mozilla.org/en/CVS_Tags'), + redirect(r'^releases/faq\.html$', '/projects/'), + redirect(r'^releases/$', '/projects/'), + redirect(r'^releases/mozilla1\.8b$', '/releases/mozilla1.8b1'), + redirect(r'^releases/stable\.html$', '/projects/'), + redirect(r'^report\.html$', 'http://bugzilla.mozilla.org/enter_bug.cgi?format=guided'), + redirect(r'^roadmap\.html$', 'https://wiki.mozilla.org/Roadmap_Scratchpad'), + redirect(r'^scriptable/agnostic\.html$', 'http://wiki.mozilla.org/Roadmap_for_language_agnostic_scripting_support'), - redirect('^scriptable/avoiding-leaks\.html$', + redirect(r'^scriptable/avoiding-leaks\.html$', 'http://developer.mozilla.org/en/Using_XPCOM_in_JavaScript_without_leaking'), - redirect('^scriptable/components_object\.html$', + redirect(r'^scriptable/components_object\.html$', 'http://developer.mozilla.org/en/Components_object'), - redirect('^scriptable/XPCShell\.html$', 'https://developer.mozilla.org/en/XPCShell_Reference'), - redirect('^scriptable/xpjs-components\.html$', + redirect(r'^scriptable/XPCShell\.html$', 'https://developer.mozilla.org/en/XPCShell_Reference'), + redirect(r'^scriptable/xpjs-components\.html$', 'http://developer.mozilla.org/en/XPJS_Components_Proposal'), - redirect('^scriptable/xptcall-faq\.html$', 'http://developer.mozilla.org/en/xptcall_FAQ'), - redirect('^search\.html$', '/'), - redirect('^sitemap\.html$', '/'), - redirect('^source-code\.html$', + redirect(r'^scriptable/xptcall-faq\.html$', 'http://developer.mozilla.org/en/xptcall_FAQ'), + redirect(r'^search\.html$', '/'), + redirect(r'^sitemap\.html$', '/'), + redirect(r'^source-code\.html$', 'http://developer.mozilla.org/en/Download_Mozilla_Source_Code'), - redirect('^source\.html$', 'http://developer.mozilla.org/en/Download_Mozilla_Source_Code'), - redirect('^status/minutes\.html$', 'https://wiki.mozilla.org/WeeklyUpdates'), - redirect('^store$', 'https://store.mozilla.org'), - redirect('^testdrivers$', 'http://wiki.mozilla.org/B2G_Testdrivers_Program'), + redirect(r'^source\.html$', 'http://developer.mozilla.org/en/Download_Mozilla_Source_Code'), + redirect(r'^status/minutes\.html$', 'https://wiki.mozilla.org/WeeklyUpdates'), + redirect(r'^store$', 'https://store.mozilla.org'), + redirect(r'^testdrivers$', 'http://wiki.mozilla.org/B2G_Testdrivers_Program'), # bug 1124038 redirect(r'^thunderbird/organizations/(?:all-esr\.html|faq/?)$', 'https://www.thunderbird.net/organizations/'), # bug 1123399, 1150649 @@ -2040,7 +2040,7 @@ redirectpatterns = ( redirect(r'^thunderbird/about/legal/eula/thunderbird', 'legal.eula.thunderbird-1.5-eula'), # bug 1204579 - redirect(r'^thunderbird/2.0.0.0/eula/?$', 'legal.eula.thunderbird-2-eula'), + redirect(r'^thunderbird/2\.0\.0\.0/eula/?$', 'legal.eula.thunderbird-2-eula'), redirect(r'^thunderbird/about/legal/?$', 'legal.terms.mozilla'), redirect(r'^thunderbird/about(/mission)?/?$', 'https://wiki.mozilla.org/Thunderbird'), @@ -2060,49 +2060,49 @@ redirectpatterns = ( redirect(r'^thunderbird/(?P[^/]+)/system-requirements/$', 'https://www.thunderbird.net/thunderbird/{version}/system-requirements/'), redirect(r'^thunderbird/?(?P.*)', 'https://www.thunderbird.net/{path}'), - redirect('^tinderbox\.html$', 'http://developer.mozilla.org/en/Tinderbox'), - redirect('^tools\.html$', 'http://developer.mozilla.org/en/Mozilla_Development_Tools'), - redirect('^university/courses\.html$', 'http://education.mozilla.org'), - redirect('^university/courses/appdev1/overview\.html$', 'http://education.mozilla.org'), - redirect('^university/courses/appdev2/overview\.html$', 'http://education.mozilla.org'), - redirect('^university/demos/xslt/demo_topic1\.xml$', 'http://education.mozilla.org'), - redirect('^university/demos/xslt/demo_topic2\.xml$', 'http://education.mozilla.org'), - redirect('^university/demos/xslt/demo_topic3\.xml$', 'http://education.mozilla.org'), - redirect('^university/demos/xslt/demo_topic4\.xml$', 'http://education.mozilla.org'), - redirect('^university/demos/xslt/demo_topic5\.xml$', 'http://education.mozilla.org'), - redirect('^university/demos/xslt/demo\.xsl$', 'http://education.mozilla.org'), - redirect('^university/HOF\.html$', '/projects/mozilla-based.html'), - redirect('^university/hof\.xml$', '/projects/mozilla-based.html'), - redirect('^university/resource_map\.html$', 'http://education.mozilla.org'), - redirect('^university/scc_roadshow_outline\.html$', 'http://education.mozilla.org'), - redirect('^unix/debugging-faq\.html$', + redirect(r'^tinderbox\.html$', 'http://developer.mozilla.org/en/Tinderbox'), + redirect(r'^tools\.html$', 'http://developer.mozilla.org/en/Mozilla_Development_Tools'), + redirect(r'^university/courses\.html$', 'http://education.mozilla.org'), + redirect(r'^university/courses/appdev1/overview\.html$', 'http://education.mozilla.org'), + redirect(r'^university/courses/appdev2/overview\.html$', 'http://education.mozilla.org'), + redirect(r'^university/demos/xslt/demo_topic1\.xml$', 'http://education.mozilla.org'), + redirect(r'^university/demos/xslt/demo_topic2\.xml$', 'http://education.mozilla.org'), + redirect(r'^university/demos/xslt/demo_topic3\.xml$', 'http://education.mozilla.org'), + redirect(r'^university/demos/xslt/demo_topic4\.xml$', 'http://education.mozilla.org'), + redirect(r'^university/demos/xslt/demo_topic5\.xml$', 'http://education.mozilla.org'), + redirect(r'^university/demos/xslt/demo\.xsl$', 'http://education.mozilla.org'), + redirect(r'^university/HOF\.html$', '/projects/mozilla-based.html'), + redirect(r'^university/hof\.xml$', '/projects/mozilla-based.html'), + redirect(r'^university/resource_map\.html$', 'http://education.mozilla.org'), + redirect(r'^university/scc_roadshow_outline\.html$', 'http://education.mozilla.org'), + redirect(r'^unix/debugging-faq\.html$', 'http://developer.mozilla.org/en/Debugging_Mozilla_on_Linux_FAQ'), - redirect('^unix/solaris-build\.html$', + redirect(r'^unix/solaris-build\.html$', 'http://developer.mozilla.org/en/Mozilla_Build_FAQ#Unix-specific_questions'), # bug 1551914 - redirect('^unix/customizing\.html$', 'https://www-archive.mozilla.org/unix/customizing.html'), + redirect(r'^unix/customizing\.html$', 'https://www-archive.mozilla.org/unix/customizing.html'), redirect('^webtools$', 'https://developer.mozilla.org/en/Mozilla_Development_Tools'), redirect('^xmlextras/$', 'http://developer.mozilla.org/en/XML_Extras'), - redirect('^xmlextras/xmldataislands/example1\.html$', + redirect(r'^xmlextras/xmldataislands/example1\.html$', 'http://developer.mozilla.org/@api/deki/files/2861/=example1.html'), - redirect('^xmlextras/xmldataislands/$', + redirect(r'^xmlextras/xmldataislands/$', 'http://developer.mozilla.org/en/Using_XML_Data_Islands_in_Mozilla'), - redirect('^xmlextras/xmldataislands/MXX_Info\.html$', + redirect(r'^xmlextras/xmldataislands/MXX_Info\.html$', 'http://developer.mozilla.org/@api/deki/files/2863/=MXX_Info_(1).html'), - redirect('^xmlextras/xmldataislands/table\.html$', + redirect(r'^xmlextras/xmldataislands/table\.html$', 'http://developer.mozilla.org/@api/deki/files/2864/=table.html'), - redirect('^xpfe/goodcss\.html$', 'http://developer.mozilla.org/en/Writing_Efficient_CSS'), - redirect('^xpfe/skins\.html$', + redirect(r'^xpfe/goodcss\.html$', 'http://developer.mozilla.org/en/Writing_Efficient_CSS'), + redirect(r'^xpfe/skins\.html$', 'http://developer.mozilla.org/en/Writing_Skinnable_XUL_and_CSS'), - redirect('^xpfe/xptoolkit/contents\.html$', '/xpfe/xptoolkit/'), - redirect('^xpfe/xptoolkit/overlays\.html$', 'http://developer.mozilla.org/en/XUL_Overlays'), - redirect('^xpfe/xptoolkit/xulintro\.html$', + redirect(r'^xpfe/xptoolkit/contents\.html$', '/xpfe/xptoolkit/'), + redirect(r'^xpfe/xptoolkit/overlays\.html$', 'http://developer.mozilla.org/en/XUL_Overlays'), + redirect(r'^xpfe/xptoolkit/xulintro\.html$', 'http://developer.mozilla.org/en/Introduction_to_XUL'), - redirect('^xpfe/xulrdf\.htm$', + redirect(r'^xpfe/xulrdf\.htm$', 'http://developer.mozilla.org/en/XUL_and_RDF:' '_The_Implementation_of_the_Application_Object_Model'), - redirect('^xpfe/xulref$', 'http://developer.mozilla.org/en/XUL_Reference'), - redirect('^xpfe/xulref-french$', + redirect(r'^xpfe/xulref$', 'http://developer.mozilla.org/en/XUL_Reference'), + redirect(r'^xpfe/xulref-french$', 'http://developer.mozilla.org/fr/docs/R%C3%A9f%C3%A9rence_XU'), # bug 832348 **/index.html -> **/ @@ -2118,5 +2118,5 @@ redirectpatterns = ( # These were causing 404s due to bad Wordpress links ending in "%E2%80%8E" # When passing through the URL system it is a \u200E character. # https://en.wikipedia.org/wiki/Left-to-right_mark - redirect(ur'^(.*)\u200E$', '/{}', locale_prefix=False), + redirect(r'^(.*)\u200E$', '/{}', locale_prefix=False), ) diff --git a/bedrock/redirects/tests/test_middleware.py b/bedrock/redirects/tests/test_middleware.py index 07bb46c3a0..0f308fa1e4 100644 --- a/bedrock/redirects/tests/test_middleware.py +++ b/bedrock/redirects/tests/test_middleware.py @@ -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): diff --git a/bedrock/redirects/tests/test_util.py b/bedrock/redirects/tests/test_util.py index a5a37b136a..a277cec891 100644 --- a/bedrock/redirects/tests/test_util.py +++ b/bedrock/redirects/tests/test_util.py @@ -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.+)/$', '/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.+)/$', '/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.+)/$', '/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.+)/$', '/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.*)$', '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 diff --git a/bedrock/redirects/util.py b/bedrock/redirects/util.py index 81c655737c..d460568fb2 100644 --- a/bedrock/redirects/util.py +++ b/bedrock/redirects/util.py @@ -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\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)) diff --git a/bedrock/releasenotes/__init__.py b/bedrock/releasenotes/__init__.py index 77d3924536..1c65e9ac71 100644 --- a/bedrock/releasenotes/__init__.py +++ b/bedrock/releasenotes/__init__.py @@ -3,12 +3,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # 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 +version_re = (r"\d+" # major (x in x.y) + 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 diff --git a/bedrock/releasenotes/management/commands/update_release_notes.py b/bedrock/releasenotes/management/commands/update_release_notes.py index c0881ab313..8856cd258b 100644 --- a/bedrock/releasenotes/management/commands/update_release_notes.py +++ b/bedrock/releasenotes/management/commands/update_release_notes.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from django.conf import settings from django.core.management.base import BaseCommand diff --git a/bedrock/releasenotes/migrations/0001_initial.py b/bedrock/releasenotes/migrations/0001_initial.py index af41e1c9cc..01244f8050 100644 --- a/bedrock/releasenotes/migrations/0001_initial.py +++ b/bedrock/releasenotes/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import bedrock.releasenotes.models diff --git a/bedrock/releasenotes/models.py b/bedrock/releasenotes/models.py index f7c9aeeb50..dd74409bf8 100644 --- a/bedrock/releasenotes/models.py +++ b/bedrock/releasenotes/models.py @@ -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): diff --git a/bedrock/releasenotes/tests/test_base.py b/bedrock/releasenotes/tests/test_base.py index 216eab983c..d3bdae837d 100644 --- a/bedrock/releasenotes/tests/test_base.py +++ b/bedrock/releasenotes/tests/test_base.py @@ -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,27 +255,28 @@ 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): - render_mock().render.return_value = HttpResponse('') - with self.activate('en-US'): - self.client.get(reverse('firefox.releases.index')) - releases = render_mock.call_args[0][2]['releases'] - assert len(releases) == len(firefox_desktop.firefox_history_major_releases) - assert releases[0][0] == 36.0 - assert releases[0][1]['major'] == '36.0' - assert releases[0][1]['minor'] == [] - assert releases[3][0] == 33.1 - assert releases[3][1]['major'] == '33.1' - assert releases[3][1]['minor'] == ['33.1.1'] - assert releases[4][0] == 33.0 - assert releases[4][1]['major'] == '33.0' - assert releases[4][1]['minor'] == ['33.0.1', '33.0.2', '33.0.3'] - assert releases[6][0] == 31.0 - assert releases[6][1]['major'] == '31.0' - assert ( - releases[6][1]['minor'] == - ['31.1.0', '31.1.1', '31.2.0', '31.3.0', '31.4.0', '31.5.0']) + 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')) + releases = render_mock.call_args[0][2]['releases'] + assert len(releases) == len(firefox_desktop.firefox_history_major_releases) + assert releases[0][0] == 36.0 + assert releases[0][1]['major'] == '36.0' + assert releases[0][1]['minor'] == [] + assert releases[3][0] == 33.1 + assert releases[3][1]['major'] == '33.1' + assert releases[3][1]['minor'] == ['33.1.1'] + assert releases[4][0] == 33.0 + assert releases[4][1]['major'] == '33.0' + assert releases[4][1]['minor'] == ['33.0.1', '33.0.2', '33.0.3'] + assert releases[6][0] == 31.0 + assert releases[6][1]['major'] == '31.0' + assert ( + releases[6][1]['minor'] == + ['31.1.0', '31.1.1', '31.2.0', '31.3.0', '31.4.0', '31.5.0']) class TestNotesRedirects(TestCase): diff --git a/bedrock/releasenotes/views.py b/bedrock/releasenotes/views.py index 58e356ed2b..c4b8fab47c 100644 --- a/bedrock/releasenotes/views.py +++ b/bedrock/releasenotes/views.py @@ -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: diff --git a/bedrock/security/management/commands/update_security_advisories.py b/bedrock/security/management/commands/update_security_advisories.py index 80838ddfba..e69c115649 100644 --- a/bedrock/security/management/commands/update_security_advisories.py +++ b/bedrock/security/management/commands/update_security_advisories.py @@ -31,7 +31,7 @@ ADVISORIES_REPO = settings.MOFO_SECURITY_ADVISORIES_REPO ADVISORIES_PATH = settings.MOFO_SECURITY_ADVISORIES_PATH ADVISORIES_BRANCH = settings.MOFO_SECURITY_ADVISORIES_BRANCH -SM_RE = re.compile('seamonkey', flags=re.IGNORECASE) +SM_RE = re.compile(r'seamonkey', flags=re.IGNORECASE) FNULL = open(os.devnull, 'w') HOF_FILES = ['client.yml', 'web.yml'] HOF_DIRECTORY = 'bug-bounty-hof' @@ -48,8 +48,9 @@ def fix_product_name(name): def filter_advisory_filenames(filenames): - return [os.path.join(ADVISORIES_PATH, fn) for fn in filenames - if FILENAME_RE.search(fn)] + return [ + os.path.join(ADVISORIES_PATH, fn) for fn in filenames if FILENAME_RE.search(fn) + ] def delete_files(filenames): @@ -83,7 +84,7 @@ def add_or_update_advisory(data, html): prodver_objs = [] fixed_in = data.pop('fixed_in') - if isinstance(fixed_in, basestring): + if isinstance(fixed_in, str): fixed_in = [fixed_in] for productname in fixed_in: @@ -123,7 +124,7 @@ def parse_cve_id(cve_id): def add_or_update_cve(data): - for cve_id, advisory in data['advisories'].iteritems(): + for cve_id, advisory in data['advisories'].items(): if not cve_id.startswith('CVE-'): # skip advisories that are not CVE continue @@ -213,8 +214,9 @@ def get_files_to_delete_from_db(filenames): def delete_orphaned_products(): """Delete any products with no advisories""" - products = Product.objects.annotate(num_advisories=Count('advisories'))\ - .filter(num_advisories=0) + products = Product.objects.annotate(num_advisories=Count('advisories')).filter( + num_advisories=0 + ) num_products = products.count() products.delete() return num_products @@ -225,26 +227,36 @@ class Command(CronCommand): lock_key = 'update_security_advisories' def add_arguments(self, parser): - parser.add_argument('--quiet', - action='store_true', - dest='quiet', - default=False, - help='Do not print output to stdout.') - parser.add_argument('--skip-git', - action='store_true', - dest='no_git', - default=False, - help='No update, just import all files') - parser.add_argument('--clear-db', - action='store_true', - dest='clear_db', - default=False, - help='Clear all security advisory data and load all files') + parser.add_argument( + '--quiet', + action='store_true', + dest='quiet', + default=False, + help='Do not print output to stdout.', + ) + parser.add_argument( + '--skip-git', + action='store_true', + dest='no_git', + default=False, + help='No update, just import all files', + ) + parser.add_argument( + '--clear-db', + action='store_true', + dest='clear_db', + default=False, + help='Clear all security advisory data and load all files', + ) def handle_safe(self, quiet, no_git, clear_db, **options): force = no_git or clear_db - repo = GitRepo(ADVISORIES_PATH, ADVISORIES_REPO, branch_name=ADVISORIES_BRANCH, - name='Security Advisories') + repo = GitRepo( + ADVISORIES_PATH, + ADVISORIES_REPO, + branch_name=ADVISORIES_BRANCH, + name='Security Advisories', + ) def printout(msg, ending=None): if not quiet: @@ -291,7 +303,9 @@ class Command(CronCommand): printout('Deleted {0} orphaned products.'.format(num_products)) if errors: - raise CommandError('Encountered {0} errors:\n\n'.format(len(errors)) + - '\n==========\n'.join(errors)) + raise CommandError( + 'Encountered {0} errors:\n\n'.format(len(errors)) + + '\n==========\n'.join(errors) + ) repo.set_db_latest() diff --git a/bedrock/security/migrations/0001_initial.py b/bedrock/security/migrations/0001_initial.py index 7a3609b147..eff7ebd833 100644 --- a/bedrock/security/migrations/0001_initial.py +++ b/bedrock/security/migrations/0001_initial.py @@ -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 diff --git a/bedrock/security/migrations/0002_auto_20161013_0642.py b/bedrock/security/migrations/0002_auto_20161013_0642.py index 37db2aef8b..9997c60bff 100644 --- a/bedrock/security/migrations/0002_auto_20161013_0642.py +++ b/bedrock/security/migrations/0002_auto_20161013_0642.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/bedrock/security/migrations/0003_halloffamer.py b/bedrock/security/migrations/0003_halloffamer.py index a2690c876d..bf88dfcd1b 100644 --- a/bedrock/security/migrations/0003_halloffamer.py +++ b/bedrock/security/migrations/0003_halloffamer.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/bedrock/security/migrations/0004_mitrecve.py b/bedrock/security/migrations/0004_mitrecve.py index 24212d0b03..0459e1867e 100644 --- a/bedrock/security/migrations/0004_mitrecve.py +++ b/bedrock/security/migrations/0004_mitrecve.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django_extensions.db.fields.json @@ -22,8 +20,8 @@ class Migration(migrations.Migration): ('impact', models.CharField(max_length=100, blank=True)), ('reporter', models.CharField(max_length=100, blank=True)), ('description', models.TextField()), - ('products', django_extensions.db.fields.json.JSONField(default=b'[]')), - ('bugs', django_extensions.db.fields.json.JSONField(default=b'[]')), + ('products', django_extensions.db.fields.json.JSONField(default='[]')), + ('bugs', django_extensions.db.fields.json.JSONField(default='[]')), ], options={ 'ordering': ('-year', '-order'), diff --git a/bedrock/security/migrations/0005_mitrecve_mfsa_ids.py b/bedrock/security/migrations/0005_mitrecve_mfsa_ids.py index fbcb1ca200..af503ca1a4 100644 --- a/bedrock/security/migrations/0005_mitrecve_mfsa_ids.py +++ b/bedrock/security/migrations/0005_mitrecve_mfsa_ids.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations import django_extensions.db.fields.json @@ -15,6 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='mitrecve', name='mfsa_ids', - field=django_extensions.db.fields.json.JSONField(default=b'[]'), + field=django_extensions.db.fields.json.JSONField(default='[]'), ), ] diff --git a/bedrock/security/models.py b/bedrock/security/models.py index 723dca2eae..dbfab667da 100644 --- a/bedrock/security/models.py +++ b/bedrock/security/models.py @@ -22,7 +22,7 @@ class Product(models.Model): class Meta: ordering = ('slug',) - def __unicode__(self): + def __str__(self): return self.name @property @@ -81,7 +81,7 @@ class SecurityAdvisory(models.Model): ordering = ('-year', '-order') get_latest_by = 'last_modified' - def __unicode__(self): + def __str__(self): return u'MFSA {0}'.format(self.id) def get_absolute_url(self): @@ -149,7 +149,7 @@ class MitreCVE(models.Model): class Meta: ordering = ('-year', '-order') - def __unicode__(self): + def __str__(self): return self.id def product_versions(self): @@ -166,7 +166,7 @@ class MitreCVE(models.Model): def get_description(self): versions = [] - for prod_name, prod_versions in self.product_versions().iteritems(): + for prod_name, prod_versions in self.product_versions().items(): versions.extend('%s < %s' % (prod_name, v) for v in prod_versions) description = self.description.strip() @@ -190,7 +190,7 @@ class MitreCVE(models.Model): def get_product_data(self): product_data = [] - for prod_name, versions in self.product_versions().iteritems(): + for prod_name, versions in self.product_versions().items(): product_data.append({ 'product_name': prod_name, 'version': { diff --git a/bedrock/security/tests/test_commands.py b/bedrock/security/tests/test_commands.py index 60ef0c0f64..a73e74f120 100644 --- a/bedrock/security/tests/test_commands.py +++ b/bedrock/security/tests/test_commands.py @@ -80,8 +80,8 @@ class TestDBActions(TestCase): make_mfsa('2015-103') all_files = ['mfsa2015-100.md', 'mfsa2015-101.md'] assert ( - update_security_advisories.get_files_to_delete_from_db(all_files) == - ['mfsa2015-102.md', 'mfsa2015-103.md']) + set(update_security_advisories.get_files_to_delete_from_db(all_files)) == + set(['mfsa2015-102.md', 'mfsa2015-103.md'])) def test_delete_orphaned_products(self): make_mfsa('2015-100') diff --git a/bedrock/security/tests/test_utils.py b/bedrock/security/tests/test_utils.py index d8570484d6..16d3344f65 100644 --- a/bedrock/security/tests/test_utils.py +++ b/bedrock/security/tests/test_utils.py @@ -3,7 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. from datetime import date from textwrap import dedent -from cStringIO import StringIO +from io import StringIO import pytest from mock import patch, call diff --git a/bedrock/security/tests/test_views.py b/bedrock/security/tests/test_views.py index 83012c96e2..82bbd5fc01 100644 --- a/bedrock/security/tests/test_views.py +++ b/bedrock/security/tests/test_views.py @@ -3,7 +3,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. from mock import patch -from product_details import product_details from product_details.version_compare import Version from bedrock.mozorg.tests import TestCase @@ -11,23 +10,27 @@ from bedrock.security.models import Product from bedrock.security.views import ProductView, ProductVersionView, product_is_obsolete -@patch.object(product_details, 'firefox_versions', {'LATEST_FIREFOX_VERSION': '33.0', - 'FIREFOX_ESR': '31.2.0'}) -@patch.object(product_details, 'thunderbird_versions', {'LATEST_THUNDERBIRD_VERSION': '31.2.0'}) def test_product_is_obsolete(): - assert product_is_obsolete('firefox', '3.6') - assert product_is_obsolete('firefox', '32') - assert product_is_obsolete('firefox-esr', '17.0') - assert product_is_obsolete('thunderbird', '30') - assert product_is_obsolete('seamonkey', '2.0') - assert product_is_obsolete('seamonkey', '2.19') - assert product_is_obsolete('other-things', '3000') + from product_details import product_details + with patch.object(product_details, 'firefox_versions', { + 'LATEST_FIREFOX_VERSION': '33.0', 'FIREFOX_ESR': '31.2.0'} + ): + with patch.object(product_details, 'thunderbird_versions', { + 'LATEST_THUNDERBIRD_VERSION': '31.2.0'} + ): + assert product_is_obsolete('firefox', '3.6') + assert product_is_obsolete('firefox', '32') + assert product_is_obsolete('firefox-esr', '17.0') + assert product_is_obsolete('thunderbird', '30') + assert product_is_obsolete('seamonkey', '2.0') + assert product_is_obsolete('seamonkey', '2.19') + assert product_is_obsolete('other-things', '3000') - assert not product_is_obsolete('firefox', '33.0.2') - assert not product_is_obsolete('firefox', '34.0') - assert not product_is_obsolete('firefox-esr', '31.0') - assert not product_is_obsolete('thunderbird', '31') - assert not product_is_obsolete('seamonkey', '2.30') + assert not product_is_obsolete('firefox', '33.0.2') + assert not product_is_obsolete('firefox', '34.0') + assert not product_is_obsolete('firefox-esr', '31.0') + assert not product_is_obsolete('thunderbird', '31') + assert not product_is_obsolete('seamonkey', '2.30') class TestViews(TestCase): diff --git a/bedrock/security/utils.py b/bedrock/security/utils.py index e8684f4946..74f3ee275b 100644 --- a/bedrock/security/utils.py +++ b/bedrock/security/utils.py @@ -12,7 +12,7 @@ from django.template.loader import render_to_string from markdown import markdown -FILENAME_RE = re.compile('mfsa(\d{4}-\d{2,3})\.(md|yml)$') +FILENAME_RE = re.compile(r'mfsa(\d{4}-\d{2,3})\.(md|yml)$') def mfsa_id_from_filename(filename): diff --git a/bedrock/security/views.py b/bedrock/security/views.py index a002163036..6f2929feb5 100644 --- a/bedrock/security/views.py +++ b/bedrock/security/views.py @@ -3,7 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import re -from django.core.urlresolvers import NoReverseMatch +from django.urls import NoReverseMatch from django.db.models import Q from django.utils.decorators import method_decorator from django.views.decorators.http import require_safe @@ -159,7 +159,7 @@ class OldAdvisoriesListView(CachedRedirectView): class KVRedirectsView(CachedRedirectView): - prod_ver_re = re.compile('(\w+)(\d{2})$') + prod_ver_re = re.compile(r'(\w+)(\d{2})$') def get_redirect_url(self, *args, **kwargs): url_component = kwargs['filename'].replace(' ', '') diff --git a/bedrock/settings/__init__.py b/bedrock/settings/__init__.py index 510a09a784..f67c880a76 100644 --- a/bedrock/settings/__init__.py +++ b/bedrock/settings/__init__.py @@ -16,7 +16,7 @@ except ImportError as exc: if DEV: ALLOWED_HOSTS = ['*'] else: - MIDDLEWARE_CLASSES += ('commonware.middleware.FrameOptionsHeader',) + MIDDLEWARE += ['bedrock.base.middleware.FrameOptionsHeader'] if CACHES['default']['BACKEND'] == 'django_pylibmc.memcached.PyLibMCCache': @@ -72,7 +72,7 @@ MEDIA_URL = CDN_BASE_URL + MEDIA_URL STATIC_URL = CDN_BASE_URL + STATIC_URL logging.config.dictConfig(LOGGING) -if (len(sys.argv) > 1 and sys.argv[1] == 'test') or sys.argv[0].endswith('py.test'): +if (len(sys.argv) > 1 and sys.argv[1] == 'test') or 'pytest' in sys.modules: # Using the CachedStaticFilesStorage for tests breaks all the things. STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' # TEMPLATE_DEBUG has to be True for Jinja to call the template_rendered diff --git a/bedrock/settings/base.py b/bedrock/settings/base.py index 876bb1b17e..4db6f9a74e 100644 --- a/bedrock/settings/base.py +++ b/bedrock/settings/base.py @@ -203,12 +203,12 @@ def lazy_lang_group(): groups.setdefault(prefix, []).append(lang) # add any group prefix to the group list if it is also a supported lang - for groupid in groups.keys(): + for groupid in groups: if groupid in langs: groups[groupid].append(groupid) # exclude groups with a single member - return {gid: glist for gid, glist in groups.iteritems() if len(glist) > 1} + return {gid: glist for gid, glist in groups.items() if len(glist) > 1} def lazy_lang_url_map(): @@ -395,7 +395,7 @@ ENABLE_VARY_NOCACHE_MIDDLEWARE = config('ENABLE_VARY_NOCACHE_MIDDLEWARE', # e.g. BASIC_AUTH_CREDS="thedude:thewalrus" BASIC_AUTH_CREDS = config('BASIC_AUTH_CREDS', default='') -MIDDLEWARE_CLASSES = [ +MIDDLEWARE = [ 'allow_cidr.middleware.AllowCIDRMiddleware', 'django.middleware.security.SecurityMiddleware', 'bedrock.mozorg.middleware.MozorgRequestTimingMiddleware', @@ -406,7 +406,7 @@ MIDDLEWARE_CLASSES = [ # must come before LocaleURLMiddleware 'bedrock.redirects.middleware.RedirectsMiddleware', 'bedrock.base.middleware.LocaleURLMiddleware', - 'commonware.middleware.RobotsTagHeader', + 'bedrock.base.middleware.RobotsTagHeader', 'bedrock.mozorg.middleware.ClacksOverheadMiddleware', 'bedrock.mozorg.middleware.HostnameMiddleware', 'django.middleware.common.CommonMiddleware', @@ -416,7 +416,7 @@ MIDDLEWARE_CLASSES = [ ENABLE_CSP_MIDDLEWARE = config('ENABLE_CSP_MIDDLEWARE', default='true', parser=bool) if ENABLE_CSP_MIDDLEWARE: - MIDDLEWARE_CLASSES.append('csp.middleware.CSPMiddleware') + MIDDLEWARE.append('csp.middleware.CSPMiddleware') INSTALLED_APPS = ( # Django contrib apps diff --git a/settings_test.py b/bedrock/settings/test.py similarity index 67% rename from settings_test.py rename to bedrock/settings/test.py index 19fec21510..102087104f 100644 --- a/settings_test.py +++ b/bedrock/settings/test.py @@ -1,4 +1,4 @@ -# These settings will always be overriding for all test runs +from bedrock.settings import * # noqa # this bypasses bcrypt to speed up test fixtures PASSWORD_HASHERS = ( diff --git a/bedrock/sitemaps/utils.py b/bedrock/sitemaps/utils.py index f10d0a38bc..d765925fd3 100644 --- a/bedrock/sitemaps/utils.py +++ b/bedrock/sitemaps/utils.py @@ -1,12 +1,10 @@ # 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 print_function, unicode_literals - import json import re -from django.core import urlresolvers +from django.urls import resolvers from django.conf import settings from django.http import HttpResponse from django.template.loader import get_template @@ -58,7 +56,7 @@ def get_security_urls(): for advisory in SecurityAdvisory.objects.all(): try: adv_url = advisory.get_absolute_url() - except urlresolvers.NoReverseMatch: + except resolvers.NoReverseMatch: continue # strip "/en-US" off the front @@ -81,7 +79,7 @@ def get_release_notes_urls(): try: rel_path = release.get_absolute_url() req_path = release.get_sysreq_url() - except urlresolvers.NoReverseMatch: + except resolvers.NoReverseMatch: continue # strip "/en-US" off the front @@ -117,10 +115,10 @@ def get_static_urls(): # get_resolver is an undocumented but convenient function. # Try to retrieve all valid URLs on this site. - # NOTE: have to use `iterlists()` here since the standard - # `iteritems()` only returns the first item in the list for the + # NOTE: have to use `lists()` here since the standard + # `items()` only returns the first item in the list for the # view since `reverse_dict` is a `MultiValueDict`. - for key, values in urlresolvers.get_resolver(None).reverse_dict.iterlists(): + for key, values in resolvers.get_resolver(None).reverse_dict.lists(): for value in values: path = value[0][0][0] # Exclude pages that we don't want be indexed by search engines. @@ -142,18 +140,18 @@ def get_static_urls(): if not render.called: continue - locales = render.call_args[0][2]['translations'].keys() + locales = set(render.call_args[0][2]['translations'].keys()) # zh-CN is a redirect on the homepage if path == '/': - locales.remove('zh-CN') + locales -= {'zh-CN'} # Firefox Focus has a different URL in German if path == '/privacy/firefox-focus/': - locales.remove('de') + locales -= {'de'} # just remove any locales not in our prod list - locales = list(set(locales).intersection(settings.PROD_LANGUAGES)) + locales = list(locales.intersection(settings.PROD_LANGUAGES)) if path not in urls: urls[path] = locales @@ -175,13 +173,13 @@ def output_json(urls): output_file = settings.ROOT_PATH.joinpath('root_files', 'sitemap.json') # Output the data as a JSON file for convenience - with output_file.open('wb') as json_file: + with output_file.open('w') as json_file: json.dump(urls, json_file) def output_xml(urls): urls_by_locale = {} - for url, locales in urls.iteritems(): + for url, locales in urls.items(): if not locales: urls_by_locale.setdefault('none', []) urls_by_locale['none'].append(url) @@ -190,7 +188,7 @@ def output_xml(urls): urls_by_locale.setdefault(locale, []) urls_by_locale[locale].append(url) - for locale, urls in urls_by_locale.iteritems(): + for locale, urls in urls_by_locale.items(): if locale != 'none': output_file = settings.ROOT_PATH.joinpath('root_files', locale, 'sitemap.xml') output_file.parent.mkdir(exist_ok=True) diff --git a/bedrock/utils/git.py b/bedrock/utils/git.py index 4dc11ee93c..f043169b67 100644 --- a/bedrock/utils/git.py +++ b/bedrock/utils/git.py @@ -1,17 +1,12 @@ # 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 print_function, unicode_literals - import os from datetime import datetime from hashlib import sha256 from shutil import rmtree from subprocess import check_output, CalledProcessError, STDOUT -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO +from io import StringIO from django.conf import settings @@ -24,15 +19,14 @@ from bedrock.utils.models import GitRepoState GIT = getattr(settings, 'GIT_BIN', 'git') -class GitRepo(object): +class GitRepo: def __init__(self, path, remote_url=None, branch_name='master', name=None): self.path = Path(path) self.path_str = str(self.path) self.remote_url = remote_url self.branch_name = branch_name - db_latest_key = '%s:%s:%s' % (self.path_str, remote_url or '', - branch_name) - self.db_latest_key = sha256(db_latest_key).hexdigest() + db_latest_key = '%s:%s:%s' % (self.path_str, remote_url or '', branch_name) + self.db_latest_key = sha256(db_latest_key.encode()).hexdigest() self.repo_name = name or self.path.name def git(self, *args): diff --git a/bedrock/utils/migrations/0001_initial.py b/bedrock/utils/migrations/0001_initial.py index 21dc219826..de4cdbfdb0 100644 --- a/bedrock/utils/migrations/0001_initial.py +++ b/bedrock/utils/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/bedrock/utils/migrations/0002_auto_20180522_1249.py b/bedrock/utils/migrations/0002_auto_20180522_1249.py index 4d987a7a3f..da89c25c72 100644 --- a/bedrock/utils/migrations/0002_auto_20180522_1249.py +++ b/bedrock/utils/migrations/0002_auto_20180522_1249.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/bedrock/utils/models.py b/bedrock/utils/models.py index 88c96a9949..c02fb3caea 100644 --- a/bedrock/utils/models.py +++ b/bedrock/utils/models.py @@ -12,7 +12,7 @@ class GitRepoState(models.Model): latest_ref = models.CharField(max_length=100) latest_ref_timestamp = models.IntegerField(default=0) - def __unicode__(self): + def __str__(self): return '%s: %s' % (self.repo_name, self.latest_ref) @property diff --git a/bedrock/utils/tests/test_git.py b/bedrock/utils/tests/test_git.py index 6b608615c8..50d7456faf 100644 --- a/bedrock/utils/tests/test_git.py +++ b/bedrock/utils/tests/test_git.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import pytest from django.test import override_settings from mock import call, patch, DEFAULT diff --git a/bedrock/utils/tests/test_views.py b/bedrock/utils/tests/test_views.py index 0142b36407..e82e2605b8 100644 --- a/bedrock/utils/tests/test_views.py +++ b/bedrock/utils/tests/test_views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import RequestFactory from django.test import override_settings diff --git a/bedrock/utils/views.py b/bedrock/utils/views.py index 5b518a5a6b..74cc3e8f26 100644 --- a/bedrock/utils/views.py +++ b/bedrock/utils/views.py @@ -5,7 +5,7 @@ from bedrock.utils import expand_locale_groups from lib.l10n_utils import LangFilesMixin, get_locale -class VariationMixin(object): +class VariationMixin: template_name_variations = None template_context_variations = None variation_locales = None diff --git a/bedrock/wordpress/api.py b/bedrock/wordpress/api.py index 7215e2f254..e3886c9925 100644 --- a/bedrock/wordpress/api.py +++ b/bedrock/wordpress/api.py @@ -1,4 +1,3 @@ -from __future__ import print_function, unicode_literals from django.conf import settings import requests diff --git a/bedrock/wordpress/management/commands/update_wordpress.py b/bedrock/wordpress/management/commands/update_wordpress.py index 5f43f47b01..332a6358c8 100644 --- a/bedrock/wordpress/management/commands/update_wordpress.py +++ b/bedrock/wordpress/management/commands/update_wordpress.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from django.conf import settings from django.core.management.base import BaseCommand, CommandError diff --git a/bedrock/wordpress/migrations/0001_initial.py b/bedrock/wordpress/migrations/0001_initial.py index 80c383d749..78155a55c7 100644 --- a/bedrock/wordpress/migrations/0001_initial.py +++ b/bedrock/wordpress/migrations/0001_initial.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django_extensions.db.fields.json diff --git a/bedrock/wordpress/models.py b/bedrock/wordpress/models.py index eee7ad8dcf..3e0e40056c 100644 --- a/bedrock/wordpress/models.py +++ b/bedrock/wordpress/models.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import print_function, unicode_literals - import operator import random @@ -18,6 +16,7 @@ from jinja2 import Markup from raven.contrib.django.raven_compat.models import client as sentry_client from bedrock.wordpress.api import get_posts_data, complete_posts_data +from functools import reduce def make_datetime(datestr): @@ -108,7 +107,7 @@ class BlogPostManager(models.Manager): try: if obj: - for key, value in post.iteritems(): + for key, value in post.items(): setattr(obj, key, value) obj.save() else: @@ -153,7 +152,7 @@ class BlogPost(models.Model): get_latest_by = 'date' ordering = ['-date'] - def __unicode__(self): + def __str__(self): return '%s: %s' % (self.blog_name, self.title) def get_absolute_url(self): diff --git a/bedrock/wordpress/views.py b/bedrock/wordpress/views.py index 7749235b77..bffe1672d4 100644 --- a/bedrock/wordpress/views.py +++ b/bedrock/wordpress/views.py @@ -6,7 +6,7 @@ from lib.l10n_utils import LangFilesMixin from raven.contrib.django.raven_compat.models import client as sentry_client -class BlogPostsMixin(object): +class BlogPostsMixin: blog_tags = None blog_slugs = None blog_posts_limit = 4 @@ -16,7 +16,7 @@ class BlogPostsMixin(object): ctx = super(BlogPostsMixin, self).get_context_data(**kwargs) blog = BlogPost.objects.all() if self.blog_slugs: - if isinstance(self.blog_slugs, basestring): + if isinstance(self.blog_slugs, str): blog_slugs = [self.blog_slugs] else: blog_slugs = self.blog_slugs diff --git a/bin/cron.py b/bin/cron.py index 699831e8ad..3c2da75066 100755 --- a/bin/cron.py +++ b/bin/cron.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function, unicode_literals - import datetime import platform import sys @@ -51,7 +49,7 @@ def call_command(command): check_call('python {0} {1}'.format(MANAGE, command), shell=True) -class scheduled_job(object): +class scheduled_job: """Decorator for scheduled jobs. Takes same args as apscheduler.schedule_job.""" def __init__(self, *args, **kwargs): diff --git a/bin/move_hashed_staticfiles.py b/bin/move_hashed_staticfiles.py index fa87dddbf5..5c30b252d4 100755 --- a/bin/move_hashed_staticfiles.py +++ b/bin/move_hashed_staticfiles.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import print_function, unicode_literals - import json import os import sys @@ -13,7 +11,7 @@ def get_hashed_filenames(static_path): with open(json_file) as jsonf: staticfiles = json.load(jsonf) - return staticfiles['paths'].values() + return list(staticfiles['paths'].values()) def move_hashed_files(static_path, hashed_path): diff --git a/bin/open-compare.py b/bin/open-compare.py index 621c756ed3..9a32e8b202 100755 --- a/bin/open-compare.py +++ b/bin/open-compare.py @@ -6,7 +6,9 @@ import argparse import json import sys -import urllib2 +import urllib.request +import urllib.error +import urllib.parse import webbrowser @@ -27,7 +29,7 @@ GITHUB_API_TEMPLATE = 'https://api.github.com/repos/{repo}/branches/{branch}' def get_current_rev(env): url = None try: - url = urllib2.urlopen(ENV_URLS[env] + REV_PATH) + url = urllib.request.urlopen(ENV_URLS[env] + REV_PATH) return url.read().strip()[:10] finally: if url: @@ -38,7 +40,7 @@ def get_current_master(repo, branch): url = GITHUB_API_TEMPLATE.format(repo=repo, branch=branch) conn = None try: - conn = urllib2.urlopen(url, timeout=30) + conn = urllib.request.urlopen(url, timeout=30) info = json.loads(conn.read().strip()) return info['commit']['sha'][:10] except Exception: @@ -62,7 +64,7 @@ def write_stdout(out_str): def main(): parser = argparse.ArgumentParser(description='Open github compare view ' 'for bedrock.') - parser.add_argument('-e', '--env', default='prod', choices=ENV_URLS.keys(), + parser.add_argument('-e', '--env', default='prod', choices=list(ENV_URLS), metavar='ENV', help='Environment: demo[1-3], ' 'stage, or prod (default)') parser.add_argument('-r', '--repo', default=DEFAULT_REPO, @@ -85,7 +87,7 @@ def main(): write_stdout(compare_url) if not args.print_only: webbrowser.open(compare_url) - except Exception, e: + except Exception as e: sys.stderr.write('\nERROR: {0}\n'.format(e)) return 1 return 0 diff --git a/bin/run-db-download.py b/bin/run-db-download.py index 29ca4317a8..109b07114f 100755 --- a/bin/run-db-download.py +++ b/bin/run-db-download.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import absolute_import, print_function, unicode_literals - import os import sys diff --git a/bin/run-db-upload.py b/bin/run-db-upload.py index 53823eea60..b305f5f20b 100755 --- a/bin/run-db-upload.py +++ b/bin/run-db-upload.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from __future__ import absolute_import, print_function, unicode_literals - import os import sys from time import time diff --git a/bin/run-tests.sh b/bin/run-tests.sh index 842da45369..b2412ae491 100755 --- a/bin/run-tests.sh +++ b/bin/run-tests.sh @@ -1,6 +1,6 @@ #!/bin/bash -xe -flake8 bedrock lib tests +flake8 python manage.py runscript check_calendars python manage.py version python manage.py migrate --noinput diff --git a/docker/envfiles/master.env b/docker/envfiles/master.env index a458182296..14c99c0bf0 100644 --- a/docker/envfiles/master.env +++ b/docker/envfiles/master.env @@ -1,5 +1,5 @@ # used for fetching data for non-production images -DEBUG=False +DEBUG=True DEV=True ALLOWED_HOSTS=* SECRET_KEY=59114b6a-2858-4caf-8878-482a24ee9542 diff --git a/lib/l10n_utils/__init__.py b/lib/l10n_utils/__init__.py index a89e6737d8..5a85717e16 100644 --- a/lib/l10n_utils/__init__.py +++ b/lib/l10n_utils/__init__.py @@ -158,7 +158,7 @@ def get_best_translation(translations, accept_languages): return translations[0] -class LangFilesMixin(object): +class LangFilesMixin: """Generic views mixin that uses l10n_utils to render responses.""" active_locales = None add_active_locales = None diff --git a/lib/l10n_utils/dotlang.py b/lib/l10n_utils/dotlang.py index e2602aae51..baafd85f2a 100644 --- a/lib/l10n_utils/dotlang.py +++ b/lib/l10n_utils/dotlang.py @@ -150,7 +150,7 @@ def _get_extra_lang_files(): finally: del frame if new_lang_files: - if isinstance(new_lang_files, basestring): + if isinstance(new_lang_files, str): new_lang_files = [new_lang_files] return [lf for lf in new_lang_files if lf not in settings.DOTLANG_FILES] @@ -185,7 +185,7 @@ def gettext(text, *args, **kwargs): return text -_lazy_proxy = lazy(gettext, unicode) +_lazy_proxy = lazy(gettext, str) def gettext_lazy(*args, **kwargs): diff --git a/lib/l10n_utils/extract.py b/lib/l10n_utils/extract.py index 5a3a5b80c8..a9a8373ce9 100644 --- a/lib/l10n_utils/extract.py +++ b/lib/l10n_utils/extract.py @@ -23,14 +23,14 @@ def tweak_message(message): 2) Babel doesn't support context (msgctxt). We hack that in ourselves here. """ - if isinstance(message, basestring): + if isinstance(message, str): message = strip_whitespace(message) elif isinstance(message, tuple): # A tuple of 2 has context, 3 is plural, 4 is plural with context if len(message) == 2: message = add_context(message[1], message[0]) elif len(message) == 3: - if all(isinstance(x, basestring) for x in message[:2]): + if all(isinstance(x, str) for x in message[:2]): singular, plural, num = message message = (strip_whitespace(singular), strip_whitespace(plural), diff --git a/lib/l10n_utils/gettext.py b/lib/l10n_utils/gettext.py index 48984618b0..d010d1979f 100644 --- a/lib/l10n_utils/gettext.py +++ b/lib/l10n_utils/gettext.py @@ -4,8 +4,6 @@ # 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 with_statement - import codecs import os import re @@ -17,8 +15,8 @@ from django.core.cache import caches from django.template.loader import get_template from jinja2 import Environment -from dotlang import (parse as parse_lang, get_lang_path, - get_translations_for_langfile, lang_file_tag_set) +from .dotlang import ( + parse as parse_lang, get_lang_path, get_translations_for_langfile, lang_file_tag_set) from lib.l10n_utils.utils import ContainsEverything @@ -84,7 +82,7 @@ def po_msgs(domain): def translated_strings(file_): path = join(settings.ROOT, 'locale', 'templates', file_) - trans = parse_lang(path).keys() + trans = list(parse_lang(path).keys()) return trans @@ -135,7 +133,7 @@ def parse_python(path): if result: new_lang_files = eval(untokenize(result)) - if isinstance(new_lang_files, basestring): + if isinstance(new_lang_files, str): new_lang_files = [new_lang_files] # remove empties return [lf for lf in new_lang_files if lf] @@ -156,7 +154,7 @@ def parse_template(path): lang_files = [] def ignore_whitespace(tokens): - token = tokens.next() + token = next(tokens) if token[1] == 'whitespace': return ignore_whitespace(tokens) return token @@ -340,7 +338,7 @@ def find_lang_files(lang): def merge_lang_files(langs): for lang in langs: - print 'Merging into %s...' % lang + print('Merging into %s...' % lang) for f in find_lang_files('templates'): # Make sure the directory exists (might be a subdirectory) @@ -369,7 +367,7 @@ def _append_to_lang_file(dest, new_msgs): with codecs.open(dest, 'a', 'utf-8') as out: for msg in new_msgs: - if isinstance(msg, basestring): + if isinstance(msg, str): msg = [None, msg] out_str = u'\n\n' if msg[0]: diff --git a/lib/l10n_utils/management/commands/l10n_check.py b/lib/l10n_utils/management/commands/l10n_check.py index 49aa3fb16d..643f824146 100644 --- a/lib/l10n_utils/management/commands/l10n_check.py +++ b/lib/l10n_utils/management/commands/l10n_check.py @@ -10,7 +10,7 @@ import os from os import path import codecs from contextlib import closing -from StringIO import StringIO +from io import StringIO from django.core.management.base import BaseCommand from django.conf import settings @@ -46,7 +46,7 @@ def update_templates(langs): for tmpl in list_templates(): template = L10nTemplate(tmpl) - print "%s..." % template.rel_path + print("%s..." % template.rel_path) template.process(langs) @@ -79,7 +79,7 @@ def write_block(block, dest, force_was=False): dest.write('\n\n') -class L10nTemplate(object): +class L10nTemplate: def __init__(self, template=None, source=None): """ @@ -151,7 +151,7 @@ class L10nTemplate(object): for block in blocks: write_block(block, dest) - print '%s: %s' % (lang, self.rel_path) + print('%s: %s' % (lang, self.rel_path)) def _get_ref_block(self, name, blocks=None): """Return the reference block""" @@ -225,12 +225,12 @@ class L10nTemplate(object): with codecs.open(dest_tmpl, 'w', 'utf-8') as dest: dest.write(buffer.getvalue()) - print '%s: %s' % (lang, self.rel_path) + print('%s: %s' % (lang, self.rel_path)) -class L10nParser(): +class L10nParser: - file_version_re = re.compile('\W*Version: (\d+)\W*') + file_version_re = re.compile(r'\W*Version: (\d+)\W*') def __init__(self): self.tmpl = None @@ -286,7 +286,7 @@ class L10nParser(): if name == 'comment_begin': # Check comments for the version string - comment = self.tokens.next()[2] + comment = next(self.tokens)[2] matches = self.file_version_re.match(comment) if matches: @@ -307,8 +307,8 @@ class L10nParser(): yield ('content', comment) elif name == 'block_begin': - space = self.tokens.next() - block = self.tokens.next() + space = next(self.tokens) + block = next(self.tokens) if block[1] == 'name': type = block[2] @@ -331,8 +331,8 @@ class L10nParser(): # that means the template has been customized # and we shouldn't touch it - ident_space = self.tokens.next() - ident = self.tokens.next() + ident_space = next(self.tokens) + ident = next(self.tokens) if ident[2] == 'content': # This is the content block, stop parsing @@ -420,8 +420,8 @@ class L10nParser(): buffer = was_content if in_was else main_content if token[1] == 'block_begin': - space = self.tokens.next()[2] - name = self.tokens.next()[2] + space = next(self.tokens)[2] + name = next(self.tokens)[2] if name == 'endl10n': self.scan_until('block_end') @@ -456,7 +456,7 @@ class L10nParser(): break def scan_next(self, name): - token = self.tokens.next() + token = next(self.tokens) if token and token[1] == name: return token[2] # Put it back on the list @@ -476,6 +476,6 @@ class Command(BaseCommand): langs = options['langs'] if not langs: langs = os.listdir(l10n_file()) - langs = filter(lambda x: x[0] != '.', langs) + langs = [x for x in langs if x[0] != '.'] update_templates(langs) diff --git a/lib/l10n_utils/management/commands/l10n_extract.py b/lib/l10n_utils/management/commands/l10n_extract.py index 4e76e01fb8..6f53a1d268 100644 --- a/lib/l10n_utils/management/commands/l10n_extract.py +++ b/lib/l10n_utils/management/commands/l10n_extract.py @@ -29,7 +29,7 @@ def gettext_extract(): def extract_callback(filename, method, options): if method != 'ignore': - print " %s" % filename + print(" %s" % filename) def extract_from_files(filenames, @@ -116,7 +116,7 @@ def extract_from_files(filenames, matched = True filepath = os.path.join(settings.ROOT, filename) if not os.path.exists(filepath): - print '! %s does not exist!' % filename + print('! %s does not exist!' % filename) break options = {} for opattern, odict in options_map.items(): @@ -133,7 +133,7 @@ def extract_from_files(filenames, yield filename, lineno, message, comments, context break if not matched: - print '! %s does not match any domain methods!' % filename + print('! %s does not match any domain methods!' % filename) class Command(BaseCommand): diff --git a/lib/l10n_utils/management/commands/l10n_merge.py b/lib/l10n_utils/management/commands/l10n_merge.py index 7916de152b..9f0b532bc0 100644 --- a/lib/l10n_utils/management/commands/l10n_merge.py +++ b/lib/l10n_utils/management/commands/l10n_merge.py @@ -21,7 +21,7 @@ class Command(BaseCommand): langs = options['langs'] if not langs: langs = os.listdir(os.path.join(settings.ROOT, 'locale')) - langs = filter(lambda x: x != 'templates', langs) - langs = filter(lambda x: x[0] != '.', langs) + langs = [x for x in langs if x != 'templates'] + langs = [x for x in langs if x[0] != '.'] merge_lang_files(langs) diff --git a/lib/l10n_utils/management/commands/l10n_update.py b/lib/l10n_utils/management/commands/l10n_update.py index b083f5d0ea..6cf71434f1 100644 --- a/lib/l10n_utils/management/commands/l10n_update.py +++ b/lib/l10n_utils/management/commands/l10n_update.py @@ -1,8 +1,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 django.core.management.base import BaseCommand from django.conf import settings diff --git a/lib/l10n_utils/template.py b/lib/l10n_utils/template.py index a19a2fb7d4..6ffbb73442 100644 --- a/lib/l10n_utils/template.py +++ b/lib/l10n_utils/template.py @@ -27,7 +27,7 @@ class L10nBlockExtension(Extension): def parse(self, parser): # Jump over first token ("l10n"), grab line number. - lineno = parser.stream.next().lineno + lineno = next(parser.stream).lineno # Block name is mandatory. name = parser.stream.expect('name').value @@ -55,7 +55,7 @@ class L10nBlockExtension(Extension): if token.type == 'sub': locales[-1] += '-' prev_sub = True - parser.stream.next() + next(parser.stream) # Add version if provided. if parser.stream.current.type == 'integer': @@ -97,14 +97,14 @@ class LoadLangExtension(Extension): def parse(self, parser): # Skip over the block name - name = parser.stream.next() + name = next(parser.stream) lineno = name.lineno # Grab all the args args = [parser.stream.expect('string').value] while parser.stream.current.type == 'string': args.append(parser.stream.current.value) - parser.stream.next() + next(parser.stream) # Make a node that calls the lang_files helper content_nodes = [nodes.Call(nodes.Name('lang_files', 'load'), diff --git a/lib/l10n_utils/tests/__init__.py b/lib/l10n_utils/tests/__init__.py index ba9f0cba35..4e8e9e1709 100644 --- a/lib/l10n_utils/tests/__init__.py +++ b/lib/l10n_utils/tests/__init__.py @@ -1,14 +1,14 @@ import sys from contextlib import contextmanager -from cStringIO import StringIO +from io import StringIO from tempfile import TemporaryFile from textwrap import dedent -class TempFileMixin(object): +class TempFileMixin: """Provide a method for getting a temp file that is removed when closed.""" def tempfile(self, data=None): - tempf = TemporaryFile() + tempf = TemporaryFile(mode='w+', encoding='utf-8') if data: tempf.write(dedent(data)) tempf.seek(0) diff --git a/lib/l10n_utils/tests/test_commands.py b/lib/l10n_utils/tests/test_commands.py index 091db0a771..3dfdbf7194 100644 --- a/lib/l10n_utils/tests/test_commands.py +++ b/lib/l10n_utils/tests/test_commands.py @@ -3,11 +3,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 __future__ import unicode_literals - import codecs from os import path -from StringIO import StringIO +from io import StringIO, IOBase from textwrap import dedent from django.conf import settings @@ -241,7 +239,7 @@ class TestL10nCheck(TestCase): # cause the template to be read and parsed before mocking open template.blocks codecs_open = 'lib.l10n_utils.management.commands.l10n_check.codecs.open' - open_mock = MagicMock(spec=file) + open_mock = MagicMock(spec=IOBase) with patch(codecs_open, open_mock): template.update('zh-TW') file_handle = open_mock.return_value.__enter__.return_value @@ -259,7 +257,7 @@ class TestL10nCheck(TestCase): # cause the template to be read and parsed before mocking open template.blocks codecs_open = 'lib.l10n_utils.management.commands.l10n_check.codecs.open' - open_mock = MagicMock(spec=file) + open_mock = MagicMock(spec=IOBase) open_buffer = StringIO() # for writing the new file open_mock.return_value.__enter__.return_value = open_buffer @@ -294,7 +292,7 @@ class TestL10nCheck(TestCase): # cause the template to be read and parsed before mocking open template.blocks codecs_open = 'lib.l10n_utils.management.commands.l10n_check.codecs.open' - open_mock = MagicMock(spec=file) + open_mock = MagicMock(spec=IOBase) with patch(codecs_open, open_mock): template.copy('zh-TW') file_handle = open_mock.return_value.__enter__.return_value @@ -311,7 +309,7 @@ class TestL10nCheck(TestCase): # cause the template to be read and parsed before mocking open template.blocks codecs_open = 'lib.l10n_utils.management.commands.l10n_check.codecs.open' - open_mock = MagicMock(spec=file) + open_mock = MagicMock(spec=IOBase) open_buffer = StringIO() open_mock.return_value.__enter__.return_value = open_buffer with patch(codecs_open, open_mock): diff --git a/lib/l10n_utils/tests/test_dotlang.py b/lib/l10n_utils/tests/test_dotlang.py index d53446609a..59e48f66ed 100644 --- a/lib/l10n_utils/tests/test_dotlang.py +++ b/lib/l10n_utils/tests/test_dotlang.py @@ -4,16 +4,16 @@ # 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 io from django.conf import settings from django.core import mail -from django.core.urlresolvers import clear_url_caches +from django.urls import clear_url_caches from django.http import HttpRequest from django.test.utils import override_settings from django_jinja.backend import Jinja2 from mock import patch from pathlib2 import Path -from product_details import product_details from pyquery import PyQuery as pq from bedrock.mozorg.tests import TestCase @@ -178,8 +178,8 @@ class TestDotlang(TestCase): assert len(mail.outbox) == 1 assert mail.outbox[0].subject == '[bedrock] %s is corrupted' % path expected = { - u'Update now': u'Niha rojane bike', - u'Supported Devices': u'C�haz�n pi�tgiriy' + 'Update now': 'Niha rojane bike', + 'Supported Devices': 'C�haz�n pi�tgiriy�' } assert parsed == expected mail.outbox = [] @@ -266,8 +266,8 @@ class TestDotlang(TestCase): # extraction pypath = ROOT_PATH.joinpath('extract_me.py') - with open(str(pypath)) as pyfile: - vals = extract_python(pyfile, ['_'], [], {}).next() + with io.open(str(pypath), 'rb') as pyfile: + vals = next(extract_python(pyfile, ['_'], [], {})) assert vals[2] == clean_string # translation @@ -367,15 +367,15 @@ class TestDotlang(TestCase): """ # test the case when LANG_FILES is a string trans_str = 'Translate me' - # have to call __unicode__ directly because the value is a Mock - # object, and the `unicode()` function throws an exception. - _lazy(trans_str, lang_files='maude').__unicode__() + # have to call __str__ directly because the value is a Mock + # object, and the `str()` function throws an exception. + _lazy(trans_str, lang_files='maude').__str__() call_lang_files = ['maude'] + settings.DOTLANG_FILES trans_patch.assert_called_with(trans_str, call_lang_files) # test the case when LANG_FILES is a list lang_files_list = ['maude', 'bunny', 'uli'] - _lazy(trans_str, lang_files=lang_files_list).__unicode__() + _lazy(trans_str, lang_files=lang_files_list).__str__() call_lang_files = lang_files_list + settings.DOTLANG_FILES trans_patch.assert_called_with(trans_str, call_lang_files) @@ -391,9 +391,9 @@ class TestDotlang(TestCase): dude_says = extract_me_with_langfiles_lazy.do_translate() dirty_string = u"I'm The Dude, so that's what you call me, man." self.assertFalse(trans_patch.called) - # have to call __unicode__ directly because the value is a Mock - # object, and the `unicode()` function throws an exception. - dude_says.__unicode__() + # have to call __str__ directly because the value is a Mock + # object, and the `str()` function throws an exception. + dude_says.__str__() trans_patch.assert_called_with(dirty_string, ['donnie', 'walter'] + settings.DOTLANG_FILES) @@ -449,6 +449,7 @@ class TestTranslationList(TestCase): The context of each view should have the 'links' dictionary which contains the canonical and alternate URLs of the page. """ + from product_details import product_details request = HttpRequest() request.path = '/' + lang + '/' + view_name + '/' request.locale = lang diff --git a/lib/l10n_utils/tests/test_extract.py b/lib/l10n_utils/tests/test_extract.py index 9f740576ef..4b1518851b 100644 --- a/lib/l10n_utils/tests/test_extract.py +++ b/lib/l10n_utils/tests/test_extract.py @@ -1,6 +1,6 @@ # taken and modified from tower tests -from cStringIO import StringIO +import io from babel.messages.catalog import Catalog from babel.messages.extract import extract @@ -10,7 +10,7 @@ from puente.settings import get_setting def test_extract_python(): - fileobj = StringIO(TEST_PO_INPUT) + fileobj = io.BytesIO(TEST_PO_INPUT) method = 'lib.l10n_utils.extract.extract_python' output = fake_extract_command(filename="filename", fileobj=fileobj, method=method) @@ -20,7 +20,7 @@ def test_extract_python(): def test_extract_jinja2(): - fileobj = StringIO(TEST_TEMPLATE_INPUT) + fileobj = io.BytesIO(TEST_TEMPLATE_INPUT) method = 'lib.l10n_utils.extract.extract_jinja2' output = fake_extract_command(filename="filename", fileobj=fileobj, method=method) @@ -39,9 +39,9 @@ def fake_extract_command(filename, fileobj, method, catalog.add(msg, None, [(filename, lineno)], auto_comments=cmts, context=ctxt) - po_out = StringIO() + po_out = io.BytesIO() write_po(po_out, catalog, width=80, omit_header=True) - return unicode(po_out.getvalue()) + return po_out.getvalue() def fake_extract_from_dir(filename, fileobj, method, options, keywords, comment_tags): @@ -54,7 +54,7 @@ def fake_extract_from_dir(filename, fileobj, method, options, keywords, comment_ yield filename, lineno, message, comments, context -TEST_PO_INPUT = """ +TEST_PO_INPUT = b""" _('fligtar') # Make sure several uses collapses to one ngettext('a fligtar', 'many fligtars', 1) @@ -70,7 +70,7 @@ ngettext('fligtar', 'many fligtars', 5) _lazy('a lazy string') """ -TEST_PO_OUTPUT = """\ +TEST_PO_OUTPUT = b"""\ #. l10n: Turn down the volume #: filename:2 filename:12 msgid "fligtar" @@ -95,7 +95,7 @@ msgstr "" """ -TEST_TEMPLATE_INPUT = """ +TEST_TEMPLATE_INPUT = b""" {{ _('sunshine') }} {# Regular comment, regular gettext #} {% trans %} @@ -116,7 +116,7 @@ TEST_TEMPLATE_INPUT = """ {% endtrans %} """ -TEST_TEMPLATE_OUTPUT = """\ +TEST_TEMPLATE_OUTPUT = b"""\ #: filename:2 msgid "sunshine" msgstr "" diff --git a/lib/l10n_utils/tests/test_gettext.py b/lib/l10n_utils/tests/test_gettext.py index 10e20edea3..50687c6b87 100644 --- a/lib/l10n_utils/tests/test_gettext.py +++ b/lib/l10n_utils/tests/test_gettext.py @@ -110,7 +110,7 @@ class TestPOFiles(TestCase): langfiles_mock.return_value = ['some_lang_files', 'firefox/fx'] pot_to_langfiles('messages') - append_mock.assert_called_with(ANY, self.good_messages) + append_mock.assert_any_call(ANY, self.good_messages) @patch('os.path.exists', TRUE_MOCK) @patch('lib.l10n_utils.gettext.codecs') diff --git a/lib/l10n_utils/translation.py b/lib/l10n_utils/translation.py index b5067ad5a2..3945dccd43 100644 --- a/lib/l10n_utils/translation.py +++ b/lib/l10n_utils/translation.py @@ -27,11 +27,11 @@ def deactivate(): def get_language(): """Returns the currently selected language.""" - l = getattr(_active, "value", None) - if l is None: + lang = getattr(_active, "value", None) + if lang is None: return settings.LANGUAGE_CODE - return l + return lang def get_language_bidi(): diff --git a/lib/l10n_utils/utils.py b/lib/l10n_utils/utils.py index 95bbe44d50..4687df954f 100644 --- a/lib/l10n_utils/utils.py +++ b/lib/l10n_utils/utils.py @@ -6,7 +6,7 @@ import re -class ContainsEverything(object): +class ContainsEverything: """An object whose instances will claim to contain anything.""" def __contains__(self, item): return True diff --git a/requirements/base.txt b/requirements/base.txt index e8619d91c5..92f15e95d3 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,10 +1,38 @@ -Django==1.11.21 \ - --hash=sha256:aae1b776d78cc3f492afda405b9b9d322b27761442997456c158687d7a0610a1 \ - --hash=sha256:ba723e524facffa2a9d8c2e9116db871e16b9207e648e1d3e4af8aae1167b029 +Django==2.2.1 \ + --hash=sha256:6fcc3cbd55b16f9a01f37de8bcbe286e0ea22e87096557f1511051780338eaea \ + --hash=sha256:bb407d0bb46395ca1241f829f5bd03f7e482f97f7d1936e26e98dacb201ed4ec +sqlparse==0.3.0 \ + --hash=sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177 \ + --hash=sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873 MarkupSafe==1.0 \ --hash=sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665 -lxml==2.3.3 \ - --hash=sha256:2a3ca34f63b062ee8e059ca2460ac18040ec9622f0a31e143383f0db944ceb36 +lxml==4.3.3 \ + --hash=sha256:03984196d00670b2ab14ae0ea83d5cc0cfa4f5a42558afa9ab5fa745995328f5 \ + --hash=sha256:0815b0c9f897468de6a386dc15917a0becf48cc92425613aa8bbfc7f0f82951f \ + --hash=sha256:175f3825f075cf02d15099eb52658457cf0ff103dcf11512b5d2583e1d40f58b \ + --hash=sha256:30e14c62d88d1e01a26936ecd1c6e784d4afc9aa002bba4321c5897937112616 \ + --hash=sha256:3210da6f36cf4b835ff1be853962b22cc354d506f493b67a4303c88bbb40d57b \ + --hash=sha256:40f60819fbd5bad6e191ba1329bfafa09ab7f3f174b3d034d413ef5266963294 \ + --hash=sha256:43b26a865a61549919f8a42e094dfdb62847113cf776d84bd6b60e4e3fc20ea3 \ + --hash=sha256:4a03dd682f8e35a10234904e0b9508d705ff98cf962c5851ed052e9340df3d90 \ + --hash=sha256:62f382cddf3d2e52cf266e161aa522d54fd624b8cc567bc18f573d9d50d40e8e \ + --hash=sha256:7b98f0325be8450da70aa4a796c4f06852949fe031878b4aa1d6c417a412f314 \ + --hash=sha256:846a0739e595871041385d86d12af4b6999f921359b38affb99cdd6b54219a8f \ + --hash=sha256:a3080470559938a09a5d0ec558c005282e99ac77bf8211fb7b9a5c66390acd8d \ + --hash=sha256:ad841b78a476623955da270ab8d207c3c694aa5eba71f4792f65926dc46c6ee8 \ + --hash=sha256:afdd75d9735e44c639ffd6258ce04a2de3b208f148072c02478162d0944d9da3 \ + --hash=sha256:b4fbf9b552faff54742bcd0791ab1da5863363fb19047e68f6592be1ac2dab33 \ + --hash=sha256:b90c4e32d6ec089d3fa3518436bdf5ce4d902a0787dbd9bb09f37afe8b994317 \ + --hash=sha256:b91cfe4438c741aeff662d413fd2808ac901cc6229c838236840d11de4586d63 \ + --hash=sha256:bdb0593a42070b0a5f138b79b872289ee73c8e25b3f0bea6564e795b55b6bcdd \ + --hash=sha256:c4e4bca2bb68ce22320297dfa1a7bf070a5b20bcbaec4ee023f83d2f6e76496f \ + --hash=sha256:cec4ab14af9eae8501be3266ff50c3c2aecc017ba1e86c160209bb4f0423df6a \ + --hash=sha256:e83b4b2bf029f5104bc1227dbb7bf5ace6fd8fabaebffcd4f8106fafc69fc45f \ + --hash=sha256:e995b3734a46d41ae60b6097f7c51ba9958648c6d1e0935b7e0ee446ee4abe22 \ + --hash=sha256:f679d93dec7f7210575c85379a31322df4c46496f184ef650d3aba1484b38a2d \ + --hash=sha256:fd213bb5166e46974f113c8228daaef1732abc47cb561ce9c4c8eaed4bd3b09b \ + --hash=sha256:fdcb57b906dbc1f80666e6290e794ab8fb959a2e17aa5aee1758a85d1da4533f \ + --hash=sha256:ff424b01d090ffe1947ec7432b07f536912e0300458f9a7f48ea217dd8362b86 Jinja2==2.10 \ --hash=sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd \ --hash=sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4 @@ -19,22 +47,25 @@ statsd==3.1 \ PyYAML==3.11 \ --hash=sha256:c36c938a872e5ff494938b33b14aaa156cb439ec67548fcab3535bb78b0846e8 \ --hash=sha256:19bb3ac350ef878dda84a62d37c7d5c17a137386dde9c2ce7249c7a21d7f6ac9 -futures==2.2.0 \ - --hash=sha256:9fd22b354a4c4755ad8c7d161d93f5026aca4cfe999bd2e53168f14765c02cd6 \ - --hash=sha256:151c057173474a3a40f897165951c0e33ad04f37de65b6de547ddef107fd0ed3 -ipaddr==2.1.11 \ - --hash=sha256:1b555b8a8800134fdafe32b7d0cb52f5bdbfdd093707c3dd484c5ea59f1d98b7 pytz==2018.9 \ --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c -chardet==1.0.1 \ - --hash=sha256:4573d91ec9e6d88c6be1dfe004ea337d47754ed3924b4c181f777c613465578b -python-memcached==1.48 \ - --hash=sha256:66bbc62d9519f9d531b1f77e687d9f2f5e521cb906f1fd7231f403997e0110c4 -beautifulsoup4==4.3.2 \ - --hash=sha256:a2b29bd048ca2fe54a046b29770964738872a9747003a371344a93eedf7ad58e -certifi==0.0.8 \ - --hash=sha256:46ecf5f7526a08cc1f8bc8232adf0cffce046f46ceff95539daec42ebc4849ef +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +python-memcached==1.59 \ + --hash=sha256:4dac64916871bd3550263323fc2ce18e1e439080a2d5670c594cf3118d99b594 \ + --hash=sha256:a2e28637be13ee0bf1a8b6843e7490f9456fd3f2a4cb60471733c7b5d5557e4f +beautifulsoup4==4.7.1 \ + --hash=sha256:034740f6cb549b4e932ae1ab975581e6103ac8f942200a0e9759065984391858 \ + --hash=sha256:945065979fb8529dd2f37dbb58f00b661bdbcbebf954f93b32fdf5263ef35348 \ + --hash=sha256:ba6d5c59906a85ac23dadfe5c88deaf3e179ef565f4898671253e50a78680718 +soupsieve==1.9.1 \ + --hash=sha256:6898e82ecb03772a0d82bd0d0a10c0d6dcc342f77e0701d0ec4a8271be465ece \ + --hash=sha256:b20eff5e564529711544066d7dc0f7661df41232ae263619dede5059799cdfca +certifi==2019.3.9 \ + --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ + --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae django-recaptcha==1.0.3 \ --hash=sha256:bbb7365a42f95e32942e6a24657e7a760c9be660ef641daccec9fa8336faea69 django-picklefield==1.0.0 \ @@ -63,8 +94,9 @@ https://github.com/html5lib/html5lib-python/archive/3b3c1031b3dadf5369af83253fff --hash=sha256:3346704459d10b377e8b65c2d1dc5199d1d7879881c674abd2cf2f06fb62782f https://github.com/kurtmckee/feedparser/archive/2aa5286245ed40b13a0f77f997d124c1844bcb43.tar.gz#egg=feedparser \ --hash=sha256:70a0535bc9252a01f062713af800658a21eee34e5474b2823ea706eda222a26f -https://github.com/tweepy/tweepy/archive/3941489457db86710b770bc120409d6947904afa.tar.gz#egg=tweepy \ - --hash=sha256:a9b7728d94beeba624fae4be378542a222cf1c5cf845b916ba9a8b3f8f9e4559 +tweepy==3.7 \ + --hash=sha256:de56db37181e6ba2a375a7a81845ee6b2aee0e39a7bc8fb96a60c0370062f0a2 \ + --hash=sha256:fe85a79f58a01dd335968523b91c5fce760e7fe78bf25a6e71c72204fe499d0b django-extensions==2.1.6 \ --hash=sha256:109004f80b6f45ad1f56addaa59debca91d94aa0dc1cb19678b9364b4fe9b6f4 \ --hash=sha256:307766e5e6c1caffe76c5d99239d8115d14ae3f7cab2cd991fcffd763dad904b @@ -80,18 +112,19 @@ https://github.com/timmyomahony/django-pagedown/archive/78556863faa5654f749c5562 --hash=sha256:3e7bfaf54a560889838104c100d8b37930306243d8913942c4675cfcc54bccc7 https://github.com/jezdez/django-appconf/archive/d7ff3bb0c35a0c2ec83afdb31f7bb79e0f1b222c.tar.gz#egg=django-appconf \ --hash=sha256:3e80e498209d7253e2cc1a4f9b02e7da819179a910c9275d5bc232de48e67c33 -https://github.com/jsocol/commonware/archive/b5544185b2d24adc1eb512735990752400ce9cbd.tar.gz#egg=commonware \ - --hash=sha256:93d8231145d43ca02e1ec3cee355333afa8e72adb9cf815767e17595749774d4 +https://github.com/mozilla/commonware/archive/v0.5.0.tar.gz#egg=commonware \ + --hash=sha256:edefcb5081b702bccba7c279df259ef80ad1e8f9f889d99f3fe621fb05edf756 https://github.com/mozilla/nuggets/archive/ce506882b6943ea45ea4f98290fc4ef23e09a083.tar.gz#egg=nuggets \ --hash=sha256:79eabfcf8d793503c11778f9f5f9b2e891a3b67a43815431c30bc52b19c3431d dj-database-url==0.3.0 \ --hash=sha256:f2e273ed34acbb560962d5cf12917936d8df02297df09bd3089b8546d4584138 \ --hash=sha256:ca01768fdecde134301f3170743226f60edff5c3935f12437378ebd911506353 -basket-client==0.3.12 \ - --hash=sha256:d7aa5e6208eeeabb8f1387a7cbb2d0f2b35dfa889c1f034c86a88b4968db3bfe \ - --hash=sha256:2dd953cd03b3068d0e596aa32224488c486b1170e46387c5bc14f20737d6a2d8 -django-cors-headers==1.1.0 \ - --hash=sha256:fcd96e2be47c8eef34c650e007a6d546e19e7ee61041b89edbbbbe7619aa3987 +basket-client==1.0.0 \ + --hash=sha256:f9f9ff4870cc1d171ff9f00a8d968336d0e74178f5ac1612bc85be39a3f0cc2c \ + --hash=sha256:fee512c8deabde53aa33b08fa34a992de3085e781be192c4120adb086d0d20c1 +django-cors-headers==2.5.3 \ + --hash=sha256:c7987faa9aaef7f6a802b0f354a719e80a9158c284f530265ac792f1ee725e52 \ + --hash=sha256:ceacbd60dd5a65c95e65e74b5559bd4161aa3fe5713c44e1f3417a12bd41e7ba puente==0.5.0 \ --hash=sha256:4a17958f7d6a83cb9ff92593c40f34911abafaec6ad959916b754aaa3869f11f \ --hash=sha256:7ba1d07f9cee9657adf874bd94879b343fea81a783fdfa8e53885520477bf1ea @@ -101,37 +134,32 @@ babel==2.3.4 \ django-jinja==2.4.1 \ --hash=sha256:8a49d73de616a12075eee14c6d3bbab936261a463457d40348d8b8e2995cfbed \ --hash=sha256:ceaa0eeebc4d91a5800967e50f4f087f0b6457503e3c2af85dc199bed8732a9a -django-jinja-markdown==1.0 \ - --hash=sha256:b6ad614bc761b01f073833d4367547990e9c9898152e9b4b4b43b0ef7b9b95bc \ - --hash=sha256:31a49dcfdbff597caba5de093b5ec7f7320170e08fc0b1ef638c99caedd6c5f3 -Markdown==2.6.6 \ - --hash=sha256:9a292bb40d6d29abac8024887bcfc1159d7a32dc1d6f1f6e8d6d8e293666c504 \ - --hash=sha256:022239550fb4a84bcc3b3b42dff9a41efc56d773ef17c4f28016dd8f265c82d0 -requests==2.11.0 \ - --hash=sha256:8b9b147f3dff1fc4055ff794ff931f735ed25e87efe667ed7c845a4bafae9b73 \ - --hash=sha256:b2ff053e93ef11ea08b0e596a1618487c4e4c5f1006d7a1706e3671c57dea385 +django-jinja-markdown==1.0.1 \ + --hash=sha256:233dcde5d6895416f5613e4ca030e88f18a163e4b676e7aa815d191754f53085 \ + --hash=sha256:d41db694bbf3085b026df227428427b859478a2846b55088429e3ccc7115c0c5 +Markdown==3.1 \ + --hash=sha256:fc4a6f69a656b8d858d7503bda633f4dd63c2d70cf80abdc6eafa64c4ae8c250 \ + --hash=sha256:fe463ff51e679377e3624984c829022e2cfb3be5518726b06f608a07a3aad680 +requests==2.21.0 \ + --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ + --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b django-mozilla-product-details==0.13.1 \ --hash=sha256:fad2022fb9289aca574a9c1cb6bea90c9977302ad31494608b540d35c201e3a9 \ --hash=sha256:fdb87422ebd1b15ece9d338100c75d1dbe936930ca14e4f5644b9918b6b4f6f3 -raven==5.25.0 \ - --hash=sha256:aea92d403fddb9a82644880eca1590017ced49111ee8654d642013472efe3a53 \ - --hash=sha256:42a665e2d2febff0652102b495ed877e89cd983ef2a07174123be49ba571e479 +raven==6.10.0 \ + --hash=sha256:3fa6de6efa2493a7c827472e984ce9b020797d0da16f1db67197bcc23c8fae54 \ + --hash=sha256:44a13f87670836e153951af9a3c80405d36b43097db869a36e92809673692ce4 contextlib2==0.5.4 \ --hash=sha256:399f659f2a8b5d5d529f132e1136fc404fbbc28e34e4618c5c92bd595be9b162 \ --hash=sha256:710626cde569f51a87f216ff757fe60f5cd13ae8f8114706590510cd5649ce88 -django_csp==3.0 \ - --hash=sha256:e6e627955651235852508f98238f3f04c7ab9a77b625a6bd101cd0cf1a1e3419 \ - --hash=sha256:e44479be426ffd5371b7522f43f8c32f8597a5003c236f31bb98ff4497a20a21 +django_csp==3.5 \ + --hash=sha256:04600237701e6d6ff78ed7d41209ff923988148bf292c128f6b474b9befe444f \ + --hash=sha256:8b9997df89a7a936d7c397e051367f974aa1d1a97d0b32acb4300087b3bed071 pathlib2==2.1.0 \ --hash=sha256:24e0b33e1333b55e73c9d1e9a8342417d519f7789a9d3b440f4acd00ea45157e \ --hash=sha256:deb3a960c1d55868dfbcac98432358b92ba89d95029cddd4040db1f27405055c -querystringsafe_base64==0.2.0 \ - --hash=sha256:1064ad8d8bf5a88501e9f895b6560d1c45949645557fd6020abec8bbbe9462ec \ - --hash=sha256:682df1043abd816be9805cc06cb2da67add4311735af5de925f81dd2cfea82da \ - --hash=sha256:977ae9b93f64413e83f84763cac8046e0d17d0e0a599842cb0be80c573b85cb2 \ - --hash=sha256:fc15ea4ce494a4585497513ac947095262bdcad3cb304c6c870418d45824a659 \ - --hash=sha256:d54e3c7aa7a2e7fbd49ee4bd39595eadad636f56e16b33c14ba2e3a1f928f312 \ - --hash=sha256:1bf7cae5509a1e4bc17ebf3518aa1e2a21bd6e03a7f82f156eb2d84bdaa7dc58 +querystringsafe_base64==1.1.1 \ + --hash=sha256:bad9187c0406456020ee354be7f51d2e0dfc262dadde0449cbdd1fd78d437eed funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 @@ -174,19 +202,49 @@ docutils==0.14 \ babis==0.2.1 \ --hash=sha256:610416de748d0708a153dd7e2a42c95b4938689e033eb96b92e92b0a0049dc24 \ --hash=sha256:d900322567fe7acd5898f91e1e9a21dff47b4d29a38c28c98f2e6e6745b5108b -Pillow==5.1.0 \ - --hash=sha256:f0d4433adce6075efd24fc0285135248b0b50f5a58129c7e552030e04fe45c7f \ - --hash=sha256:81762cf5fca9a82b53b7b2d0e6b420e0f3b06167b97678c81d00470daa622d58 \ - --hash=sha256:b48401752496757e95304a46213c3155bc911ac884bed2e9b275ce1c1df3e293 \ - --hash=sha256:4d32c8e3623a61d6e29ccd024066cd1ba556555abfb4cd714155020e00107e3f \ - --hash=sha256:438a3faf5f702c8d0f80b9f9f9b8382cfa048ca6a0d64ef71b86b563b0ee0359 \ - --hash=sha256:040144ba422216aecf7577484865ade90e1a475f867301c48bf9fbd7579efd76 \ - --hash=sha256:b6cf18f9e653a8077522bb3aa753a776b117e3e0cc872c25811cfdf1459491c2 \ - --hash=sha256:e39142332541ed2884c257495504858b22c078a5d781059b07aba4c3a80d7551 \ - --hash=sha256:6c7cab6a05351cf61e469937c49dbf3cdf5ffb3eeac71f8d22dc9be3507598d8 \ - --hash=sha256:cee9bc75bff455d317b6947081df0824a8f118de2786dc3d74a3503fd631f4ef \ - --hash=sha256:f42a87cbf50e905f49f053c0b1fb86c911c730624022bf44c8857244fc4cdaca \ - --hash=sha256:2ee6364b270b56a49e8b8a51488e847ab130adc1220c171bed6818c0d4742455 +Pillow==6.0.0 \ + --hash=sha256:0683e80d81e840d401b687ebc00a02bbb23d0793c34d0852a5af64cfa1589540 \ + --hash=sha256:09c4e81c3277199898e8dc2d85d94febad87c41251ecbd447ba7d64d94765bd8 \ + --hash=sha256:0ee74a23022af9baf997e3016b4e090e4ff08688d37a6f49010338ab46cfe101 \ + --hash=sha256:10860baedfe5da7c43cd17835b091494dcc59dda5ad176a011713fe398ea6ac2 \ + --hash=sha256:15c056bfa284c30a7f265a41ac4cbbc93bdbfc0dfe0613b9cb8a8581b51a9e55 \ + --hash=sha256:1a4e06ba4f74494ea0c58c24de2bb752818e9d504474ec95b0aa94f6b0a7e479 \ + --hash=sha256:1c3c707c76be43c9e99cb7e3d5f1bee1c8e5be8b8a2a5eeee665efbf8ddde91a \ + --hash=sha256:1fd0b290203e3b0882d9605d807b03c0f47e3440f97824586c173eca0aadd99d \ + --hash=sha256:24114e4a6e1870c5a24b1da8f60d0ba77a0b4027907860188ea82bd3508c80eb \ + --hash=sha256:258d886a49b6b058cd7abb0ab4b2b85ce78669a857398e83e8b8e28b317b5abb \ + --hash=sha256:2734c55f7d054b0ad889c971136cbb0a5b35a921e27beaa44fdc2436af529c6e \ + --hash=sha256:2ac36ec56727a95bd5a04dfca6abce1db8042c31ee73b65796a42f31fd52d009 \ + --hash=sha256:2bc1002b573d107c0b172a5da0f34b4900b2ddc6c3296b82d601e966d5ac1959 \ + --hash=sha256:33c79b6dd6bc7f65079ab9ca5bebffb5f5d1141c689c9c6a7855776d1b09b7e8 \ + --hash=sha256:367385fc797b2c31564c427430c7a8630db1a00bd040555dfc1d5c52e39fcd72 \ + --hash=sha256:3c1884ff078fb8bf5f63d7d86921838b82ed4a7d0c027add773c2f38b3168754 \ + --hash=sha256:44e5240e8f4f8861d748f2a58b3f04daadab5e22bfec896bf5434745f788f33f \ + --hash=sha256:46aa988e15f3ea72dddd81afe3839437b755fffddb5e173886f11460be909dce \ + --hash=sha256:492e1e4df823b57f9334f591c78a1e0e65a361e92594534e0568eeeeea56bbba \ + --hash=sha256:50fb9e25d25cfcb50b2e6842c4e104e4f0b424be4624e1724532bf005c67589a \ + --hash=sha256:5ceadd60dbd1e56ab7faffbfee1df5ecb83c3f0420e47f652cd5306d70eb0296 \ + --hash=sha256:74d90d499c9c736d52dd6d9b7221af5665b9c04f1767e35f5dd8694324bd4601 \ + --hash=sha256:7eeac51fc37e6b19631a4b8e38b8261a074efcf7cc27fc16a6bee4697af7aaa5 \ + --hash=sha256:809c0a2ce9032cbcd7b5313f71af4bdc5c8c771cb86eb7559afd954cab82ebb5 \ + --hash=sha256:85d1ef2cdafd5507c4221d201aaf62fc9276f8b0f71bd3933363e62a33abc734 \ + --hash=sha256:8c3889c7681af77ecfa4431cd42a2885d093ecb811e81fbe5e203abc07e0995b \ + --hash=sha256:9218d81b9fca98d2c47d35d688a0cea0c42fd473159dfd5612dcb0483c63e40b \ + --hash=sha256:9319215530e236822169cbe92426cdc18d16b88c943fdf365a6309a89876e335 \ + --hash=sha256:96ec275c83bf839972d6a7dd7d685fdfb6a3233c3c382ecff839d04e7d53955d \ + --hash=sha256:9aa4f3827992288edd37c9df345783a69ef58bd20cc02e64b36e44bcd157bbf1 \ + --hash=sha256:9d80f44137a70b6f84c750d11019a3419f409c944526a95219bea0ac31f4dd91 \ + --hash=sha256:b7ebd36128a2fe93991293f997e44be9286503c7530ace6a55b938b20be288d8 \ + --hash=sha256:c30857e1fbf7d4a4b79d7d376eefaf293ea4307b8293d00a62e6f517f51bfe9b \ + --hash=sha256:c4c78e2c71c257c136cdd43869fd3d5e34fc2162dc22e4a5406b0ebe86958239 \ + --hash=sha256:c5472ea3945e8f9eb0659f37fc1f592fd06f4f725f0f03774a8999ad8c130334 \ + --hash=sha256:c6a842537f887be1fe115d8abb5daa9bc8cc124e455ff995830cc785624a97af \ + --hash=sha256:cf0a2e040fdf5a6d95f4c286c6ef1df6b36c218b528c8a9158ec2452a804b9b8 \ + --hash=sha256:cfd28aad6fc61f7a5d4ee556a997dc6e5555d9381d1390c00ecaf984d57e4232 \ + --hash=sha256:d0fd1ec2e7c3e0aeaae999efe83f5d0f42c1160a1f8be5120d40857d20baa452 \ + --hash=sha256:dca5660e25932771460d4688ccbb515677caaf8595f3f3240ec16c117deff89a \ + --hash=sha256:de7aedc85918c2f887886442e50f52c1b93545606317956d65f342bd81cb4fc3 \ + --hash=sha256:e6c0bbf8e277b74196e3140c35f9a1ae3eafd818f7f2d3a15819c49135d6c062 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 everett==0.9 \ @@ -229,11 +287,6 @@ PyBrowserID==0.14.0 \ hawkauthlib==2.0.0 \ --hash=sha256:935878d3a75832aa76f78ddee13491f1466cbd69a8e7e4248902763cf9953ba9 \ --hash=sha256:effd64a2572e3c0d9090b55ad2180b36ad50e7760bea225cb6ce2248f421510d -enum34==1.1.6 \ - --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ - --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ - --hash=sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1 \ - --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 cffi==1.11.5 \ --hash=sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50 \ --hash=sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596 \ @@ -273,11 +326,20 @@ asn1crypto==0.24.0 \ idna==2.7 \ --hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e \ --hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16 -ipaddress==1.0.22 \ - --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ - --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c WebOb==1.8.4 \ --hash=sha256:fc8c466af474e2e2775f1aef7afb902ed8b82e597eb0b13624818a34e8bfe720 \ --hash=sha256:a48315158db05df0c47fbdd061b57ba0ba85bdd0b6ea9dca87511b4b7c798e99 pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 +requests-oauthlib==1.2.0 \ + --hash=sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57 \ + --hash=sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140 \ + --hash=sha256:dd5a0499abfefd087c6dd96693cbd5bfd28aa009719a7f85ab3fabe3956ef19a +oauthlib==3.0.1 \ + --hash=sha256:0ce32c5d989a1827e3f1148f98b9085ed2370fc939bf524c9c851d8714797298 \ + --hash=sha256:3e1e14f6cde7e5475128d30e97edc3bfb4dc857cb884d8714ec161fdbb3b358e +PySocks==1.6.8 \ + --hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672 +urllib3==1.24.1 \ + --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \ + --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 diff --git a/requirements/dev.txt b/requirements/dev.txt index fe699b689e..c93ba0d036 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -3,59 +3,61 @@ Pygments==2.2.0 \ --hash=sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d \ --hash=sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc -flake8==2.0 \ - --hash=sha256:8dce4f7e64cc202cc6da93eab84b2ce660110ff684b6738bba64a0a431b3bc69 -mccabe==0.3 \ - --hash=sha256:89cf6bf4ff644ff8b912e2fa7e2abb68d57a8e7e918e393c72840d30d2ebcf0b \ - --hash=sha256:3d8ca9bf65c5014f469180544d1dd5bb5b9df709aad6304f9c2e4370ae0a7b7c -pep8==1.6.2 \ - --hash=sha256:4aa129df8d9007b192bf82013f415533994652d7caa930d002687eb42a6c2a41 \ - --hash=sha256:b8b7e35630b5539e26a197dfc6005be9e1e9a135496b377723a8ebc01b9bcbff -pyflakes==0.8.1 \ - --hash=sha256:ac4571695c10ce1536bcdba1a294b9f2d3e6cc9d0ea171b67d50a0864ce3e042 \ - --hash=sha256:3fa80a10b36d51686bf7744f5dc99622cd5c98ce8ed64022e629868aafc17769 -pyquery==1.0 \ - --hash=sha256:fb2345065647211507d23355b1026d995ded40e088d86fcae262562921d80408 \ - --hash=sha256:9346fe16961119eb1c46e7d36ef0ff83c87e12514c64a29b204b1ee900f1f644 +flake8==3.7.7 \ + --hash=sha256:859996073f341f2670741b51ec1e67a01da142831aa1fdc6242dbf88dffbe661 \ + --hash=sha256:a796a115208f5c03b18f332f7c11729812c8c3ded6c46319c59b53efd3819da8 +mccabe==0.6.1 \ + --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ + --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f +entrypoints==0.3 \ + --hash=sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19 \ + --hash=sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451 +pycodestyle==2.5.0 \ + --hash=sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56 \ + --hash=sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c +pep8==1.7.1 \ + --hash=sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee \ + --hash=sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374 +pyflakes==2.1.1 \ + --hash=sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0 \ + --hash=sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2 +pyquery==1.4.0 \ + --hash=sha256:07987c2ed2aed5cba29ff18af95e56e9eb04a2249f42ce47bddfb37f487229a3 \ + --hash=sha256:4771db76bd14352eba006463656aef990a0147a0eeaf094725097acfa90442bf factory-boy==2.2.1 \ --hash=sha256:d94c63814195cae8ffad89046d425cdab414aecf2c0f2cb17c88cbe49bbb554c coverage==3.6 \ --hash=sha256:df3bf169d4727f3fad146ca715a49a1f72a8258689651ef9de908022e739700d chkcrontab==1.6 \ --hash=sha256:10fd86e7e3d3d0339e0e93e0876a8702eac82649ec3b4abe4d350dc08c389a0e -translate-toolkit==1.8.0 \ - --hash=sha256:b146d8cdc287db07f6ef4491766d841a931b1b267040fef81eb559a475fe3db9 +translate-toolkit==2.3.1 \ + --hash=sha256:1d156b376487254ac2e4d99cf36e3cf8e891e58aeacf2a29584bf2063d864ebb responses==0.5.1 \ --hash=sha256:3a907f7aae2fd2286d06cfdf238957786c38bbcadc451adceecc769a4ef882b7 \ --hash=sha256:8cad64c45959a651ceaf0023484bd26180c927fea64a81e63d334ddf6377ecea cookies==2.2.1 \ --hash=sha256:15bee753002dff684987b8df8c235288eb8d45f8191ae056254812dfd42c81d3 \ --hash=sha256:d6b698788cae4cfa4e62ef8643a9ca332b79bd96cb314294b864ae8d7eb3ee8e -greenlet==0.4.12 \ - --hash=sha256:96888e47898a471073b394ea641b7d675c1d054c580dd4a04a382bd34e67d89e \ - --hash=sha256:bc339de0e0969de5118d0b62a080a7611e2ba729a90f4a3ad78559c51bc5576d \ - --hash=sha256:b8ab98f8ae25938326dc4c21e3689a933531500ae4f3bfcefe36e3e25fda4dbf \ - --hash=sha256:d2d5103f6cba131e1be660230018e21f276911d2b68b629ead1c5cb5e5472ac7 \ - --hash=sha256:416a3328d7e0a19aa1df3ec09524a109061fd7b80e010ef0dff9f695b4ac5e20 \ - --hash=sha256:6803d8c6b235c861c50afddf00c7467ffbcd5ab960d137ff0f9c36f2cb11ee4b \ - --hash=sha256:76dab055476dd4dabb00a967b4df1990b25542d17eaa40a18f66971d10193e0b \ - --hash=sha256:21232907c8c26838b16915bd8fbbf82fc70c996073464cc70981dd4a96bc841c \ - --hash=sha256:70b9ff28921f5a3c03df4896ec8c55f5f94c593d7a79abd98b4c5c4a692ba873 \ - --hash=sha256:7114b757b4146f4c87a0f00f1e58abd4c4729836679af0fc37266910a4a72eb0 \ - --hash=sha256:0d90c709355ed13f16676f84e5a9cd67826a9f5c5143381c21e8fc3100ade1f1 \ - --hash=sha256:ebae83b6247f83b1e8d887733dfa8046ce6e29d8b3e2a7380256e9de5c6ae55d \ - --hash=sha256:e841e3ece633acae5e2bf6102140a605ffee7d5d4921dca1625c5fdc0f0b3248 \ - --hash=sha256:3e5e9be157ece49e4f97f3225460caf758ccb00f934fcbc5db34367cc1ff0aee \ - --hash=sha256:e77b708c37b652c7501b9f8f6056b23633c567aaa0d29edfef1c11673c64b949 \ - --hash=sha256:0da1fc809c3bdb93fbacd0f921f461aacd53e554a7b7d4e9953ba09131c4206e \ - --hash=sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 \ - --hash=sha256:e5451e1ce06b74a4861576c2db74405a4398c4809a105774550a9e52cfc8c4da \ - --hash=sha256:9c407aa6adfd4eea1232e81aa9f3cb3d9b955a9891c4819bf9b498c77efba14b \ - --hash=sha256:b56ac981f07b77e72ad5154278b93396d706572ea52c2fce79fee2abfcc8bfa6 \ - --hash=sha256:e4c99c6010a5d153d481fdaf63b8a0782825c0721506d880403a3b9b82ae347e -urllib3==1.22 \ - --hash=sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b \ - --hash=sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f +greenlet==0.4.15 \ + --hash=sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0 \ + --hash=sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28 \ + --hash=sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8 \ + --hash=sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304 \ + --hash=sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0 \ + --hash=sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214 \ + --hash=sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043 \ + --hash=sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6 \ + --hash=sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625 \ + --hash=sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc \ + --hash=sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638 \ + --hash=sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163 \ + --hash=sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4 \ + --hash=sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490 \ + --hash=sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248 \ + --hash=sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939 \ + --hash=sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87 \ + --hash=sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720 \ + --hash=sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656 blessings==1.6.1 \ --hash=sha256:26dbaf2f89c3e6dee11c10f7c0b85756ed75cf602b1bb7935b4efd8ed67a000f \ --hash=sha256:466e43ff45723b272311de0437649df464b33b4daba7a54c69493212958e19c7 \ @@ -76,37 +78,59 @@ braceexpand==0.1.1 \ execnet==1.4.1 \ --hash=sha256:f66dd4a7519725a1b7e14ad9ae7d3df8e09b2da88062386e08e941cafc0ef3e6 \ --hash=sha256:d2b909c7945832e1c19cfacd96e78da68bdadc656440cfc7dfe59b766744eb8c -py==1.4.31 \ - --hash=sha256:4a3e4f3000c123835ac39cab5ccc510642153bc47bc1f13e2bbb53039540ae69 \ - --hash=sha256:a6501963c725fc2554dabfece8ae9a8fb5e149c0ac0a42fd2b02c5c1c57fc114 +py==1.8.0 \ + --hash=sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa \ + --hash=sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53 PyPOM==1.0 \ --hash=sha256:a8728981970404c3c9a14efeb23da92191749a4a4ad489130fd855e4cf8f154e \ --hash=sha256:5c98e3d8ef2c7f96c3365a3b8295560446f2447eecdcfe393a30abf2fcc16d96 -pytest==3.0.1 \ - --hash=sha256:ba6ae459222c3ee650d9e7a3f8cbf69c23c20ed6977496e9081f3b8da19107d0 \ - --hash=sha256:e82bc0596ee96b2287c08705cfcb6898db1fe4b5c87db3b6823f1fdd77fb3ff1 -pytest-django==3.0.0 \ - --hash=sha256:7898731205fa245926c800273dcffcd9b25e0b660a363ecb6cc2901ffe4aa1cc \ - --hash=sha256:5952283b0e6e0f9d8cb46803636e3940e1c77da8a529f03a8a4320f52d9bf560 -pytest-html==1.14.1 \ - --hash=sha256:bc6007504193e93b135aa48eedd798957beef895c090a557984a65e97fb2c59e \ - --hash=sha256:6f2dd32a98e76503cd1bfa8647268d8622b848c7c99dbef5840a426dd124e8f0 -pytest-selenium==1.11.1 \ - --hash=sha256:b98f97f7fc1f24b9806ab369df3077cf43cce0884e427ad7a21b9239ac8bef47 \ - --hash=sha256:91a94a1221d180c7d455b5b1bd50d5cbce797f0ca81d28239075048abbf416b4 -pytest-variables==1.5.1 \ - --hash=sha256:1365c5f2c3be70d1b165272c1ab76ba0692c901db9b98c0b09062cc70b55a36d \ - --hash=sha256:8f2b7c16c54bfe93561c7a36a5e9d8780bdcd36e6e37d9f7b3b51b656449e2ea +pytest==4.4.1 \ + --hash=sha256:3773f4c235918987d51daf1db66d51c99fac654c81d6f2f709a046ab446d5e5d \ + --hash=sha256:b7802283b70ca24d7119b32915efa7c409982f59913c1a6c0640aacf118b95f5 +more-itertools==7.0.0 \ + --hash=sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7 \ + --hash=sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a +atomicwrites==1.3.0 \ + --hash=sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4 \ + --hash=sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6 +attrs==19.1.0 \ + --hash=sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79 \ + --hash=sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399 +pluggy==0.9.0 \ + --hash=sha256:19ecf9ce9db2fce065a7a0586e07cfb4ac8614fe96edf628a264b1c70116cf8f \ + --hash=sha256:84d306a647cc805219916e62aab89caa97a33a1dd8c342e87a37f91073cd4746 +pytest-django==3.4.8 \ + --hash=sha256:30d773f1768e8f214a3106f1090e00300ce6edfcac8c55fd13b675fe1cbd1c85 \ + --hash=sha256:4d3283e774fe1d40630ee58bf34929b83875e4751b525eeb07a7506996eb42ee +pytest-html==1.20.0 \ + --hash=sha256:648b7ba1d6035cc021d607e9d44f4dc06e916bdb04e09572dd04fb82eecab9ed \ + --hash=sha256:a7c65cdd9d5e4d09cef2f500ca801f80c1110204f24e5b84d019c6f919b15e9e +pytest-selenium==1.16.0 \ + --hash=sha256:18c5db66512efcf6db9e32cfe8dcd0b69ef11ac1e8b9a4ce8a1b4813e6c73c9a \ + --hash=sha256:c4d5a73d501f9fb3afb70e781f6fc2106a7b5f64387ed449b15eb1df61fa7915 +pytest-variables==1.7.1 \ + --hash=sha256:59c00b95779657532ac5f8209b28b5d447c8b4bc4210c1d6bdf9a42aa201f9b0 \ + --hash=sha256:7808b77b643b9f8a24f1ee1c32132648b1c62ab93956f20fe101dde66db6d09a selenium==3.5.0 \ --hash=sha256:69b479bdfa1ab2fee86a75086f7d5c6ef93cdad8cb6521cb0596554c6722f3eb \ --hash=sha256:267418f5fde1a4f8c180e5b8f45bd57c6d45b1f7d8fa5ad699a48e9a98fa79a3 -pytest-xdist==1.15.0 \ - --hash=sha256:6238395f8bd050f9288a3b10f34330edece80f4424cf2b4204d6e7d622f0f00b -pytest-rerunfailures==2.0.1 \ - --hash=sha256:5b77224649ec1df6d5f0234a87cdea76ce1e74f5172e1dd8b80d79bbb8a6fdff -pytest-base-url==1.1.0 \ - --hash=sha256:90a1ce6a00a558231117b5aee32300edecbc0c2fd701c13e8cec62177900d28c \ - --hash=sha256:001c1b2678dae82c2891db415251361b29ed6824cb2085e4821635119499071e -pytest-metadata==1.3.0 \ - --hash=sha256:2aa89161636c12418b2f8dfe226b6007a50ce3570f2b09fe23d72506fb21a3bc \ - --hash=sha256:b8735ad24a4974b35ca2d37a443596a26e0986754b0841c46231f4a6f6fa92c9 +pytest-xdist==1.28.0 \ + --hash=sha256:b0bb4b0293ee8657b9eb3ff334a3b6aac4db74fd4a86b81e1982c879237a47eb \ + --hash=sha256:f83a485293e81fd57c8a5a85a3f12473a532c5ca7dec518857cbb72766bb526c +pytest-forked==1.0.2 \ + --hash=sha256:5fe33fbd07d7b1302c95310803a5e5726a4ff7f19d5a542b7ce57c76fed8135f \ + --hash=sha256:d352aaced2ebd54d42a65825722cb433004b4446ab5d2044851d9cc7a00c9e38 +pytest-rerunfailures==7.0 \ + --hash=sha256:1180a0f98975e1e1a2e055c87c1159cbd3bace8ceb71b1e7ffe4ace6121e7801 \ + --hash=sha256:f3c9cf31339bf87b048c09dadb633a81156fa4899527fffc55cde105d04ed5fd +pytest-base-url==1.4.1 \ + --hash=sha256:31e42366a5fc22f450b398837dc819bb7569f5e6bd5d74e494b2b9ec239876d1 \ + --hash=sha256:7425e8163345494ac7f544e99c6f3e5a08f4228bee5e26013b98c462a4d31f6e +pytest-metadata==1.8.0 \ + --hash=sha256:2071a59285de40d7541fde1eb9f1ddea1c9db165882df82781367471238b66ba \ + --hash=sha256:c29a1fb470424926c63154c1b632c02585f2ba4282932058a71d35295ff8c96d +diff-match-patch==20121119 \ + --hash=sha256:9dba5611fbf27893347349fd51cc1911cb403682a7163373adacc565d11e2e4c +cssselect==1.0.3 \ + --hash=sha256:066d8bc5229af09617e24b3ca4d52f1f9092d9e061931f4184cd572885c23204 \ + --hash=sha256:3b5103e8789da9e936a68d993b70df732d06b8bb9a337a05ed4eb52c17ef7206 diff --git a/requirements/prod.txt b/requirements/prod.txt index d3875a891e..c345f267e5 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -8,35 +8,34 @@ apscheduler==3.0.5 \ --hash=sha256:009dcf552035b30ee967f2677d0d7a49a88f2d36291c42669aa069dd549da9e4 tzlocal==1.2.2 \ --hash=sha256:cbbaa4e9d25c36386f12af9febe315139fdd39317b91abcb42d782a5e93e525d -supervisor==3.2.3 \ - --hash=sha256:3d6f0304c8ce74ab2100dfc4ab0f70050568504216f9508a81b8ed269aec9705 +supervisor==4.0.1 \ + --hash=sha256:3268709d7e3d4e728f88fd06a29215916e1e59ff5fad5fead9de856df6f98382 \ + --hash=sha256:dbc7417425a354f304eab981999b1378ea8c7405c43673fbbc794c7a929f85cc meld3==1.0.2 \ --hash=sha256:b28a9bfac342aadb4557aa144bea9f8e6208bfb0596190570d10a892d35ff7dc \ --hash=sha256:f7b754a0fde7a4429b2ebe49409db240b5699385a572501bb0d5627d299f9558 meinheld==0.6.1 \ --hash=sha256:40d9dbce0165b2d9142f364d26fd6d59d3682f89d0dfe2117717a8ddad1f4133 \ --hash=sha256:293eff4983b7fcbd9134b47706b22189883fe354993bd10163c65869d141e565 -greenlet==0.4.12 \ - --hash=sha256:96888e47898a471073b394ea641b7d675c1d054c580dd4a04a382bd34e67d89e \ - --hash=sha256:bc339de0e0969de5118d0b62a080a7611e2ba729a90f4a3ad78559c51bc5576d \ - --hash=sha256:b8ab98f8ae25938326dc4c21e3689a933531500ae4f3bfcefe36e3e25fda4dbf \ - --hash=sha256:d2d5103f6cba131e1be660230018e21f276911d2b68b629ead1c5cb5e5472ac7 \ - --hash=sha256:416a3328d7e0a19aa1df3ec09524a109061fd7b80e010ef0dff9f695b4ac5e20 \ - --hash=sha256:6803d8c6b235c861c50afddf00c7467ffbcd5ab960d137ff0f9c36f2cb11ee4b \ - --hash=sha256:76dab055476dd4dabb00a967b4df1990b25542d17eaa40a18f66971d10193e0b \ - --hash=sha256:21232907c8c26838b16915bd8fbbf82fc70c996073464cc70981dd4a96bc841c \ - --hash=sha256:70b9ff28921f5a3c03df4896ec8c55f5f94c593d7a79abd98b4c5c4a692ba873 \ - --hash=sha256:7114b757b4146f4c87a0f00f1e58abd4c4729836679af0fc37266910a4a72eb0 \ - --hash=sha256:0d90c709355ed13f16676f84e5a9cd67826a9f5c5143381c21e8fc3100ade1f1 \ - --hash=sha256:ebae83b6247f83b1e8d887733dfa8046ce6e29d8b3e2a7380256e9de5c6ae55d \ - --hash=sha256:e841e3ece633acae5e2bf6102140a605ffee7d5d4921dca1625c5fdc0f0b3248 \ - --hash=sha256:3e5e9be157ece49e4f97f3225460caf758ccb00f934fcbc5db34367cc1ff0aee \ - --hash=sha256:e77b708c37b652c7501b9f8f6056b23633c567aaa0d29edfef1c11673c64b949 \ - --hash=sha256:0da1fc809c3bdb93fbacd0f921f461aacd53e554a7b7d4e9953ba09131c4206e \ - --hash=sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 \ - --hash=sha256:e5451e1ce06b74a4861576c2db74405a4398c4809a105774550a9e52cfc8c4da \ - --hash=sha256:9c407aa6adfd4eea1232e81aa9f3cb3d9b955a9891c4819bf9b498c77efba14b \ - --hash=sha256:b56ac981f07b77e72ad5154278b93396d706572ea52c2fce79fee2abfcc8bfa6 \ - --hash=sha256:e4c99c6010a5d153d481fdaf63b8a0782825c0721506d880403a3b9b82ae347e -newrelic==4.18.0.118 \ - --hash=sha256:3cd496022c6906ee3cdbf27c67ec3e03922830bfebbca4549ef6f2e58cf719e2 +greenlet==0.4.15 \ + --hash=sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0 \ + --hash=sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28 \ + --hash=sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8 \ + --hash=sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304 \ + --hash=sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0 \ + --hash=sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214 \ + --hash=sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043 \ + --hash=sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6 \ + --hash=sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625 \ + --hash=sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc \ + --hash=sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638 \ + --hash=sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163 \ + --hash=sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4 \ + --hash=sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490 \ + --hash=sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248 \ + --hash=sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939 \ + --hash=sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87 \ + --hash=sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720 \ + --hash=sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656 +newrelic==4.4.1.104 \ + --hash=sha256:eb60752a2c2a9904ea1eaf6b25dfbe8181e02fca9c11f895c13469057971b343 diff --git a/scripts/check_calendars.py b/scripts/check_calendars.py index 7160345617..5e50c82fef 100644 --- a/scripts/check_calendars.py +++ b/scripts/check_calendars.py @@ -25,8 +25,7 @@ def check_if_correct_parse(ics_file): def run(*args): calendars_dir = os.path.join('media', 'caldata') - ics_files = map(lambda x: os.path.join(calendars_dir, x), - filter(get_ics, os.listdir(calendars_dir))) + ics_files = [os.path.join(calendars_dir, x) for x in list(filter(get_ics, os.listdir(calendars_dir)))] format_str = "Failed to parse the icalendar file: {}. {}" check_failed = False @@ -35,7 +34,7 @@ def run(*args): check_if_correct_parse(f) except ValueError as ve: check_failed = True - print format_str.format(f, ve.message) + print(format_str.format(f, ve.message)) if check_failed: sys.exit(1) diff --git a/scripts/update_tableau_data.py b/scripts/update_tableau_data.py index 457801c8f2..2cf93403c2 100644 --- a/scripts/update_tableau_data.py +++ b/scripts/update_tableau_data.py @@ -1,4 +1,4 @@ -import urlparse +import urllib.parse import sys from django.conf import settings @@ -13,7 +13,7 @@ except ImportError: from bedrock.mozorg.models import ContributorActivity -urlparse.uses_netloc.append('mysql') +urllib.parse.uses_netloc.append('mysql') QUERY = ('SELECT c_date, team_name, source_name, count(*) AS total, IFNULL(SUM(is_new), 0) AS new ' 'FROM contributor_active {where} GROUP BY c_date, team_name, source_name') @@ -26,13 +26,13 @@ def process_name_fields(team_name): def get_external_data(): """Get the data and return it as a tuple of tuples.""" if not settings.TABLEAU_DB_URL: - print 'Must set TABLEAU_DB_URL.' + print('Must set TABLEAU_DB_URL.') sys.exit(1) - url = urlparse.urlparse(settings.TABLEAU_DB_URL) + url = urllib.parse.urlparse(settings.TABLEAU_DB_URL) if not url.path: # bad db url - print 'TABLEAU_DB_URL not parseable.' + print('TABLEAU_DB_URL not parseable.') sys.exit(1) con_data = { diff --git a/setup.cfg b/setup.cfg index a2c102976a..853bd7dc5f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,28 @@ [flake8] -ignore=E121,E123,E124,E126,E127,E128 max-line-length=150 statistics=True +exclude= + docs + bedrock/settings + community_data + node_modules + static_build + static_final + static + bedrock/externalfiles/files_cache + mofo_security_advisories + release_notes + content_cards + product_details_json + community_data + www_config [tool:pytest] -addopts = --showlocals -r a --ignore=node_modules -DJANGO_SETTINGS_MODULE = bedrock.settings +# Hiding warnings for now, the noise is making test fixes harder +addopts = --showlocals -r a --ignore=node_modules -p no:warnings +DJANGO_SETTINGS_MODULE = bedrock.settings.test sensitive_url = (mozilla\.(com|org)|bedrock-prod) +testpaths = + bedrock + lib + tests diff --git a/setup.py b/setup.py index 58dbd933f0..22da3da124 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,6 @@ -import os - from setuptools import setup, find_packages - -setup(name='project', +setup(name='bedrock', version='1.0', description='Django application.', long_description='', @@ -12,6 +9,6 @@ setup(name='project', license='', url='', include_package_data=True, - classifiers = [], + classifiers=[], packages=find_packages(exclude=['tests']), install_requires=[]) diff --git a/tests/conftest.py b/tests/conftest.py index 245f56ed72..1d49a9a4fc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,8 +3,22 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import pytest +from django.db.backends.sqlite3.base import BaseDatabaseWrapper as SQLiteWrapper + + +# pytest-django is currently broken by attempting to set the read only +# property ``allow_thread_sharing`` (as of Django 2.2), so work around +# that. +def mutable_allow_thread_sharing(self, allow): + if allow: + if not self.allow_thread_sharing: + self.inc_thread_sharing() + + +SQLiteWrapper.allow_thread_sharing = property( + SQLiteWrapper.allow_thread_sharing.__get__, mutable_allow_thread_sharing) @pytest.fixture(scope='session') def base_url(base_url, request): - return base_url or request.getfuncargvalue('live_server').url + return base_url or request.getfixturevalue('live_server').url diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 08055d0853..18551925d8 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -4,10 +4,6 @@ import pytest -VIEWPORT = { - 'desktop': {'width': 1280, 'height': 1024}, - 'mobile': {'width': 320, 'height': 480}} - @pytest.fixture def capabilities(request, capabilities): @@ -46,12 +42,12 @@ def internet_explorer(selenium): @pytest.fixture(autouse=True) def filter_capabilities(request): marker = None - if request.node.get_marker('skip_if_firefox') and request.getfuncargvalue('firefox'): - marker = request.node.get_marker('skip_if_firefox') - if request.node.get_marker('skip_if_not_firefox') and not request.getfuncargvalue('firefox'): - marker = request.node.get_marker('skip_if_not_firefox') - if request.node.get_marker('skip_if_internet_explorer') and request.getfuncargvalue('internet_explorer'): - marker = request.node.get_marker('skip_if_internet_explorer') + if request.node.get_closest_marker('skip_if_firefox') and request.getfixturevalue('firefox'): + marker = request.node.get_closest_marker('skip_if_firefox') + if request.node.get_closest_marker('skip_if_not_firefox') and not request.getfixturevalue('firefox'): + marker = request.node.get_closest_marker('skip_if_not_firefox') + if request.node.get_closest_marker('skip_if_internet_explorer') and request.getfixturevalue('internet_explorer'): + marker = request.node.get_closest_marker('skip_if_internet_explorer') if marker: reason = marker.kwargs.get('reason') or marker.name @@ -59,9 +55,12 @@ def filter_capabilities(request): @pytest.fixture -def selenium(request, selenium): - viewport = VIEWPORT['desktop'] - if request.keywords.get('viewport') is not None: - viewport = VIEWPORT[request.keywords.get('viewport').args[0]] - selenium.set_window_size(viewport['width'], viewport['height']) +def selenium(selenium): + selenium.set_window_size(1280, 1024) # width, height + return selenium + + +@pytest.fixture +def selenium_mobile(selenium): + selenium.set_window_size(320, 480) # width, height return selenium diff --git a/tests/functional/firefox/test_accounts.py b/tests/functional/firefox/test_accounts.py index 73b0778795..5dbe2a0498 100644 --- a/tests/functional/firefox/test_accounts.py +++ b/tests/functional/firefox/test_accounts.py @@ -3,7 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. import pytest -import urllib +from urllib.parse import unquote from pages.firefox.accounts import FirefoxAccountsPage @@ -13,7 +13,7 @@ def test_account_form(base_url, selenium): page = FirefoxAccountsPage(selenium, base_url, params='').open() page.join_firefox_form.type_email('success@example.com') page.join_firefox_form.click_continue() - url = urllib.unquote(selenium.current_url) + url = unquote(selenium.current_url) assert 'email=success@example.com' in url, 'Email address is not in URL' diff --git a/tests/functional/test_contact.py b/tests/functional/test_contact.py index 7029d7b6a6..ba4eff3f1e 100644 --- a/tests/functional/test_contact.py +++ b/tests/functional/test_contact.py @@ -66,22 +66,20 @@ def test_communities_region_menus(slug, base_url, selenium): @pytest.mark.nondestructive -@pytest.mark.viewport('mobile') -def test_spaces_mobile_navigation(base_url, selenium): - page = SpacesPage(selenium, base_url, slug='').open() +def test_spaces_mobile_navigation(base_url, selenium_mobile): + page = SpacesPage(selenium_mobile, base_url, slug='').open() assert not page.is_desktop_nav_displayed assert page.is_mobile_nav_displayed expected_url = '/contact/spaces/mountain-view/' page.select_mobile_nav_item('Mountain View', expected_url) - assert expected_url in selenium.current_url, 'Page did not navigate to expected URL' + assert expected_url in selenium_mobile.current_url, 'Page did not navigate to expected URL' @pytest.mark.nondestructive -@pytest.mark.viewport('mobile') -def test_communities_mobile_navigation(base_url, selenium): - page = CommunitiesPage(selenium, base_url, slug='').open() +def test_communities_mobile_navigation(base_url, selenium_mobile): + page = CommunitiesPage(selenium_mobile, base_url, slug='').open() assert not page.is_desktop_nav_displayed assert page.is_mobile_nav_displayed expected_url = '/contact/communities/north-america/' page.select_mobile_nav_item('North America', expected_url) - assert expected_url in selenium.current_url, 'Page did not navigate to expected URL' + assert expected_url in selenium_mobile.current_url, 'Page did not navigate to expected URL' diff --git a/tests/functional/test_history.py b/tests/functional/test_history.py index 6faf9c693c..3720c4c153 100644 --- a/tests/functional/test_history.py +++ b/tests/functional/test_history.py @@ -16,9 +16,8 @@ def test_slideshow_displayed(base_url, selenium): @pytest.mark.nondestructive -@pytest.mark.viewport('mobile') -def test_list_displayed(base_url, selenium): - page = HistoryPage(selenium, base_url).open() +def test_list_displayed(base_url, selenium_mobile): + page = HistoryPage(selenium_mobile, base_url).open() assert not page.is_slideshow_displayed assert not page.is_previous_button_displayed assert not page.is_next_button_displayed diff --git a/tests/functional/test_link_hreflang_tags.py b/tests/functional/test_link_hreflang_tags.py index 146cb64c2b..35deb9b020 100644 --- a/tests/functional/test_link_hreflang_tags.py +++ b/tests/functional/test_link_hreflang_tags.py @@ -21,4 +21,4 @@ def test_link_hreflang_tags(url, locales, base_url): full_url = '{}/{}{}'.format(base_url, locale, url) link_url = '{}/{}{}'.format('https://www.mozilla.org', locale, url) resp = requests.get(full_url, timeout=5) - assert LINK_TEMPLATE.format(url=link_url) in resp.content + assert LINK_TEMPLATE.format(url=link_url).encode('utf-8') in resp.content diff --git a/tests/functional/test_navigation.py b/tests/functional/test_navigation.py index 58c4f693c5..6ab68a41a5 100644 --- a/tests/functional/test_navigation.py +++ b/tests/functional/test_navigation.py @@ -23,19 +23,18 @@ def test_navigation(base_url, selenium): @pytest.mark.nondestructive -@pytest.mark.viewport('mobile') -def test_mobile_navigation(base_url, selenium): - page = HomePage(selenium, base_url).open() +def test_mobile_navigation(base_url, selenium_mobile): + page = HomePage(selenium_mobile, base_url).open() page.navigation.show() firefox_desktop_page = page.navigation.open_firefox_desktop_page() - assert firefox_desktop_page.seed_url in selenium.current_url + assert firefox_desktop_page.seed_url in selenium_mobile.current_url page.open() page.navigation.show() developer_edition_page = page.navigation.open_developer_edition_page() - assert developer_edition_page.seed_url in selenium.current_url + assert developer_edition_page.seed_url in selenium_mobile.current_url page.open() page.navigation.show() about_page = page.navigation.open_about_page() - assert about_page.seed_url in selenium.current_url + assert about_page.seed_url in selenium_mobile.current_url diff --git a/tests/pages/__init__.py b/tests/pages/__init__.py index ae7d693d43..556264778f 100644 --- a/tests/pages/__init__.py +++ b/tests/pages/__init__.py @@ -12,4 +12,5 @@ def scroll_element_into_view(self, strategy, locator, x=0, y=0): 'window.scrollBy(arguments[1], arguments[2]);', el, x, y) return el + WebView.scroll_element_into_view = scroll_element_into_view diff --git a/tests/pages/base.py b/tests/pages/base.py index 50cfc398bd..707a63d4fa 100644 --- a/tests/pages/base.py +++ b/tests/pages/base.py @@ -6,7 +6,7 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support.select import Select from pypom import Page, Region -from regions.newsletter import NewsletterEmbedForm, LegacyNewsletterEmbedForm +from pages.regions.newsletter import NewsletterEmbedForm, LegacyNewsletterEmbedForm class BasePage(Page): @@ -60,8 +60,10 @@ class BasePage(Page): @property def is_displayed(self): toggle = self.find_element(*self._toggle_locator) - return (self.find_element(*self._menu_locator).is_displayed() and - 'is-active' in toggle.get_attribute('class')) + return ( + self.find_element(*self._menu_locator).is_displayed() and + 'is-active' in toggle.get_attribute('class') + ) def open_navigation_menu(self, locator): firefox_menu = self.find_element(*locator) @@ -71,19 +73,19 @@ class BasePage(Page): def open_firefox_desktop_page(self): self.open_navigation_menu(self._firefox_menu_locator) self.find_element(*self._firefox_desktop_page_locator).click() - from firefox.home import FirefoxHomePage + from .firefox.home import FirefoxHomePage return FirefoxHomePage(self.selenium, self.page.base_url).wait_for_page_to_load() def open_developer_edition_page(self): self.open_navigation_menu(self._developers_menu_locator) self.find_element(*self._developer_edition_page_locator).click() - from firefox.developer import DeveloperPage + from .firefox.developer import DeveloperPage return DeveloperPage(self.selenium, self.page.base_url).wait_for_page_to_load() def open_about_page(self): self.open_navigation_menu(self._about_menu_locator) self.find_element(*self._about_page_locator).click() - from about import AboutPage + from .about import AboutPage return AboutPage(self.selenium, self.page.base_url).wait_for_page_to_load() class Footer(Region): diff --git a/tests/pages/contact.py b/tests/pages/contact.py index d0df392de7..bc5de7cbb5 100644 --- a/tests/pages/contact.py +++ b/tests/pages/contact.py @@ -62,8 +62,7 @@ class SpacesPage(ContactPage): @property def spaces(self): - return [self.Space(self, root=el) for el in - self.find_elements(*self._desktop_nav_locator)] + return [self.Space(self, root=el) for el in self.find_elements(*self._desktop_nav_locator)] class Space(Region): @@ -92,8 +91,7 @@ class CommunitiesPage(ContactPage): @property def regions(self): - return [self.Region(self, root=el) for el in - self.find_elements(*self._desktop_nav_locator)] + return [self.Region(self, root=el) for el in self.find_elements(*self._desktop_nav_locator)] class Region(Region): @@ -105,8 +103,7 @@ class CommunitiesPage(ContactPage): @property def communities(self): - return [self.Community(self.page, root=el) for el in - self.find_elements(*self._communities_locator)] + return [self.Community(self.page, root=el) for el in self.find_elements(*self._communities_locator)] @property def is_selected(self): diff --git a/tests/pages/firefox/base.py b/tests/pages/firefox/base.py index b823fd292c..c7b498fc6b 100644 --- a/tests/pages/firefox/base.py +++ b/tests/pages/firefox/base.py @@ -7,7 +7,7 @@ from pypom import Region from pages.base import BasePage -class ScrollElementIntoView(object): +class ScrollElementIntoView: def scroll_element_into_view(self, strategy, locator): # scroll elements so they are not beneath the header diff --git a/tests/pages/leadership.py b/tests/pages/leadership.py index 41f6941900..59bfa9dff6 100644 --- a/tests/pages/leadership.py +++ b/tests/pages/leadership.py @@ -35,8 +35,9 @@ class LeadershipPage(BasePage): return modal def is_biography_displayed(self, value): - return self.is_element_displayed(*(By.CSS_SELECTOR, - '#modal .vcard.has-bio[data-id="{0}"]'.format(value))) + return self.is_element_displayed( + *(By.CSS_SELECTOR, '#modal .vcard.has-bio[data-id="{0}"]'.format(value)) + ) def get_next_leader(self, index, leaders): current_index = leaders.index(index) diff --git a/tests/redirects/base.py b/tests/redirects/base.py index 2dfeb46442..0a3227b945 100644 --- a/tests/redirects/base.py +++ b/tests/redirects/base.py @@ -1,5 +1,5 @@ import re -from urlparse import urlparse, parse_qs +from urllib.parse import urlparse, parse_qs from braceexpand import braceexpand import requests @@ -20,7 +20,7 @@ def get_abs_url(url, base_url): def url_test(url, location=None, status_code=requests.codes.moved_permanently, req_headers=None, req_kwargs=None, resp_headers=None, query=None, follow_redirects=False, final_status_code=requests.codes.ok): - """ + r""" Function for producing a config dict for the redirect test. You can use simple bash style brace expansion in the `url` and `location` @@ -125,10 +125,13 @@ def assert_valid_url(url, location=None, status_code=requests.codes.moved_perman else: assert resp.status_code == status_code if location and not follow_redirects: + if not query and '?' in location: + query = parse_qs(urlparse(location).query) + location = location.split('?')[0] if query: # all query values must be lists for k, v in query.items(): - if isinstance(v, basestring): + if isinstance(v, str): query[k] = [v] # parse the QS from resp location header and compare to query arg # since order doesn't matter. @@ -137,15 +140,15 @@ def assert_valid_url(url, location=None, status_code=requests.codes.moved_perman # strip off query for further comparison resp_location = resp_location.split('?')[0] - try: + if hasattr(location, 'match'): # location is a compiled regular expression pattern assert location.match(resp_location) is not None - except AttributeError: + else: assert location == resp_location if resp_headers and not follow_redirects: for name, value in resp_headers.items(): - print name, value + print(name, value) assert name in resp.headers assert resp.headers[name].lower() == value.lower() diff --git a/tests/redirects/map_301.py b/tests/redirects/map_301.py index 117f6fc41a..fadf62e690 100644 --- a/tests/redirects/map_301.py +++ b/tests/redirects/map_301.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from .base import flatten, url_test diff --git a/tests/redirects/map_globalconf.py b/tests/redirects/map_globalconf.py index 302f370451..c8e1a285b1 100644 --- a/tests/redirects/map_globalconf.py +++ b/tests/redirects/map_globalconf.py @@ -1,8 +1,4 @@ # -*- coding: utf-8 -*- -# flake8: noqa - -from __future__ import absolute_import -import re import requests @@ -395,7 +391,8 @@ URLS = flatten(( url_test('/press/mozilla-2004-08-18.html', 'https://blog.mozilla.org/press/2004/08/mozilla-affiliate-in-japan-kicks-off/'), url_test('/press/mozilla-2004-09-14-01.html', - 'https://blog.mozilla.org/press/2004/09/mozilla-foundation-announces-first-payments-of-security-bug-bounty-program-further-strengthens-browser-security/'), + 'https://blog.mozilla.org/press/2004/09/mozilla-foundation-announces-first-payments-of-security' + '-bug-bounty-program-further-strengthens-browser-security/'), url_test('/press/mozilla-2004-09-14-02.html', 'https://blog.mozilla.org/press/2004/09/firefox-preview-release-and-thunderbird-0-8-released/'), url_test('/press/mozilla-2004-09-20.html', @@ -429,7 +426,8 @@ URLS = flatten(( url_test('/press/mozilla-2005-07-28.html', 'https://blog.mozilla.org/press/2005/07/mozilla-headlines-two-key-open-source-development-conferences-in-august/'), url_test('/press/mozilla-2005-08-03.html', - 'https://blog.mozilla.org/press/2005/08/mozilla-foundation-forms-new-organization-to-further-the-creation-of-free-open-source-internet-software-including-the-award-winning-mozilla-firefox-browser/'), + 'https://blog.mozilla.org/press/2005/08/mozilla-foundation-forms-new-organization-to-further-the-creation-of-free' + '-open-source-internet-software-including-the-award-winning-mozilla-firefox-browser/'), url_test('/press/mozilla-2005-10-03.html', 'https://blog.mozilla.org/press/2005/10/mozilla-launches-beta-of-comprehensive-online-developer-center/'), url_test('/press/mozilla-2005-10-19.html', @@ -451,7 +449,8 @@ URLS = flatten(( url_test('/press/mozilla-2006-01-25.html', 'https://blog.mozilla.org/press/2006/01/indie-film-all-stars-foin-firefox-flicks-crew/'), url_test('/press/mozilla-2006-02-03.html', - 'https://blog.mozilla.org/press/2006/02/mozilla-releases-preview-of-application-framework-for-development-of-cross-platform-internet-client-applications/'), + 'https://blog.mozilla.org/press/2006/02/mozilla-releases-preview-of-application-framework-for' + '-development-of-cross-platform-internet-client-applications/'), url_test('/press/mozilla-2006-03-02.html', 'https://blog.mozilla.org/press/2006/03/mozilla-announces-winners-of-extend-firefox-competition/'), url_test('/press/mozilla-2006-04-12.html', @@ -463,7 +462,8 @@ URLS = flatten(( url_test('/press/mozilla-2006-06-14.html', 'https://blog.mozilla.org/press/2006/06/mozilla-feeds-soccer-fans-passion-with-new-firefox-add-on/'), url_test('/press/mozilla-2006-10-11.html', - 'https://blog.mozilla.org/press/2006/10/qualcomm-launches-project-in-collaboration-with-mozilla-foundation-to-develop-open-source-version-of-eudora-email-program/'), + 'https://blog.mozilla.org/press/2006/10/qualcomm-launches-project-in-collaboration-with-mozilla' + '-foundation-to-develop-open-source-version-of-eudora-email-program/'), url_test('/press/mozilla-2006-10-24-02.html', 'https://blog.mozilla.org/press/2006/10/firefox-moving-the-internet-forward/'), url_test('/press/mozilla-2006-10-24.html', @@ -479,7 +479,8 @@ URLS = flatten(( url_test('/press/mozilla-2007-03-27.html', 'https://blog.mozilla.org/press/2007/03/mozilla-launches-new-firefox-add-ons-web-site/'), url_test('/press/mozilla-2007-03-28.html', - 'https://blog.mozilla.org/press/2007/03/mozilla-and-ebay-working-together-to-make-the-auction-experience-easier-for-firefox-users-in-france-germany-and-the-uk/'), + 'https://blog.mozilla.org/press/2007/03/mozilla-and-ebay-working-together-to-make-the-auction-experience' + '-easier-for-firefox-users-in-france-germany-and-the-uk/'), url_test('/press/mozilla-2007-04-19.html', 'https://blog.mozilla.org/press/2007/04/mozilla-thunderbird-2-soars-to-new-heights/'), url_test('/press/mozilla-2007-05-16.html', @@ -525,7 +526,8 @@ URLS = flatten(( url_test('/press/mozilla1.0.html', 'https://blog.mozilla.org/press/2002/06/mozilla-org-launches-mozilla-1-0/'), url_test('/press/open-source-security.html', - 'https://blog.mozilla.org/press/2000/01/open-source-development-of-security-products-possible-worldwide-enhancing-security-and-privacy-for-e-commerce-and-communication/'), + 'https://blog.mozilla.org/press/2000/01/open-source-development-of-security-products-possible' + '-worldwide-enhancing-security-and-privacy-for-e-commerce-and-communication/'), # Bug 608370, 957664 url_test('/press/kit{.html,s/}', 'https://blog.mozilla.org/press/kits/'), @@ -1082,7 +1084,8 @@ URLS = flatten(( url_test('/firefox/{49.0,49.0.1,50.0a1,51.0a2}/firstrun/learnmore', '/firefox/features/?utm_campaign=redirect&utm_medium=firefox-browser&utm_source=firefox-browser&utm_content=learnmore-tab'), url_test('/firefox/windows-10/welcome', - 'https://support.mozilla.org/kb/how-change-your-default-browser-windows-10?utm_campaign=redirect&utm_medium=firefox-browser&utm_source=firefox-browser&utm_content=windows10-welcome-tab'), + 'https://support.mozilla.org/kb/how-change-your-default-browser-windows-10?utm_campaign=redirect' + '&utm_medium=firefox-browser&utm_source=firefox-browser&utm_content=windows10-welcome-tab'), # bug 1319207 url_test('/de/privacy/firefox-focus/', '/de/privacy/firefox-klar/'), @@ -1131,7 +1134,7 @@ URLS = flatten(( # Bug 1324504 url_test('/contribute/studentambassadors/{,join/,thanks/}', 'https://campus.mozilla.community/'), - # Bug 1340600 + # Bug 1340600 url_test('/css-grid', '/developer/css-grid/', query={ 'utm_source': 'redirect', 'utm_medium': 'collateral', diff --git a/tests/redirects/map_htaccess.py b/tests/redirects/map_htaccess.py index d3834de176..fff7b9656f 100644 --- a/tests/redirects/map_htaccess.py +++ b/tests/redirects/map_htaccess.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from .base import flatten, url_test diff --git a/tests/redirects/map_locales.py b/tests/redirects/map_locales.py index 8a2ed196a1..126b485a04 100644 --- a/tests/redirects/map_locales.py +++ b/tests/redirects/map_locales.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import - from .base import flatten, url_test # supported locales diff --git a/tests/redirects/test_redirects.py b/tests/redirects/test_redirects.py index 549fe78ede..074b5c58a1 100644 --- a/tests/redirects/test_redirects.py +++ b/tests/redirects/test_redirects.py @@ -1,5 +1,4 @@ """Test redirects from the global.conf file.""" -from __future__ import absolute_import from operator import itemgetter import pytest diff --git a/tests/redirects/test_urls.py b/tests/redirects/test_urls.py index e4e714d418..a1e33afa2e 100644 --- a/tests/redirects/test_urls.py +++ b/tests/redirects/test_urls.py @@ -1,6 +1,4 @@ """Test redirects from the global.conf file.""" -from __future__ import absolute_import - import pytest import requests @@ -30,7 +28,7 @@ def test_404_url(base_url): @pytest.mark.nondestructive @pytest.mark.django_db def test_x_robots_tag(base_url): - assert_valid_url(base_url, resp_headers={'x-robots-tag': 'noodp'}) + assert_valid_url('/en-US/', status_code=200, resp_headers={'x-robots-tag': 'noodp'}, base_url=base_url) @pytest.mark.headless