chore(): add swagger UI to document our apis (#21521)
* chore(): generate api versions from method * chore(): add swagger UI to document our apis
This commit is contained in:
Родитель
e9e3dfc64b
Коммит
3ab0252f3a
|
@ -46,6 +46,10 @@ server {
|
|||
alias /srv/site-static/admin/;
|
||||
}
|
||||
|
||||
location /static/drf-yasg/ {
|
||||
alias /srv/site-static/drf-yasg/;
|
||||
}
|
||||
|
||||
location ~ ^/api/ {
|
||||
try_files $uri @olympia;
|
||||
}
|
||||
|
|
|
@ -861,3 +861,12 @@ grpcio==1.59.3 \
|
|||
--hash=sha256:f2eb8f0c7c0c62f7a547ad7a91ba627a5aa32a5ae8d930783f7ee61680d7eb8d \
|
||||
--hash=sha256:fb111aa99d3180c361a35b5ae1e2c63750220c584a1344229abc139d5c891881 \
|
||||
--hash=sha256:fcfa56f8d031ffda902c258c84c4b88707f3a4be4827b4e3ab8ec7c24676320d
|
||||
drf-yasg==1.21.7 \
|
||||
--hash=sha256:4c3b93068b3dfca6969ab111155e4dd6f7b2d680b98778de8fd460b7837bdb0d \
|
||||
--hash=sha256:f85642072c35e684356475781b7ecf5d218fff2c6185c040664dd49f0a4be181
|
||||
uritemplate==4.1.1 \
|
||||
--hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \
|
||||
--hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e
|
||||
inflection==0.5.1 \
|
||||
--hash=sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417 \
|
||||
--hash=sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2
|
||||
|
|
|
@ -150,3 +150,8 @@ SITEMAP_DEBUG_AVAILABLE = True
|
|||
# Will show the widget but no captcha, verification will always pass.
|
||||
RECAPTCHA_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
|
||||
RECAPTCHA_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe'
|
||||
|
||||
SWAGGER_SETTINGS = {
|
||||
'USE_SESSION_AUTH': False,
|
||||
'DEEP_LINKING': True,
|
||||
}
|
||||
|
|
|
@ -360,10 +360,21 @@ class AbuseReport(ModelBase):
|
|||
blank=True,
|
||||
related_name='abuse_reported',
|
||||
on_delete=models.SET_NULL,
|
||||
help_text='The user who submitted the report, if authenticated.',
|
||||
)
|
||||
# name and/or email can be provided instead for unauthenticated reporters
|
||||
reporter_email = models.CharField(max_length=255, default=None, null=True)
|
||||
reporter_name = models.CharField(max_length=255, default=None, null=True)
|
||||
reporter_email = models.CharField(
|
||||
max_length=255,
|
||||
default=None,
|
||||
null=True,
|
||||
help_text='The provided email of the reporter, if not authenticated.',
|
||||
)
|
||||
reporter_name = models.CharField(
|
||||
max_length=255,
|
||||
default=None,
|
||||
null=True,
|
||||
help_text='The provided name of the reporter, if not authenticated.',
|
||||
)
|
||||
country_code = models.CharField(max_length=2, default=None, null=True)
|
||||
# An abuse report can be for an addon, a user or a rating.
|
||||
# - If user is set then guid and rating should be null.
|
||||
|
@ -379,7 +390,9 @@ class AbuseReport(ModelBase):
|
|||
collection = models.ForeignKey(
|
||||
Collection, null=True, related_name='abuse_reports', on_delete=models.SET_NULL
|
||||
)
|
||||
message = models.TextField(blank=True)
|
||||
message = models.TextField(
|
||||
blank=True, help_text='The body/content of the abuse report.'
|
||||
)
|
||||
|
||||
state = models.PositiveSmallIntegerField(
|
||||
default=STATES.UNTRIAGED, choices=STATES.choices
|
||||
|
@ -388,10 +401,26 @@ class AbuseReport(ModelBase):
|
|||
# Extra optional fields for more information, giving some context that is
|
||||
# meant to be extracted automatically by the client (i.e. Firefox) and
|
||||
# submitted via the API.
|
||||
client_id = models.CharField(default=None, max_length=64, blank=True, null=True)
|
||||
addon_name = models.CharField(default=None, max_length=255, blank=True, null=True)
|
||||
client_id = models.CharField(
|
||||
default=None,
|
||||
max_length=64,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="The client's hashed telemetry ID.",
|
||||
)
|
||||
addon_name = models.CharField(
|
||||
default=None,
|
||||
max_length=255,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='The add-on name in the locale used by the client.',
|
||||
)
|
||||
addon_summary = models.CharField(
|
||||
default=None, max_length=255, blank=True, null=True
|
||||
default=None,
|
||||
max_length=255,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text='The add-on summary in the locale used by the client.',
|
||||
)
|
||||
addon_version = models.CharField(
|
||||
default=None, max_length=255, blank=True, null=True
|
||||
|
@ -429,10 +458,26 @@ class AbuseReport(ModelBase):
|
|||
null=True,
|
||||
)
|
||||
addon_install_method = models.PositiveSmallIntegerField(
|
||||
default=None, choices=ADDON_INSTALL_METHODS.choices, blank=True, null=True
|
||||
default=None,
|
||||
choices=ADDON_INSTALL_METHODS.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=(
|
||||
'For addon_install_method and addon_install_source specifically,'
|
||||
'if an unsupported value is sent, it will be silently changed to other'
|
||||
'instead of raising a 400 error.'
|
||||
),
|
||||
)
|
||||
addon_install_source = models.PositiveSmallIntegerField(
|
||||
default=None, choices=ADDON_INSTALL_SOURCES.choices, blank=True, null=True
|
||||
default=None,
|
||||
choices=ADDON_INSTALL_SOURCES.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=(
|
||||
'For addon_install_method and addon_install_source specifically,'
|
||||
'if an unsupported value is sent, it will be silently changed to other'
|
||||
'instead of raising a 400 error.'
|
||||
),
|
||||
)
|
||||
addon_install_source_url = models.CharField(
|
||||
# See addon_install_origin above as for why it's not an URLField.
|
||||
|
@ -445,7 +490,13 @@ class AbuseReport(ModelBase):
|
|||
default=None, choices=REPORT_ENTRY_POINTS.choices, blank=True, null=True
|
||||
)
|
||||
location = models.PositiveSmallIntegerField(
|
||||
default=None, choices=LOCATION.choices, blank=True, null=True
|
||||
default=None,
|
||||
choices=LOCATION.choices,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=(
|
||||
'Where the content being reported is located - on AMO or inside the add-on.'
|
||||
),
|
||||
)
|
||||
cinder_job = models.ForeignKey(CinderJob, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
|
|
|
@ -44,7 +44,12 @@ class BaseAbuseReportSerializer(AMOModelSerializer):
|
|||
error_messages=error_messages,
|
||||
)
|
||||
lang = serializers.CharField(
|
||||
required=False, source='application_locale', max_length=255
|
||||
required=False,
|
||||
source='application_locale',
|
||||
max_length=255,
|
||||
help_text=(
|
||||
'The language code of the locale used by the client for the application.'
|
||||
),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -98,7 +103,9 @@ class BaseAbuseReportSerializer(AMOModelSerializer):
|
|||
|
||||
|
||||
class AddonAbuseReportSerializer(BaseAbuseReportSerializer):
|
||||
addon = serializers.SerializerMethodField()
|
||||
addon = serializers.SerializerMethodField(
|
||||
help_text='The add-on reported for abuse.'
|
||||
)
|
||||
app = ReverseChoiceField(
|
||||
choices=list((v.id, k) for k, v in amo.APPS.items()),
|
||||
required=False,
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.shortcuts import redirect
|
|||
from django.utils.cache import patch_cache_control
|
||||
from django.utils.translation import gettext
|
||||
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from elasticsearch_dsl import Q, Search, query
|
||||
from rest_framework import exceptions, serializers, status
|
||||
from rest_framework.decorators import action
|
||||
|
@ -392,6 +393,19 @@ class AddonViewSet(
|
|||
self.action = 'create'
|
||||
return self.create(request, *args, **kwargs)
|
||||
|
||||
@swagger_auto_schema(
|
||||
operation_description="""
|
||||
This endpoint allows a submission of an upload to create a new add-on
|
||||
and setting other AMO metadata.
|
||||
|
||||
To create an add-on with a listed version from an upload
|
||||
(an upload that has channel == listed) certain metadata must be defined
|
||||
- version license
|
||||
- add-on name
|
||||
- add-on summary
|
||||
- add-on categories for each app the version is compatible with.
|
||||
"""
|
||||
)
|
||||
def create(self, request, *args, **kwargs):
|
||||
response = super().create(request, *args, **kwargs)
|
||||
webext_version_stats(request, 'addons.submission')
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.deprecation import MiddlewareMixin
|
|||
|
||||
class APIRequestMiddleware(MiddlewareMixin):
|
||||
def identify_request(self, request):
|
||||
request.is_api = re.match(settings.DRF_API_REGEX, request.path_info)
|
||||
request.is_api = re.match(settings.DRF_API_NOT_SWAGGER_REGEX, request.path_info)
|
||||
|
||||
def process_request(self, request):
|
||||
self.identify_request(request)
|
||||
|
|
|
@ -36,6 +36,21 @@ class TestAPIRequestMiddleware(TestCase):
|
|||
APIRequestMiddleware(lambda: None).process_response(request, response)
|
||||
assert response['Vary'] == 'Foo, Bar'
|
||||
|
||||
def test_disabled_on_swagger(self):
|
||||
"""Test that we don't tag the request as API on swagger pages."""
|
||||
|
||||
urls = [
|
||||
'/api/v3/swagger',
|
||||
'/api/v3/swagger.json',
|
||||
'/api/v4/swagger/',
|
||||
'/api/v4/swagger.yaml',
|
||||
]
|
||||
|
||||
for url in urls:
|
||||
request = self.request_factory.get(url)
|
||||
APIRequestMiddleware(lambda: None).process_request(request)
|
||||
assert not request.is_api
|
||||
|
||||
def test_disabled_for_the_rest(self):
|
||||
"""Test that we don't tag the request as API on "regular" pages."""
|
||||
request = self.request_factory.get('/overtherainbow')
|
||||
|
|
|
@ -1,11 +1,56 @@
|
|||
from django.conf import settings
|
||||
from django.urls import include, re_path
|
||||
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework import permissions
|
||||
|
||||
from olympia.accounts.urls import accounts_v3, accounts_v4, auth_urls
|
||||
from olympia.addons.api_urls import addons_v3, addons_v4, addons_v5
|
||||
from olympia.amo.urls import api_patterns as amo_api_patterns
|
||||
from olympia.ratings.api_urls import ratings_v3, ratings_v4
|
||||
|
||||
|
||||
def get_versioned_api_routes(version, url_patterns):
|
||||
route_pattern = r'^{}/'.format(version)
|
||||
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title='AMO API',
|
||||
default_version=version,
|
||||
description='The official API for addons.mozilla.org.',
|
||||
),
|
||||
public=True,
|
||||
permission_classes=(permissions.AllowAny,),
|
||||
)
|
||||
|
||||
routes = url_patterns
|
||||
|
||||
# For now, this feature is only enabled in dev mode
|
||||
if settings.DEBUG:
|
||||
routes.extend(
|
||||
[
|
||||
re_path(
|
||||
r'^swagger(?P<format>\.json|\.yaml)$',
|
||||
schema_view.without_ui(cache_timeout=0),
|
||||
name='schema-json',
|
||||
),
|
||||
re_path(
|
||||
r'^swagger/$',
|
||||
schema_view.with_ui('swagger', cache_timeout=0),
|
||||
name='schema-swagger-ui',
|
||||
),
|
||||
re_path(
|
||||
r'^redoc/$',
|
||||
schema_view.with_ui('redoc', cache_timeout=0),
|
||||
name='schema-redoc',
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
return (re_path(route_pattern, include((routes, version))),)
|
||||
|
||||
|
||||
v3_api_urls = [
|
||||
re_path(r'^abuse/', include('olympia.abuse.api_urls')),
|
||||
re_path(r'^accounts/', include(accounts_v3)),
|
||||
|
@ -51,7 +96,7 @@ v5_api_urls = [
|
|||
|
||||
urlpatterns = [
|
||||
re_path(r'^auth/', include((auth_urls, 'auth'))),
|
||||
re_path(r'^v3/', include((v3_api_urls, 'v3'))),
|
||||
re_path(r'^v4/', include((v4_api_urls, 'v4'))),
|
||||
re_path(r'^v5/', include((v5_api_urls, 'v5'))),
|
||||
*get_versioned_api_routes('v3', v3_api_urls),
|
||||
*get_versioned_api_routes('v4', v4_api_urls),
|
||||
*get_versioned_api_routes('v5', v5_api_urls),
|
||||
]
|
||||
|
|
|
@ -104,6 +104,8 @@ DELETION_EMAIL = 'amo-notifications+deletion@mozilla.com'
|
|||
DRF_API_VERSIONS = ['auth', 'v3', 'v4', 'v5']
|
||||
DRF_API_REGEX = r'^/?api/(?:auth|v3|v4|v5)/'
|
||||
|
||||
DRF_API_NOT_SWAGGER_REGEX = rf'{DRF_API_REGEX}(?!swagger|redoc).*$'
|
||||
|
||||
# Add Access-Control-Allow-Origin: * header for the new API with
|
||||
# django-cors-headers.
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
|
@ -329,6 +331,9 @@ JINJA_EXCLUDE_TEMPLATE_PATHS = (
|
|||
r'^registration\/',
|
||||
# Django's sitemap_index.xml template uses some syntax that jinja doesn't support
|
||||
r'sitemap_index.xml',
|
||||
# Swagger URLs are for the API docs, use some syntax that jinja doesn't support
|
||||
r'drf-yasg/swagger-ui.html',
|
||||
r'drf-yasg/redoc.html',
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
|
@ -528,6 +533,7 @@ INSTALLED_APPS = (
|
|||
'django_jinja',
|
||||
'rangefilter',
|
||||
'django_recaptcha',
|
||||
'drf_yasg',
|
||||
# Django contrib apps
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
|
|
Загрузка…
Ссылка в новой задаче