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:
Kevin Meinhardt 2023-12-11 18:26:58 +01:00 коммит произвёл GitHub
Родитель e9e3dfc64b
Коммит 3ab0252f3a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 171 добавлений и 15 удалений

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

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