From 99f58f8ab75357b5cbaf5c21a2a9b70e8ef46d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Tue, 12 Nov 2024 11:52:17 +0200 Subject: [PATCH 1/5] wip(api): add drf-spectacular --- dependencies/pip/dev_requirements.txt | 15 +++++++++++++-- dependencies/pip/requirements.in | 2 ++ dependencies/pip/requirements.txt | 15 +++++++++++++-- kobo/settings/base.py | 10 ++++++++++ kobo/urls.py | 8 ++++++++ 5 files changed, 46 insertions(+), 4 deletions(-) diff --git a/dependencies/pip/dev_requirements.txt b/dependencies/pip/dev_requirements.txt index 1df8bbb9f1..5e4e485c5d 100644 --- a/dependencies/pip/dev_requirements.txt +++ b/dependencies/pip/dev_requirements.txt @@ -169,6 +169,7 @@ django==4.2.15 # django-taggit # django-timezone-field # djangorestframework + # drf-spectacular # jsonfield # model-bakery django-allauth==0.61.1 @@ -238,6 +239,7 @@ djangorestframework==3.15.1 # -r dependencies/pip/requirements.in # djangorestframework-csv # drf-extensions + # drf-spectacular djangorestframework-csv==3.0.2 # via -r dependencies/pip/requirements.in djangorestframework-jsonp==1.0.2 @@ -252,6 +254,8 @@ docutils==0.20.1 # via statistics drf-extensions==0.7.1 # via -r dependencies/pip/requirements.in +drf-spectacular==0.27.2 + # via -r dependencies/pip/requirements.in et-xmlfile==1.1.0 # via openpyxl exceptiongroup==1.2.0 @@ -344,6 +348,8 @@ idna==3.6 # via # requests # yarl +inflection==0.5.1 + # via drf-spectacular iniconfig==2.0.0 # via pytest invoke==2.2.0 @@ -365,6 +371,7 @@ jsonfield==3.1.0 jsonschema==4.21.1 # via # -r dependencies/pip/requirements.in + # drf-spectacular # formpack jsonschema-specifications==2023.12.1 # via jsonschema @@ -542,7 +549,9 @@ pyxform==2.2.0 # -r dependencies/pip/requirements.in # formpack pyyaml==6.0.1 - # via responses + # via + # drf-spectacular + # responses redis==5.0.3 # via # celery @@ -654,7 +663,9 @@ tzdata==2024.1 ua-parser==0.18.0 # via -r dependencies/pip/requirements.in uritemplate==4.1.1 - # via google-api-python-client + # via + # drf-spectacular + # google-api-python-client urllib3==1.26.18 # via # botocore diff --git a/dependencies/pip/requirements.in b/dependencies/pip/requirements.in index 968f5b6ba8..47b69d72f9 100644 --- a/dependencies/pip/requirements.in +++ b/dependencies/pip/requirements.in @@ -111,3 +111,5 @@ modilabs-python-utils djangorestframework-csv djangorestframework-jsonp pandas + +drf-spectacular # OpenAPI v3 schema generator diff --git a/dependencies/pip/requirements.txt b/dependencies/pip/requirements.txt index 76175a5dc5..1ab008aa6e 100644 --- a/dependencies/pip/requirements.txt +++ b/dependencies/pip/requirements.txt @@ -138,6 +138,7 @@ django==4.2.15 # django-taggit # django-timezone-field # djangorestframework + # drf-spectacular # jsonfield django-allauth==0.61.1 # via -r dependencies/pip/requirements.in @@ -206,6 +207,7 @@ djangorestframework==3.15.1 # -r dependencies/pip/requirements.in # djangorestframework-csv # drf-extensions + # drf-spectacular djangorestframework-csv==3.0.2 # via -r dependencies/pip/requirements.in djangorestframework-jsonp==1.0.2 @@ -218,6 +220,8 @@ docutils==0.20.1 # via statistics drf-extensions==0.7.1 # via -r dependencies/pip/requirements.in +drf-spectacular==0.27.2 + # via -r dependencies/pip/requirements.in et-xmlfile==1.1.0 # via openpyxl flower==2.0.1 @@ -288,6 +292,8 @@ idna==3.6 # via # requests # yarl +inflection==0.5.1 + # via drf-spectacular isodate==0.6.1 # via azure-storage-blob jmespath==1.0.1 @@ -299,6 +305,7 @@ jsonfield==3.1.0 jsonschema==4.21.1 # via # -r dependencies/pip/requirements.in + # drf-spectacular # formpack jsonschema-specifications==2023.12.1 # via jsonschema @@ -417,7 +424,9 @@ pyxform==2.2.0 # -r dependencies/pip/requirements.in # formpack pyyaml==6.0.1 - # via responses + # via + # drf-spectacular + # responses redis==5.0.3 # via # celery @@ -502,7 +511,9 @@ tzdata==2024.1 ua-parser==0.18.0 # via -r dependencies/pip/requirements.in uritemplate==4.1.1 - # via google-api-python-client + # via + # drf-spectacular + # google-api-python-client urllib3==1.26.18 # via # botocore diff --git a/kobo/settings/base.py b/kobo/settings/base.py index 0e632527bc..4761776307 100644 --- a/kobo/settings/base.py +++ b/kobo/settings/base.py @@ -142,6 +142,7 @@ 'guardian', 'kobo.apps.openrosa.libs', 'kobo.apps.project_ownership.ProjectOwnershipAppConfig', + 'drf_spectacular', ) MIDDLEWARE = [ @@ -937,6 +938,15 @@ def __init__(self, *args, **kwargs): 'DEFAULT_VERSIONING_CLASS': 'kpi.versioning.APIAutoVersioning', # Cannot be placed in kpi.exceptions.py because of circular imports 'EXCEPTION_HANDLER': 'kpi.utils.drf_exceptions.custom_exception_handler', + 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', +} + +SPECTACULAR_SETTINGS = { + 'TITLE': 'KoboToolbox API', + 'DESCRIPTION': 'Powerful and intuitive data collection tools to make an impact', + 'VERSION': '0.0.1', + 'SERVE_INCLUDE_SCHEMA': False, + # OTHER SETTINGS } OPENROSA_REST_FRAMEWORK = { diff --git a/kobo/urls.py b/kobo/urls.py index 7c5ba388c8..e3fdd24464 100644 --- a/kobo/urls.py +++ b/kobo/urls.py @@ -8,6 +8,7 @@ from django.views.generic.base import RedirectView from rest_framework import status from rest_framework.exceptions import server_error +from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView from kpi.utils.urls import is_request_for_html @@ -17,6 +18,13 @@ ) urlpatterns = [ + + # YOUR PATTERNS + path('api/schema/', SpectacularAPIView.as_view(), name='schema'), + # Optional UI: + path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), + # https://github.com/stochastic-technologies/django-loginas re_path(r'^admin/', include('loginas.urls')), # Disable admin login form From 406302e5bf863e82131dda12963e6f9c456d7e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 13 Nov 2024 11:30:03 +0200 Subject: [PATCH 2/5] fix(api): don't crash drf-spectacular --- kobo/apps/openrosa/libs/renderers/renderers.py | 2 +- kpi/renderers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kobo/apps/openrosa/libs/renderers/renderers.py b/kobo/apps/openrosa/libs/renderers/renderers.py index 53db176681..7b9cfa6906 100644 --- a/kobo/apps/openrosa/libs/renderers/renderers.py +++ b/kobo/apps/openrosa/libs/renderers/renderers.py @@ -58,7 +58,7 @@ def filter_renderers(self, renderers, format): class MediaFileRenderer(BaseRenderer): media_type = '*/*' - format = None + format = 'TODO' charset = None render_style = 'binary' diff --git a/kpi/renderers.py b/kpi/renderers.py index 49019edd62..bb1f30aba2 100644 --- a/kpi/renderers.py +++ b/kpi/renderers.py @@ -24,7 +24,7 @@ class AssetJsonRenderer(renderers.JSONRenderer): class MediaFileRenderer(renderers.BaseRenderer): media_type = '*/*' - format = None + format = 'TODO' charset = None render_style = 'binary' From b9e6409a95c79ba04bf36cff9a259d469e0a030e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 13 Nov 2024 13:43:44 +0200 Subject: [PATCH 3/5] fix(api): nicier url /api/schema/swagger --- kobo/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kobo/urls.py b/kobo/urls.py index e3fdd24464..112f9b4dc8 100644 --- a/kobo/urls.py +++ b/kobo/urls.py @@ -22,7 +22,7 @@ # YOUR PATTERNS path('api/schema/', SpectacularAPIView.as_view(), name='schema'), # Optional UI: - path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path('api/schema/swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), # https://github.com/stochastic-technologies/django-loginas From be3c033977be474ae90e292819ecc7efb704805a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 13 Nov 2024 14:01:17 +0200 Subject: [PATCH 4/5] style(backend): linter --- kobo/urls.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/kobo/urls.py b/kobo/urls.py index 112f9b4dc8..25187bdee1 100644 --- a/kobo/urls.py +++ b/kobo/urls.py @@ -8,23 +8,29 @@ from django.views.generic.base import RedirectView from rest_framework import status from rest_framework.exceptions import server_error -from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) from kpi.utils.urls import is_request_for_html admin.autodiscover() -admin.site.login = staff_member_required( - admin.site.login, login_url=settings.LOGIN_URL -) +admin.site.login = staff_member_required(admin.site.login, login_url=settings.LOGIN_URL) urlpatterns = [ - - # YOUR PATTERNS path('api/schema/', SpectacularAPIView.as_view(), name='schema'), - # Optional UI: - path('api/schema/swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), - path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), - + path( + 'api/schema/swagger/', + SpectacularSwaggerView.as_view(url_name='schema'), + name='swagger-ui', + ), + path( + 'api/schema/redoc/', + SpectacularRedocView.as_view(url_name='schema'), + name='redoc', + ), # https://github.com/stochastic-technologies/django-loginas re_path(r'^admin/', include('loginas.urls')), # Disable admin login form From eec41c2370ea6ea33cb1607e2c73e66bc8a2991c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kalvis=20Kalni=C5=86=C5=A1?= Date: Wed, 13 Nov 2024 16:41:52 +0200 Subject: [PATCH 5/5] docs(api): disclaimer about schema --- kobo/settings/base.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kobo/settings/base.py b/kobo/settings/base.py index 4761776307..21ace455c0 100644 --- a/kobo/settings/base.py +++ b/kobo/settings/base.py @@ -641,9 +641,6 @@ 'positive_int_minus_one': ['django.forms.fields.IntegerField', { 'min_value': -1 }], - 'positive_int': ['django.forms.fields.IntegerField', { - 'min_value': 0 - }], } CONSTANCE_CONFIG_FIELDSETS = { @@ -943,7 +940,7 @@ def __init__(self, *args, **kwargs): SPECTACULAR_SETTINGS = { 'TITLE': 'KoboToolbox API', - 'DESCRIPTION': 'Powerful and intuitive data collection tools to make an impact', + 'DESCRIPTION': 'Warning: experimental schema generation. Use at your own risk.', 'VERSION': '0.0.1', 'SERVE_INCLUDE_SCHEMA': False, # OTHER SETTINGS