Implement appversion compatibility filtering in language tools API

- Allow filtering by type to only show language packs
- Expose a new current_compatible_version if appversion is passed.
- This property will fetch the latest publicly available version
  compatible with the appversion passed.
- Replace caching with a filesystem-based implementation to work
  around cache eviction issues.
This commit is contained in:
Mathieu Pillard 2018-03-05 14:34:43 +01:00
Родитель bf98023a8a
Коммит c125bf9354
11 изменённых файлов: 447 добавлений и 104 удалений

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

@ -1,5 +1,5 @@
from django import http, test
from django.core.cache import cache
from django.core.cache import caches
from django.utils import translation
import caching
@ -86,7 +86,8 @@ def default_prefixer(settings):
@pytest.yield_fixture(autouse=True)
def test_pre_setup(request, tmpdir, settings):
cache.clear()
caches['default'].clear()
caches['filesystem'].clear()
# Override django-cache-machine caching.base.TIMEOUT because it's
# computed too early, before settings_test.py is imported.
caching.base.TIMEOUT = settings.CACHE_COUNT_TIMEOUT

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

@ -457,10 +457,16 @@ on AMO.
it's up to the clients to re-order results as needed before displaying
the add-ons to the end-users.
In addition, the results can be cached for up to 24 hours, based on the
full URL used in the request.
:query string app: Mandatory. Filter by :ref:`add-on application <addon-detail-application>` availability.
:query string appversion: Filter by application version compatibility. Pass the full version as a string, e.g. ``46.0``. Only valid when both the ``app`` and ``type`` parameters are also present, and only makes sense for Language Packs, since Dictionaries are always compatible with every application version.
:query string lang: Activate translations in the specific language for that query. (See :ref:`translated fields <api-overview-translations>`)
:query string type: Filter by :ref:`add-on type <addon-detail-type>`. The default is to return both Language Packs or Dictionaries.
:>json array results: An array of language tools.
:>json int results[].id: The add-on id on AMO.
:>json object results[].current_compatible_version: Object holding the latest publicly available :ref:`version <version-detail-object>` of the add-on compatible with the ``appversion`` parameter used. Only present when ``appversion`` is passed and valid. For performance reasons, only the following version properties are returned on the object: ``id``, ``files``, ``reviewed``, and ``version``.
:>json string results[].default_locale: The add-on default locale for translations.
:>json string|object|null results[].name: The add-on name (See :ref:`translated fields <api-overview-translations>`).
:>json string results[].guid: The add-on `extension identifier <https://developer.mozilla.org/en-US/Add-ons/Install_Manifests#id>`_.

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

@ -19,6 +19,8 @@ INSTALLED_APPS += (
'olympia.landfill',
)
FILESYSTEM_CACHE_ROOT = os.path.join(TMP_PATH, 'cache')
# Using locmem deadlocks in certain scenarios. This should all be fixed,
# hopefully, in Django1.7. At that point, we may try again, and remove this to
# not require memcache installation for newcomers.
@ -35,6 +37,10 @@ CACHES = {
'default': {
'BACKEND': 'caching.backends.memcached.MemcachedCache',
'LOCATION': os.environ.get('MEMCACHE_LOCATION', 'localhost:11211'),
},
'filesystem': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': FILESYSTEM_CACHE_ROOT,
}
}

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

@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
from settings import * # noqa
from django.utils.functional import lazy
# Make sure the app needed to test translations is present.
INSTALLED_APPS += TEST_INSTALLED_APPS
@ -39,6 +37,10 @@ CACHES = {
'default': {
'BACKEND': 'caching.backends.locmem.LocMemCache',
'LOCATION': 'olympia',
},
'filesystem': { # In real settings it's a filesystem cache, not here.
'BACKEND': 'caching.backends.locmem.LocMemCache',
'LOCATION': 'olympia-filesystem',
}
}

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

@ -16,6 +16,7 @@ from olympia.constants.applications import APPS_ALL
from olympia.constants.base import ADDON_TYPE_CHOICES_API
from olympia.constants.categories import CATEGORIES_BY_ID
from olympia.files.models import File
from olympia.search.filters import AddonAppVersionQueryParam
from olympia.users.models import UserProfile
from olympia.versions.models import ApplicationsVersions, License, Version
@ -144,11 +145,18 @@ class CompactLicenseSerializer(LicenseSerializer):
fields = ('id', 'name', 'url')
class SimpleVersionSerializer(serializers.ModelSerializer):
compatibility = serializers.SerializerMethodField()
is_strict_compatibility_enabled = serializers.SerializerMethodField()
edit_url = serializers.SerializerMethodField()
class MinimalVersionSerializer(serializers.ModelSerializer):
files = FileSerializer(source='all_files', many=True)
class Meta:
model = Version
fields = ('id', 'files', 'reviewed', 'version')
class SimpleVersionSerializer(MinimalVersionSerializer):
compatibility = serializers.SerializerMethodField()
edit_url = serializers.SerializerMethodField()
is_strict_compatibility_enabled = serializers.SerializerMethodField()
license = CompactLicenseSerializer()
release_notes = TranslationSerializerField(source='releasenotes')
url = serializers.SerializerMethodField()
@ -166,13 +174,6 @@ class SimpleVersionSerializer(serializers.ModelSerializer):
instance.license.version_instance = instance
return super(SimpleVersionSerializer, self).to_representation(instance)
def get_url(self, obj):
return absolutify(obj.get_url_path())
def get_edit_url(self, obj):
return absolutify(obj.addon.get_dev_url(
'versions.edit', args=[obj.pk], prefix_only=True))
def get_compatibility(self, obj):
return {
app.short: {
@ -182,9 +183,16 @@ class SimpleVersionSerializer(serializers.ModelSerializer):
} for app, compat in obj.compatible_apps.items()
}
def get_edit_url(self, obj):
return absolutify(obj.addon.get_dev_url(
'versions.edit', args=[obj.pk], prefix_only=True))
def get_is_strict_compatibility_enabled(self, obj):
return any(file_.strict_compatibility for file_ in obj.all_files)
def get_url(self, obj):
return absolutify(obj.get_url_path())
class SimpleESVersionSerializer(SimpleVersionSerializer):
class Meta:
@ -362,7 +370,10 @@ class AddonSerializer(serializers.ModelSerializer):
return getattr(obj, 'tag_list', [])
def get_url(self, obj):
return absolutify(obj.get_url_path())
# Use get_detail_url(), get_url_path() does an extra check on
# current_version that is annoying in subclasses which don't want to
# load that version.
return absolutify(obj.get_detail_url())
def get_edit_url(self, obj):
return absolutify(obj.get_dev_url())
@ -632,13 +643,40 @@ class StaticCategorySerializer(serializers.Serializer):
class LanguageToolsSerializer(AddonSerializer):
target_locale = serializers.CharField()
locale_disambiguation = serializers.CharField()
current_compatible_version = serializers.SerializerMethodField()
class Meta:
model = Addon
fields = ('id', 'default_locale', 'guid',
fields = ('id', 'current_compatible_version', 'default_locale', 'guid',
'locale_disambiguation', 'name', 'slug', 'target_locale',
'type', 'url', )
def get_current_compatible_version(self, obj):
compatible_versions = getattr(obj, 'compatible_versions', None)
if compatible_versions is not None:
data = MinimalVersionSerializer(
compatible_versions, many=True).data
try:
# 99% of the cases there will only be one result, since most
# language packs are automatically uploaded for a given app
# version. If there are more, pick the most recent one.
return data[0]
except IndexError:
# This should not happen, because the queryset in the view is
# supposed to filter results to only return add-ons that do
# have at least one compatible version, but let's not fail
# too loudly if the unthinkable happens...
pass
return None
def to_representation(self, obj):
data = super(LanguageToolsSerializer, self).to_representation(obj)
request = self.context['request']
if (AddonAppVersionQueryParam.query_param not in request.GET and
'current_compatible_version' in data):
data.pop('current_compatible_version')
return data
class ReplacementAddonSerializer(serializers.ModelSerializer):
replacement = serializers.SerializerMethodField()

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

@ -933,11 +933,43 @@ class TestLanguageToolsSerializerOutput(TestCase):
assert result['target_locale'] == self.addon.target_locale
assert result['type'] == 'language'
assert result['url'] == absolutify(self.addon.get_url_path())
assert 'current_compatible_version' not in result
def test_basic_dict(self):
self.addon = addon_factory(type=amo.ADDON_DICT)
result = self.serialize()
assert result['type'] == 'dictionary'
assert 'current_compatible_version' not in result
def test_current_compatible_version(self):
self.addon = addon_factory(type=amo.ADDON_LPAPP)
# compatible_versions is set by the view through prefetch, it
# looks like a list.
self.addon.compatible_versions = [self.addon.current_version]
self.addon.compatible_versions[0].update(created=self.days_ago(1))
# Create a new current version, just to prove that
# current_compatible_version does not use that.
version_factory(addon=self.addon)
self.addon.reload
assert (
self.addon.compatible_versions[0] !=
self.addon.current_version)
self.request = APIRequestFactory().get('/?app=firefox&appversion=57.0')
result = self.serialize()
assert 'current_compatible_version' in result
assert result['current_compatible_version'] is not None
assert set(result['current_compatible_version'].keys()) == set(
['id', 'files', 'reviewed', 'version'])
self.addon.compatible_versions = None
result = self.serialize()
assert 'current_compatible_version' in result
assert result['current_compatible_version'] is None
self.addon.compatible_versions = []
result = self.serialize()
assert 'current_compatible_version' in result
assert result['current_compatible_version'] is None
class TestESAddonAutoCompleteSerializer(ESTestCase):

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

@ -3254,19 +3254,26 @@ class TestLanguageToolsView(TestCase):
super(TestLanguageToolsView, self).setUp()
self.url = reverse('addon-language-tools')
def test_wrong_app(self):
def test_wrong_app_or_no_app(self):
response = self.client.get(self.url)
assert response.status_code == 400
assert response.data == {
'detail': u'Invalid or missing app parameter.'}
response = self.client.get(self.url, {'app': 'foo'})
assert response.status_code == 400
assert response.data == {
'detail': u'Invalid or missing app parameter.'}
def test_basic(self):
dictionary = addon_factory(type=amo.ADDON_DICT, target_locale='fr')
dictionary_spelling_variant = addon_factory(
type=amo.ADDON_DICT, target_locale='fr',
locale_disambiguation='For spelling reform')
language_pack = addon_factory(type=amo.ADDON_LPAPP, target_locale='es')
language_pack = addon_factory(
type=amo.ADDON_LPAPP, target_locale='es',
file_kw={'strict_compatibility': True},
version_kw={'min_app_version': '57.0', 'max_app_version': '57.*'})
# These add-ons below should be ignored: they are either not public or
# of the wrong type, not supporting the app we care about, or their
@ -3297,6 +3304,136 @@ class TestLanguageToolsView(TestCase):
assert 'locale_disambiguation' in data['results'][0]
assert 'target_locale' in data['results'][0]
# We were not filtering by appversion, so we do not get the
# current_compatible_version property.
assert 'current_compatible_version' not in data['results'][0]
def test_with_appversion_but_no_type(self):
response = self.client.get(
self.url, {'app': 'firefox', 'appversion': '57.0'})
assert response.status_code == 400
assert response.data == {
'detail': 'Invalid or missing type parameter while appversion '
'parameter is set.'}
def test_with_invalid_appversion(self):
response = self.client.get(
self.url,
{'app': 'firefox', 'type': 'language', 'appversion': u'foôbar'})
assert response.status_code == 400
assert response.data == {'detail': 'Invalid appversion parameter.'}
def test_with_appversion_filtering(self):
# Add compatible add-ons. We're going to request language packs
# compatible with 58.0.
compatible_pack1 = addon_factory(
name='Spanish Language Pack',
type=amo.ADDON_LPAPP, target_locale='es',
file_kw={'strict_compatibility': True},
version_kw={'min_app_version': '57.0', 'max_app_version': '57.*'})
compatible_pack1.current_version.update(created=self.days_ago(2))
compatible_version1 = version_factory(
addon=compatible_pack1, file_kw={'strict_compatibility': True},
min_app_version='58.0', max_app_version='58.*')
compatible_version1.update(created=self.days_ago(1))
compatible_pack2 = addon_factory(
name='French Language Pack',
type=amo.ADDON_LPAPP, target_locale='fr',
file_kw={'strict_compatibility': True},
version_kw={'min_app_version': '58.0', 'max_app_version': '58.*'})
compatible_version2 = compatible_pack2.current_version
compatible_version2.update(created=self.days_ago(1))
version_factory(
addon=compatible_pack2, file_kw={'strict_compatibility': True},
min_app_version='59.0', max_app_version='59.*')
# Add a more recent version for both add-ons, that would be compatible
# with 58.0, but is not public/listed so should not be returned.
version_factory(
addon=compatible_pack1, file_kw={'strict_compatibility': True},
min_app_version='58.0', max_app_version='58.*',
channel=amo.RELEASE_CHANNEL_UNLISTED)
version_factory(
addon=compatible_pack2,
file_kw={'strict_compatibility': True,
'status': amo.STATUS_DISABLED},
min_app_version='58.0', max_app_version='58.*')
# Add a few of incompatible add-ons.
incompatible_pack1 = addon_factory(
name='German Language Pack (incompatible with 58.0)',
type=amo.ADDON_LPAPP, target_locale='fr',
file_kw={'strict_compatibility': True},
version_kw={'min_app_version': '56.0', 'max_app_version': '56.*'})
version_factory(
addon=incompatible_pack1, file_kw={'strict_compatibility': True},
min_app_version='59.0', max_app_version='59.*')
addon_factory(
name='Italian Language Pack (incompatible with 58.0)',
type=amo.ADDON_LPAPP, target_locale='it',
file_kw={'strict_compatibility': True},
version_kw={'min_app_version': '59.0', 'max_app_version': '59.*'})
addon_factory(
name='Thunderbird Polish Language Pack',
type=amo.ADDON_LPAPP, target_locale='pl',
file_kw={'strict_compatibility': True},
version_kw={
'application': amo.THUNDERBIRD.id,
'min_app_version': '58.0', 'max_app_version': '58.*'})
# Even add a pack with a compatible version... not public. And another
# one with a compatible version... not listed.
incompatible_pack2 = addon_factory(
name='Japanese Language Pack (public, but 58.0 version is not)',
type=amo.ADDON_LPAPP, target_locale='ja',
file_kw={'strict_compatibility': True},
version_kw={'min_app_version': '57.0', 'max_app_version': '57.*'})
version_factory(
addon=incompatible_pack2,
min_app_version='58.0', max_app_version='58.*',
file_kw={'status': amo.STATUS_AWAITING_REVIEW,
'strict_compatibility': True})
incompatible_pack3 = addon_factory(
name='Nederlands Language Pack (58.0 version is unlisted)',
type=amo.ADDON_LPAPP, target_locale='ja',
file_kw={'strict_compatibility': True},
version_kw={'min_app_version': '57.0', 'max_app_version': '57.*'})
version_factory(
addon=incompatible_pack3,
min_app_version='58.0', max_app_version='58.*',
channel=amo.RELEASE_CHANNEL_UNLISTED,
file_kw={'strict_compatibility': True})
# Test it.
with self.assertNumQueries(5):
# 5 queries, regardless of how many add-ons are returned:
# - 1 for the add-ons
# - 1 for the add-ons translations (name)
# - 1 for the compatible versions (through prefetch_related)
# - 1 for the applications versions for those versions
# (we don't need it, but we're using the default Version
# transformer to get the files... this could be improved.)
# - 1 for the files for those versions
response = self.client.get(
self.url,
{'app': 'firefox', 'appversion': '58.0', 'type': 'language',
'lang': 'en-US'})
assert response.status_code == 200, response.content
results = response.data['results']
assert len(results) == 2
# Ordering is not guaranteed by this API, but do check that the
# current_compatible_version returned makes sense.
assert results[0]['current_compatible_version']
assert results[1]['current_compatible_version']
expected_versions = set((
(compatible_pack1.pk, compatible_version1.pk),
(compatible_pack2.pk, compatible_version2.pk),
))
returned_versions = set((
(results[0]['id'], results[0]['current_compatible_version']['id']),
(results[1]['id'], results[1]['current_compatible_version']['id']),
))
assert expected_versions == returned_versions
def test_memoize(self):
addon_factory(type=amo.ADDON_DICT, target_locale='fr')
@ -3308,26 +3445,31 @@ class TestLanguageToolsView(TestCase):
type=amo.ADDON_LPAPP, target_locale='de',
version_kw={'application': amo.THUNDERBIRD.id})
response = self.client.get(self.url, {'app': 'firefox'})
with self.assertNumQueries(2):
response = self.client.get(
self.url, {'app': 'firefox', 'lang': 'fr'})
assert response.status_code == 200
assert len(json.loads(response.content)['results']) == 3
# Same again, should be cached; no queries.
with self.assertNumQueries(0):
assert self.client.get(self.url, {'app': 'firefox'}).content == (
assert self.client.get(
self.url, {'app': 'firefox', 'lang': 'fr'}).content == (
response.content
)
# But different app is different
with self.assertNumQueries(12):
with self.assertNumQueries(2):
assert (
self.client.get(self.url, {'app': 'thunderbird'}).content !=
self.client.get(
self.url, {'app': 'thunderbird', 'lang': 'fr'}).content !=
response.content
)
# Same again, should be cached; no queries.
with self.assertNumQueries(0):
self.client.get(self.url, {'app': 'thunderbird'})
# But throw in a lang request and not cached:
with self.assertNumQueries(10):
self.client.get(self.url, {'app': 'firefox', 'lang': 'fr'})
self.client.get(self.url, {'app': 'thunderbird', 'lang': 'fr'})
# Change the lang, we should get queries again.
with self.assertNumQueries(2):
self.client.get(self.url, {'app': 'firefox', 'lang': 'de'})
class TestReplacementAddonView(TestCase):

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

@ -1,9 +1,11 @@
from django import http
from django.db.models import Prefetch
from django.db.transaction import non_atomic_requests
from django.shortcuts import get_list_or_404, get_object_or_404, redirect
from django.utils.cache import patch_cache_control
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext
from django.views.decorators.cache import cache_control
from django.views.decorators.cache import cache_control, cache_page
from django.views.decorators.vary import vary_on_headers
import caching.base as caching
@ -25,7 +27,6 @@ from olympia import amo
from olympia.abuse.models import send_abuse_report
from olympia.access import acl
from olympia.amo import messages
from olympia.amo.cache_nuggets import memoize
from olympia.amo.forms import AbuseForm
from olympia.amo.models import manual_order
from olympia.amo.urlresolvers import get_outgoing_url, get_url_prefix, reverse
@ -39,9 +40,9 @@ from olympia.constants.categories import CATEGORIES_BY_ID
from olympia.ratings.forms import RatingForm
from olympia.ratings.models import GroupedRating, Rating
from olympia.search.filters import (
AddonAppQueryParam, AddonCategoryQueryParam, AddonGuidQueryParam,
AddonTypeQueryParam, ReviewedContentFilter, SearchParameterFilter,
SearchQueryFilter, SortingFilter)
AddonAppQueryParam, AddonAppVersionQueryParam, AddonCategoryQueryParam,
AddonGuidQueryParam, AddonTypeQueryParam, ReviewedContentFilter,
SearchParameterFilter, SearchQueryFilter, SortingFilter)
from olympia.translations.query import order_by_translation
from olympia.versions.models import Version
@ -812,33 +813,127 @@ class LanguageToolsView(ListAPIView):
return non_atomic_requests(
super(LanguageToolsView, cls).as_view(**initkwargs))
def get_application_id(self):
if not hasattr(self, 'application_id'):
def get_query_params(self):
"""
Parse query parameters that this API supports:
- app (mandatory)
- type (optional)
- appversion (optional, makes type mandatory)
Can raise ParseError() in case a mandatory parameter is missing or a
parameter is invalid.
Returns a tuple with application, addon_types tuple (or None), and
appversions dict (or None) ready to be consumed by the get_queryset_*()
methods.
"""
# app parameter is mandatory when calling this API.
try:
self.application_id = AddonAppQueryParam(
self.request).get_value()
application = AddonAppQueryParam(self.request).get_value()
except ValueError:
raise exceptions.ParseError('Invalid app parameter.')
return self.application_id
raise exceptions.ParseError('Invalid or missing app parameter.')
# appversion parameter is optional.
if AddonAppVersionQueryParam.query_param in self.request.GET:
try:
value = AddonAppVersionQueryParam(self.request).get_values()
appversions = {
'min': value[1],
'max': value[2]
}
except ValueError:
raise exceptions.ParseError('Invalid appversion parameter.')
else:
appversions = None
# type is optional, unless appversion is set. That's because the way
# dicts and language packs have their compatibility info set in the
# database differs, so to make things simpler for us we force clients
# to filter by type if they want appversion filtering.
if AddonTypeQueryParam.query_param in self.request.GET or appversions:
try:
addon_types = (AddonTypeQueryParam(self.request).get_value(),)
except ValueError:
raise exceptions.ParseError(
'Invalid or missing type parameter while appversion '
'parameter is set.')
else:
addon_types = (amo.ADDON_LPAPP, amo.ADDON_DICT)
return application, addon_types, appversions
def get_queryset(self):
types = (amo.ADDON_DICT, amo.ADDON_LPAPP)
return Addon.objects.public().filter(
appsupport__app=self.get_application_id(), type__in=types,
target_locale__isnull=False).exclude(target_locale='')
application, addon_types, appversions = self.get_query_params()
if addon_types == (amo.ADDON_LPAPP,) and appversions:
return self.get_language_packs_queryset_with_appversions(
application, appversions)
else:
# appversions filtering only makes sense for language packs only,
# so it's ignored here.
return self.get_queryset_base(application, addon_types)
@memoize('API:language-tools', time=(60 * 60 * 24))
def get_data(self, app_id, lang):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return serializer.data
def get_queryset_base(self, application, addon_types):
return (
Addon.objects.public()
.no_cache()
.filter(appsupport__app=application, type__in=addon_types,
target_locale__isnull=False)
.exclude(target_locale='')
# Deactivate default transforms which fetch a ton of stuff we
# don't need here like authors, previews or current version.
# It would be nice to avoid translations entirely, because the
# translations transformer is going to fetch a lot of translations
# we don't need, but some language packs or dictionaries have
# custom names, so we can't use a generic one for them...
.only_translations()
# Since we're fetching everything with no pagination, might as well
# not order it.
.order_by()
)
def get_language_packs_queryset_with_appversions(
self, application, appversions):
"""
Return queryset to use specifically when requesting language packs
compatible with a given app + versions.
application is an application id, and appversions is a dict with min
and max keys pointing to application versions expressed as ints.
"""
# Version queryset we'll prefetch once for all results. We need to
# find the ones compatible with the app+appversion requested, and we
# can avoid loading translations by removing transforms and then
# re-applying the default one that takes care of the files and compat
# info.
versions_qs = Version.objects.no_cache().filter(
apps__application=application,
apps__min__version_int__lte=appversions['min'],
apps__max__version_int__gte=appversions['max'],
channel=amo.RELEASE_CHANNEL_LISTED,
files__status=amo.STATUS_PUBLIC,
).no_transforms().transform(Version.transformer)
qs = self.get_queryset_base(application, (amo.ADDON_LPAPP,))
return (
qs.prefetch_related(Prefetch('versions',
to_attr='compatible_versions',
queryset=versions_qs))
.filter(versions__apps__application=application,
versions__apps__min__version_int__lte=appversions['min'],
versions__apps__max__version_int__gte=appversions['max'],
versions__channel=amo.RELEASE_CHANNEL_LISTED,
versions__files__status=amo.STATUS_PUBLIC)
)
@method_decorator(cache_page(60 * 60 * 24, cache='filesystem'))
def dispatch(self, *args, **kwargs):
return super(LanguageToolsView, self).dispatch(*args, **kwargs)
def list(self, request, *args, **kwargs):
# Ignore pagination (return everything) but do wrap the data in a
# 'results' property to mimic what the default implementation of list()
# does in DRF.
return Response({'results': self.get_data(
self.get_application_id(), self.request.GET.get('lang'))})
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return Response({'results': serializer.data})
class ReplacementAddonView(ListAPIView):

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

@ -64,6 +64,24 @@ MOZLOG_NAME = SYSLOG_TAG
SYSLOG_TAG2 = "http_app_addons_dev_timer"
SYSLOG_CSP = "http_app_addons_dev_csp"
NETAPP_STORAGE_ROOT = env('NETAPP_STORAGE_ROOT')
NETAPP_STORAGE = NETAPP_STORAGE_ROOT + '/shared_storage'
GUARDED_ADDONS_PATH = NETAPP_STORAGE_ROOT + '/guarded-addons'
MEDIA_ROOT = NETAPP_STORAGE + '/uploads'
TMP_PATH = os.path.join(NETAPP_STORAGE, 'tmp')
PACKAGER_PATH = os.path.join(TMP_PATH, 'packager')
ADDONS_PATH = NETAPP_STORAGE_ROOT + '/files'
# Must be forced in settings because name => path can't be dyncamically
# computed: reviewer_attachmentS VS reviewer_attachment.
# TODO: rename folder on file system.
# (One can also just rename the setting, but this will not be consistent
# with the naming scheme.)
REVIEWER_ATTACHMENTS_PATH = MEDIA_ROOT + '/reviewer_attachment'
FILESYSTEM_CACHE_ROOT = NETAPP_STORAGE_ROOT + '/cache'
DATABASES = {}
DATABASES['default'] = env.db('DATABASES_DEFAULT_URL')
DATABASES['default']['ENGINE'] = 'django.db.backends.mysql'
@ -85,7 +103,12 @@ SLAVE_DATABASES = ['slave']
CACHE_MIDDLEWARE_KEY_PREFIX = CACHE_PREFIX
CACHES = {}
CACHES = {
'filesystem': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': FILESYSTEM_CACHE_ROOT,
}
}
CACHES['default'] = env.cache('CACHES_DEFAULT')
CACHES['default']['TIMEOUT'] = 500
CACHES['default']['BACKEND'] = 'caching.backends.memcached.MemcachedCache'
@ -103,22 +126,6 @@ CELERY_WORKER_DISABLE_RATE_LIMITS = True
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND')
NETAPP_STORAGE_ROOT = env('NETAPP_STORAGE_ROOT')
NETAPP_STORAGE = NETAPP_STORAGE_ROOT + '/shared_storage'
GUARDED_ADDONS_PATH = NETAPP_STORAGE_ROOT + '/guarded-addons'
MEDIA_ROOT = NETAPP_STORAGE + '/uploads'
TMP_PATH = os.path.join(NETAPP_STORAGE, 'tmp')
PACKAGER_PATH = os.path.join(TMP_PATH, 'packager')
ADDONS_PATH = NETAPP_STORAGE_ROOT + '/files'
# Must be forced in settings because name => path can't be dyncamically
# computed: reviewer_attachmentS VS reviewer_attachment.
# TODO: rename folder on file system.
# (One can also just rename the setting, but this will not be consistent
# with the naming scheme.)
REVIEWER_ATTACHMENTS_PATH = MEDIA_ROOT + '/reviewer_attachment'
LOGGING['loggers'].update({
'amqp': {'level': logging.WARNING},
'raven': {'level': logging.WARNING},

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

@ -52,6 +52,25 @@ SYSLOG_TAG = "http_app_addons"
SYSLOG_TAG2 = "http_app_addons_timer"
SYSLOG_CSP = "http_app_addons_csp"
NETAPP_STORAGE_ROOT = env('NETAPP_STORAGE_ROOT')
NETAPP_STORAGE = NETAPP_STORAGE_ROOT + '/shared_storage'
GUARDED_ADDONS_PATH = NETAPP_STORAGE_ROOT + '/guarded-addons'
MEDIA_ROOT = NETAPP_STORAGE + '/uploads'
TMP_PATH = os.path.join(NETAPP_STORAGE, 'tmp')
PACKAGER_PATH = os.path.join(TMP_PATH, 'packager')
ADDONS_PATH = NETAPP_STORAGE_ROOT + '/files'
# Must be forced in settings because name => path can't be dyncamically
# computed: reviewer_attachmentS VS reviewer_attachment.
# TODO: rename folder on file system.
# (One can also just rename the setting, but this will not be consistent
# with the naming scheme.)
REVIEWER_ATTACHMENTS_PATH = MEDIA_ROOT + '/reviewer_attachment'
FILESYSTEM_CACHE_ROOT = NETAPP_STORAGE_ROOT + '/cache'
DATABASES = {}
DATABASES['default'] = env.db('DATABASES_DEFAULT_URL')
DATABASES['default']['ENGINE'] = 'django.db.backends.mysql'
@ -73,7 +92,12 @@ SLAVE_DATABASES = ['slave']
CACHE_MIDDLEWARE_KEY_PREFIX = CACHE_PREFIX
CACHES = {}
CACHES = {
'filesystem': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': FILESYSTEM_CACHE_ROOT,
}
}
CACHES['default'] = env.cache('CACHES_DEFAULT')
CACHES['default']['TIMEOUT'] = 500
CACHES['default']['BACKEND'] = 'caching.backends.memcached.MemcachedCache'
@ -90,23 +114,6 @@ CELERY_WORKER_DISABLE_RATE_LIMITS = True
CELERY_BROKER_CONNECTION_TIMEOUT = 0.5
CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND')
NETAPP_STORAGE_ROOT = env('NETAPP_STORAGE_ROOT')
NETAPP_STORAGE = NETAPP_STORAGE_ROOT + '/shared_storage'
GUARDED_ADDONS_PATH = NETAPP_STORAGE_ROOT + '/guarded-addons'
MEDIA_ROOT = NETAPP_STORAGE + '/uploads'
TMP_PATH = os.path.join(NETAPP_STORAGE, 'tmp')
PACKAGER_PATH = os.path.join(TMP_PATH, 'packager')
ADDONS_PATH = NETAPP_STORAGE_ROOT + '/files'
# Must be forced in settings because name => path can't be dyncamically
# computed: reviewer_attachmentS VS reviewer_attachment.
# TODO: rename folder on file system.
# (One can also just rename the setting, but this will not be consistent
# with the naming scheme.)
REVIEWER_ATTACHMENTS_PATH = MEDIA_ROOT + '/reviewer_attachment'
LOG_LEVEL = logging.DEBUG
LOGGING['loggers'].update({

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

@ -61,6 +61,25 @@ SYSLOG_TAG = "http_app_addons_stage"
SYSLOG_TAG2 = "http_app_addons_stage_timer"
SYSLOG_CSP = "http_app_addons_stage_csp"
NETAPP_STORAGE_ROOT = env('NETAPP_STORAGE_ROOT')
NETAPP_STORAGE = NETAPP_STORAGE_ROOT + '/shared_storage'
GUARDED_ADDONS_PATH = NETAPP_STORAGE_ROOT + '/guarded-addons'
MEDIA_ROOT = NETAPP_STORAGE + '/uploads'
TMP_PATH = os.path.join(NETAPP_STORAGE, 'tmp')
PACKAGER_PATH = os.path.join(TMP_PATH, 'packager')
ADDONS_PATH = NETAPP_STORAGE_ROOT + '/files'
# Must be forced in settings because name => path can't be dyncamically
# computed: reviewer_attachmentS VS reviewer_attachment.
# TODO: rename folder on file system.
# (One can also just rename the setting, but this will not be consistent
# with the naming scheme.)
REVIEWER_ATTACHMENTS_PATH = MEDIA_ROOT + '/reviewer_attachment'
FILESYSTEM_CACHE_ROOT = NETAPP_STORAGE_ROOT + '/cache'
DATABASES = {}
DATABASES['default'] = env.db('DATABASES_DEFAULT_URL')
DATABASES['default']['ENGINE'] = 'django.db.backends.mysql'
@ -82,7 +101,12 @@ SLAVE_DATABASES = ['slave']
CACHE_MIDDLEWARE_KEY_PREFIX = CACHE_PREFIX
CACHES = {}
CACHES = {
'filesystem': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': FILESYSTEM_CACHE_ROOT,
}
}
CACHES['default'] = env.cache('CACHES_DEFAULT')
CACHES['default']['TIMEOUT'] = 500
CACHES['default']['BACKEND'] = 'caching.backends.memcached.MemcachedCache'
@ -100,23 +124,6 @@ CELERY_WORKER_DISABLE_RATE_LIMITS = True
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND')
NETAPP_STORAGE_ROOT = env('NETAPP_STORAGE_ROOT')
NETAPP_STORAGE = NETAPP_STORAGE_ROOT + '/shared_storage'
GUARDED_ADDONS_PATH = NETAPP_STORAGE_ROOT + '/guarded-addons'
MEDIA_ROOT = NETAPP_STORAGE + '/uploads'
TMP_PATH = os.path.join(NETAPP_STORAGE, 'tmp')
PACKAGER_PATH = os.path.join(TMP_PATH, 'packager')
ADDONS_PATH = NETAPP_STORAGE_ROOT + '/files'
# Must be forced in settings because name => path can't be dyncamically
# computed: reviewer_attachmentS VS reviewer_attachment.
# TODO: rename folder on file system.
# (One can also just rename the setting, but this will not be consistent
# with the naming scheme.)
REVIEWER_ATTACHMENTS_PATH = MEDIA_ROOT + '/reviewer_attachment'
LOGGING['loggers'].update({
'z.task': {'level': logging.DEBUG},
'z.redis': {'level': logging.DEBUG},