Skip to content

Commit c6b1829

Browse files
committed
TG-162 Add API documentation
1 parent e581e39 commit c6b1829

File tree

10 files changed

+131
-3
lines changed

10 files changed

+131
-3
lines changed

bootstrap/collector.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def collect(
3939
):
4040
"""Collect options and run the setup."""
4141
project_slug = clean_project_slug(project_name, project_slug)
42+
project_description = clean_project_description()
4243
service_slug = clean_service_slug(service_slug)
4344
project_dirname = clean_project_dirname(project_dirname, project_slug, service_slug)
4445
project_url_dev = validate_or_prompt_url(
@@ -72,6 +73,7 @@ def collect(
7273
"output_dir": output_dir,
7374
"project_name": project_name,
7475
"project_slug": project_slug,
76+
"project_description": project_description,
7577
"project_dirname": project_dirname,
7678
"service_dir": service_dir,
7779
"service_slug": service_slug,
@@ -119,6 +121,11 @@ def clean_project_slug(project_name, project_slug):
119121
)
120122

121123

124+
def clean_project_description(project_slug):
125+
"""Return the project description."""
126+
return click.prompt("Project description", default="")
127+
128+
122129
def clean_service_slug(service_slug):
123130
"""Return the service slug."""
124131
return slugify(

bootstrap/runner.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ def run(
2626
output_dir,
2727
project_name,
2828
project_slug,
29+
project_description,
2930
project_dirname,
3031
service_dir,
3132
service_slug,
@@ -51,6 +52,7 @@ def run(
5152
output_dir,
5253
project_name,
5354
project_slug,
55+
project_description,
5456
project_dirname,
5557
service_slug,
5658
internal_service_port,
@@ -91,6 +93,7 @@ def init_service(
9193
output_dir,
9294
project_name,
9395
project_slug,
96+
project_description,
9497
project_dirname,
9598
service_slug,
9699
internal_service_port,
@@ -110,6 +113,7 @@ def init_service(
110113
"project_dirname": project_dirname,
111114
"project_name": project_name,
112115
"project_slug": project_slug,
116+
"project_description": project_description,
113117
"project_url_dev": project_url_dev,
114118
"project_url_prod": project_url_prod,
115119
"project_url_stage": project_url_stage,

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
@click.option("--output-dir", default=OUTPUT_DIR)
2121
@click.option("--project-name", prompt=True)
2222
@click.option("--project-slug", callback=slugify_option)
23+
@click.option("--project-description")
2324
@click.option("--project-dirname")
2425
@click.option("--service-slug", callback=slugify_option)
2526
@click.option("--internal-service-port", default=8000, type=int)

{{cookiecutter.project_dirname}}/.env_template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ [email protected]
1515
DJANGO_SUPERUSER_PASSWORD={{ cookiecutter.project_slug }}
1616
DJANGO_SUPERUSER_USERNAME=${USER}
1717
EMAIL_URL=console:///
18+
DJANGO_ENABLE_API_DOCS=True
1819
PYTHONBREAKPOINT=ipdb.set_trace

{{cookiecutter.project_dirname}}/docker-compose.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ services:
1919
- DJANGO_CONFIGURATION=${DJANGO_CONFIGURATION:-Remote}
2020
- DJANGO_DEBUG
2121
- DJANGO_DEFAULT_FROM_EMAIL
22+
- DJANGO_ENABLE_API_DOCS
2223
- DJANGO_SECRET_KEY
2324
- DJANGO_SERVER_EMAIL
2425
- DJANGO_SESSION_COOKIE_DOMAIN
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
# pip-compile --output-file common.txt common.in
22
-r base.in
3+
djangorestframework-camel-case~=1.3.0
4+
djangorestframework~=3.13.0
5+
drf-spectacular~=0.22.0

{{cookiecutter.project_dirname}}/terraform/main.tf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,10 @@ resource "kubernetes_secret_v1" "env" {
8989

9090
data = { for k, v in merge(
9191
{
92-
DJANGO_SECRET_KEY = random_password.django_secret_key.result
93-
EMAIL_URL = var.email_url
94-
SENTRY_DSN = var.sentry_dsn
92+
DJANGO_SECRET_KEY = random_password.django_secret_key.result
93+
DJANGO_ENABLE_API_DOCS = var.environment_slug == "prod" ? "False" : "True"
94+
EMAIL_URL = var.email_url
95+
SENTRY_DSN = var.sentry_dsn
9596
},
9697
local.use_s3 ? {
9798
AWS_ACCESS_KEY_ID = var.s3_bucket_access_id

{{cookiecutter.project_dirname}}/{{cookiecutter.django_settings_dirname}}/settings.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class ProjectDefault(Configuration):
4747
"django.contrib.sessions",
4848
"django.contrib.messages",
4949
"django.contrib.staticfiles",
50+
"rest_framework",
51+
"drf_spectacular",
5052
]
5153

5254
MIDDLEWARE = [
@@ -181,6 +183,51 @@ class ProjectDefault(Configuration):
181183

182184
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
183185

186+
# API Documentation
187+
188+
ENABLE_API_DOCS = values.BooleanValue(False)
189+
190+
# Django REST Framework
191+
# https://www.django-rest-framework.org/api-guide/settings/
192+
193+
REST_FRAMEWORK: dict = {
194+
"DEFAULT_AUTHENTICATION_CLASSES": [
195+
"rest_framework.authentication.SessionAuthentication",
196+
],
197+
"DEFAULT_FILTER_BACKENDS": [
198+
"django_filters.rest_framework.DjangoFilterBackend"
199+
],
200+
"DEFAULT_PARSER_CLASSES": [
201+
"djangorestframework_camel_case.parser.CamelCaseMultiPartParser",
202+
"djangorestframework_camel_case.parser.CamelCaseJSONParser",
203+
],
204+
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
205+
"DEFAULT_RENDERER_CLASSES": [
206+
"djangorestframework_camel_case.render.CamelCaseJSONRenderer",
207+
],
208+
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
209+
}
210+
211+
# drf-spectacular
212+
# https://drf-spectacular.readthedocs.io/en/latest/
213+
214+
SPECTACULAR_SETTINGS = {
215+
"TITLE": "{{ cookiecutter.project_name }} - API documentation",
216+
"DESCRIPTION": "{{ cookiecutter.project_description }}",
217+
"VERSION": "1.0.0",
218+
"CAMELIZE_NAMES": True,
219+
"SWAGGER_UI_SETTINGS": {
220+
"deepLinking": True,
221+
"displayRequestDuration": True,
222+
"persistAuthorization": True,
223+
"syntaxHighlight.activate": True,
224+
},
225+
"POSTPROCESSING_HOOKS": [
226+
"drf_spectacular.hooks.postprocess_schema_enums",
227+
"drf_spectacular.contrib.djangorestframework_camel_case.camelize_serializer_fields", # noqa
228+
],
229+
}
230+
184231

185232
class Local(ProjectDefault):
186233
"""The local settings."""
@@ -251,6 +298,18 @@ class Local(ProjectDefault):
251298
"verbose_names": True,
252299
}
253300

301+
# Django REST Framework
302+
# https://www.django-rest-framework.org/api-guide/settings/
303+
304+
REST_FRAMEWORK = {
305+
**ProjectDefault.REST_FRAMEWORK,
306+
"DEFAULT_RENDERER_CLASSES": [
307+
*ProjectDefault.REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"],
308+
"djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer",
309+
],
310+
}
311+
312+
254313

255314
class Testing(ProjectDefault):
256315
"""The testing settings."""

{{cookiecutter.project_dirname}}/{{cookiecutter.django_settings_dirname}}/urls.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,21 @@
2323

2424
urlpatterns = [
2525
path("admin/", admin.site.urls),
26+
path(
27+
"api/",
28+
include(
29+
(
30+
[
31+
*(
32+
[path("", include("{{ cookiecutter.project_slug }}.urls_apibrowser"))]
33+
if settings.ENABLE_API_DOCS
34+
else []
35+
),
36+
],
37+
"api",
38+
)
39+
),
40+
),
2641
]
2742

2843
if settings.DEBUG: # pragma: no cover
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""API browser URL configuration."""
2+
3+
from django.contrib.admin.views.decorators import staff_member_required
4+
from django.urls import include, path
5+
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
6+
7+
urlpatterns = [
8+
path(
9+
"browser/",
10+
include(
11+
(
12+
[
13+
path(
14+
"schema/",
15+
staff_member_required(
16+
SpectacularAPIView.as_view(
17+
urlconf="{{ cookiecutter.project_slug }}.urls"
18+
)
19+
),
20+
name="schema",
21+
),
22+
path(
23+
"docs/",
24+
staff_member_required(
25+
SpectacularSwaggerView.as_view(
26+
url_name="api:browser:schema"
27+
)
28+
),
29+
name="docs",
30+
),
31+
],
32+
"browser",
33+
),
34+
),
35+
)
36+
]

0 commit comments

Comments
 (0)