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/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/kobo/settings/base.py b/kobo/settings/base.py index 0e632527bc..21ace455c0 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 = [ @@ -640,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 = { @@ -937,6 +935,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': 'Warning: experimental schema generation. Use at your own risk.', + '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..25187bdee1 100644 --- a/kobo/urls.py +++ b/kobo/urls.py @@ -8,15 +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 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 = [ + path('api/schema/', SpectacularAPIView.as_view(), name='schema'), + 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 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'