Django +  REST framework + Django Debug Toolbar + JWT + Swagger + docker-compose の環境構築

## 目的

Mac上に開発環境、Dokcerに本番環境を構築し、下記URLにブラウザアクセスが可能な状態にする。

|Page                     | URL |
|---                      |---   |
|Django (Hello, world.)   |http://localhost:8000|
|Django administration    |http://localhost:8000/admin/|
|REST framewrk            |http://localhost:8000/api/|
|Django Debug Toolbar     |-|
|Swagger                  |http://localhost:8000/api/schema/swagger-ui|
|Django REST framework JWT| http://localhost:8000/api-auth/|

## 環境

```
MacOSX: Sonoma
Python: 3.11.1
Django: 4.2.11
Django REST framework: 3.14.0
Django Debug Toolbar: 4.3.0
drf-spectacular(swagger): 0.27.1
Simple JWT: 5.3.1
```

## Django 開発環境構築
### 作業ディレクトリ用意

```
% mkdir Django4.2-DRF && cd Django4.2-DRF
```

### Pythonのセットアップ

```bash
% pyenv versions        # インストールされているバージョンを確認
% pyenv install --list  # インストール可能なバージョンを表示
% pyenv install 3.11.1  # Python3.11.1をインストール
% pyenv local 3.11.1    # このディレクトリ配下のバージョンを固定
```

### ローカルリポジトリの作成
```
% git init
% echo '# Django4.2-DRF' > README.md
% cat << EOF > .gitignore
*.pyc
__pycache__
db.sqlite3
venv/*
EOF
% git add .
% git commit -m 'first commit'
```

↓ この作業後のディレクトリ構成

```
Django4.2-DRF
├── .git
├── .gitignore
└── README.md
```

### Django用作業ディレクトリの作成

```bash
% mkdir django && cd django
% poetry init
```

```
This command will guide you through creating your pyproject.toml config.

Package name [django4.2-drf]:
Version [0.1.0]:
Description []:
Author [mykysyk, n to skip]:
License []:
Compatible Python versions [^3.11]:

Would you like to define your main dependencies interactively? (yes/no) [yes]
You can specify a package in the following forms:
  - A single name (requests): this will search for matches on PyPI
  - A name and a constraint (requests@^2.23.0)
  - A git url (git+https://github.com/python-poetry/poetry.git)
  - A git url with a revision (git+https://github.com/python-poetry/poetry.git#develop)
  - A file path (../my-package/my-package.whl)
  - A directory (../my-package/)
  - A url (https://example.com/packages/my-package-0.1.0.tar.gz)

Package to add or search for (leave blank to skip):

Would you like to define your development dependencies interactively? (yes/no) [yes]
Package to add or search for (leave blank to skip):

Generated file

[tool.poetry]
name = "django4-2-drf"
version = "0.1.0"
description = ""
authors = ["mykysyk"]
readme = "README.md"
packages = [{include = "django"}]

[tool.poetry.dependencies]
python = "^3.11"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"


Do you confirm generation? (yes/no) [yes]
```

↓ この作業後のディレクトリ構成

```
Django4.2-DRF
├── .git
├── .gitignore
├── .python-version
├── README.md
└── django
    └── pyproject.toml
```

## Djangoのインストール

```
Django4.2-DRF
└── django ← 現在の階層
```

https://www.djangoproject.com/download/ より最新LTSのバージョンを調べてインストール

```bash
% poetry add Django==4.2.11
% poetry run python -m django --version
4.2.11
```


### Djangoプロジェクト作成

```bash
% poetry run django-admin startproject config .
```

↓ この作業後のディレクトリ構成

```
Django4.2-DRF
├── .git
├── .gitignore
├── .python-version
├── README.md
└── django
    ├── .venv
    ├── config               # プロジェクトが作成された
    │   ├── __init__.py     # プロジェクトが作成された
    │   ├── asgi.py         # プロジェクトが作成された
    │   ├── settings.py     # プロジェクトが作成された
    │   ├── urls.py         # プロジェクトが作成された
    │   └── wsgi.py         # プロジェクトが作成された
    ├── manage.py            # プロジェクトが作成された
    ├── poetry.lock
    └── pyproject.toml
```


### 管理画面にログインするスーパーユーザーを追加

#### データベース構築

```bash
% poetry run python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK
```

↓ この作業後のディレクトリ構成
```
├── .git
├── .gitignore
├── .python-version
├── README.md
└── django
    ├── config
    │   ├── __init__.py
    │   ├── __pycache__
    │   ├── asgi.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    ├── db.sqlite3  # DBが新規作成される
    ├── manage.py
    ├── poetry.lock
    └── pyproject.toml
```

#### スーパーユーザ作成
```
% poetry run python manage.py createsuperuser
```

#### 動作確認で起動してみる
```bash
% poetry run python manage.py runserver
```

##### 確認 1: http://localhost:8000 にアクセスし「The install worked successfully! Congratulations!」が出ればOK


##### 確認 2: http://localhost:8000/admin/sample_app/memo/ にアクセスできるのを確認
<img width="404" alt="login画面" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24811/4350e539-999d-2a21-bb16-d6876b023f1c.jpeg">

<img width="760" alt="ログイン後" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24811/45adf007-803c-5fcb-5778-9e643c6c3a27.jpeg">


#### ここまでの変更をGitでコミット

```
git add .
git commit -m 'Djangoプロジェクト作成'
```

### sample_app アプリケーション作成

```bash
% poetry run python manage.py startapp sample_app
```

↓ この作業後のディレクトリ構成

```
Django4.2-DRF
├── .git
├── .gitignore
├── .python-version
├── README.md
└── django
    ├── .venv
    ├── config
    │   ├── __init__.py
    │   ├── asgi.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    ├── manage.py
    ├── poetry.lock
    ├── pyproject.toml
    └── sample_app          # アプリケーションが作成された
        ├── __init__.py     # アプリケーションが作成された
        ├── admin.py        # アプリケーションが作成された
        ├── apps.py         # アプリケーションが作成された
        ├── migrations      # アプリケーションが作成された
        ├── models.py       # アプリケーションが作成された
        ├── tests.py        # アプリケーションが作成された
        └── views.py        # アプリケーションが作成された
```


### sample_app のアプリケーション登録

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── settings.py ← これ
```

```bash
% vi config/settings.py
```

既存ファイルとの差分

```diff:django/config/settings.py
% git diff config/settings.py

diff --git a/django/config/settings.py b/django/config/settings.py
index be04b32..33ec9e6 100644
--- a/django/config/settings.py
+++ b/django/config/settings.py
@@ -31,6 +31,7 @@ ALLOWED_HOSTS = []
 # Application definition

 INSTALLED_APPS = [
+    'sample_app',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
```

### ビュー作成

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── sample_app
        └── views.py ← これ
```

```bash
% vi sample_app/views.py
```

```python:django/sample_app/views.py
from django.http import HttpResponse


def index(request):
    return HttpResponse("Hello, world.")
```

### アプリ用URLConfの新規作成

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── sample_app
        └── urls.py ← これ新規作成
```

```bash
% vi sample_app/urls.py
```

```python:django/sample_app/urls.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index, name='index'),
]
```

### ルート用URLConfの編集

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── urls.py ← これ
```

```bash
% vi config/urls.py
```

既存ファイルとの差分

```diff:django/config/urls.py
% git diff config/urls.py
diff --git a/django/config/urls.py b/django/config/urls.py
index 5006338..d212c09 100644
--- a/django/config/urls.py
+++ b/django/config/urls.py
@@ -15,8 +15,9 @@ Including another URLconf
     2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
 """
 from django.contrib import admin
-from django.urls import path
+from django.urls import path, include

 urlpatterns = [
+    path('', include('sample_app.urls')),
     path('admin/', admin.site.urls),
 ]
```

#### 動作確認で起動してみる
```bash
% poetry run python manage.py runserver
```

##### 確認: http://localhost:8000 にアクセスし「Hello, world.」が出ればOK


#### ここまでの変更をGitでコミット

```bash
% git add .
% git commit -m 'Djangoアプリケーション作成'
```

## Django REST framework

Django REST Framework で RESTful API バックエンドを構築

### Django REST framework のインストール

```
Django4.2-DRF
└── django ← 現在の階層
```

```bash
% poetry add djangorestframework django-filter
```

### Djagno REST Framework のアプリケーション登録

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── settings.py ← これ
```

```
% vi config/settings.py
```

既存ファイルとの差分

```diff:django/config/settings.py
% git diff django/config/settings.py

diff --git a/django/config/settings.py b/django/config/settings.py
index 75b5a5e..d23b69d 100644
--- a/django/config/settings.py
+++ b/django/config/settings.py
@@ -31,6 +31,7 @@ ALLOWED_HOSTS = []
 # Application definition

 INSTALLED_APPS = [
+    'rest_framework',
     'sample_app',
     'django.contrib.admin',
     'django.contrib.auth',
```

### [Models](https://docs.djangoproject.com/ja/4.2/topics/db/models/)の定義

データが必要とするフィールドとその動作を定義

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── sample_app
        └── models.py ← これ
```

```
% vi sample_app/models.py
```

```python:django/sample_app/models.py
from django.db import models


class Memo(models.Model):
    title = models.CharField(max_length=64)
    memo = models.TextField(max_length=1024)
```

### [Serializer](https://www.django-rest-framework.org/api-guide/serializers/)の定義

「入力データ(jsonデータ)とモデルオブジェクトの相互変換」
「入力データのバリデーション」

新規作成するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── sample_app
        └── serializer.py ← 新規作成
```

```bash
% vi sample_app/serializer.py
```

```python:sample_app/serializer.py
from rest_framework import serializers
from .models import Memo


class MemoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Memo
        fields = '__all__'
```


↓ この作業によりできるディレクトリ構成

```
Django4.2-DRF
├── .git
├── .gitignore
├── .python-version
├── .venv
├── README.md
└── django
    ├── .venv
    ├── config
    │   ├── asgi.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    ├── db.sqlite3
    ├── manage.py
    ├── poetry.lock
    ├── pyproject.toml
    └── sample_app
        ├── __init__.py
        ├── __pycache__
        ├── admin.py
        ├── apps.py
        ├── migrations
        ├── models.py
        ├── serializer.py  # 新規作成
        ├── tests.py
        ├── urls.py
        └── views.py
```

### [ViewSet](https://www.django-rest-framework.org/api-guide/viewsets/)の追加

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── sample_app
        └──  views.py ← これ
```

```
% vi sample_app/views.py
```

```python:django/sample_app/views.py
from django.http import HttpResponse
from rest_framework import viewsets
from .models import Memo
from .serializer import MemoSerializer


def index(request):
    return HttpResponse("Hello, world.")


class MemoViewSet(viewsets.ModelViewSet):
    queryset = Memo.objects.all()
    serializer_class = MemoSerializer
```

### アプリ用URLConfの編集

REST frameworkへの設定を組み込む

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── sample_app
        └── urls.py ← これ
```

```
% vi sample_app/urls.py
```

既存ファイルとの差分

```diff:django/sample_app/urls.py
% git diff sample_app/urls.py

diff --git a/django/sample_app/urls.py b/django/sample_app/urls.py
index 88a9cac..43c2a4d 100644
--- a/django/sample_app/urls.py
+++ b/django/sample_app/urls.py
@@ -1,7 +1,11 @@
 from django.urls import path

 from . import views
+from rest_framework import routers

 urlpatterns = [
     path('', views.index, name='index'),
 ]
+
+router = routers.DefaultRouter()
+router.register(r'memo', views.MemoViewSet)
```

### ルート用URLConfの編集

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── urls.py ← これ
```

```
% vi config/urls.py
```

既存ファイルとの差分

```diff:django/config/urls.py
 % git diff config/urls.py
 
diff --git a/django/config/urls.py b/django/config/urls.py
index d212c09..6503349 100644
--- a/django/config/urls.py
+++ b/django/config/urls.py
@@ -16,8 +16,10 @@ Including another URLconf
 """
 from django.contrib import admin
 from django.urls import path, include
+from sample_app.urls import router as sample_app_router

 urlpatterns = [
     path('', include('sample_app.urls')),
     path('admin/', admin.site.urls),
+    path('api/', include(sample_app_router.urls)),
 ]
```

### データベース再構築

#### マイグレーションファイル作成
```bash
% poetry run python manage.py makemigrations

Migrations for 'sample_app':
  sample_app/migrations/0001_initial.py
    - Create model Memo
```

#### 再構築
```bash
% poetry run python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sample_app, sessions
Running migrations:
  Applying sample_app.0001_initial... OK
```

↓ この作業後のディレクトリ構成

```
Django4.2-DRF
├── .git
├── .gitignore
├── .python-version
├── .venv
├── README.md
├── django
│   ├── config
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── db.sqlite3
│   ├── manage.py
│   └── sample_app
│       ├── __init__.py
│       ├── admin.py
│       ├── apps.py
│       ├── migrations
│       │   ├── 0001_initial.py  # makemigrationsによって作成
│       │   └── __init__.py
│       ├── models.py
│       ├── tests.py
│       ├── urls.py
│       └── views.py
├── poetry.lock
└── pyproject.toml
```

### 管理画面にsample_appのモデルを追加する

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── sample_app
        └── admin.py ← これ
```

```
% vi sample_app/admin.py
```

```python:django/sample_app/admin.py
from django.contrib import admin
from .models import Memo


@admin.register(Memo)
class MemoAdmin(admin.ModelAdmin):
    pass
```


#### 動作確認で起動してみる

```
% poetry run python manage.py runserver
```

##### 確認 1: http://localhost:8000/api/memo/ にアクセスできるか確認
<img width="736" alt="Django REST Framework" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24811/a05a1606-56dd-1702-48f0-765138e16f55.jpeg">


##### 確認 2: http://localhost:8000/admin/sample_app/memo/ にアクセスできるか確認
<img width="760" alt="ログイン後" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24811/45adf007-803c-5fcb-5778-9e643c6c3a27.jpeg">


#### ここまでの変更をGitでコミット

```
git add .
git commit -m 'DRF組み込み'
```


##  Django Debug Toolbar

Django Debug Toolbarでデバッグ情報を表示

###  Django Debug Toolbar のインストール

```
Django4.2-DRF
└── django ← 現在の階層
```

```bsh 
% poetry add django-debug-toolbar
```


### Django Debug Toolbar の有効化

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── settings.py ← これ
```

```bash
% vi config/settings.py
```

```diff:djangoconfig/settings.py
% git diff config/settings.py

diff --git a/django/config/settings.py b/django/config/settings.py
index aa06725..1b42fa6 100644
--- a/django/config/settings.py
+++ b/django/config/settings.py
@@ -123,3 +123,18 @@ STATIC_URL = 'static/'
 # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

 DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+if DEBUG:
+    STATIC_ROOT = None
+    STATICFILES_DIRS = ['static']
+    # django-debug-toolbar
+    # https://django-debug-toolbar.readthedocs.io/en/latest/index.html
+    INSTALLED_APPS += ['debug_toolbar']
+    MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
+    DEBUG_TOOLBAR_CONFIG = {'SHOW_TOOLBAR_CALLBACK': lambda request: True}
+else:
+    import os
+    STATIC_URL = 'static/'
+    STATIC_ROOT = os.path.join(BASE_DIR, 'static')
+    STATICFILES_DIRS = []
+    ALLOWED_HOSTS = ['*']
```

### ルート用URLConfの編集

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── urls.py ← これ
```


```
% vi config/urls.py
```

```diff:django/config/urls.py
% git diff config/urls.py

diff --git a/django/config/urls.py b/django/config/urls.py
index d956986..9242764 100644
--- a/django/config/urls.py
+++ b/django/config/urls.py
@@ -18,6 +18,7 @@ from django.contrib import admin
 from django.urls import path, include
 from sample_app.urls import router as sample_app_router
 from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
+from config import settings

 urlpatterns = [
     path('', include('sample_app.urls')),
@@ -27,3 +28,7 @@ urlpatterns = [
     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'),
 ]
+
+if settings.DEBUG:
+    import debug_toolbar
+    urlpatterns += [path('__debug__/', include(debug_toolbar.urls))]
```

#### 動作確認で起動してみる

```bash
% poetry run python manage.py runserver
```

##### 確認: http://localhost:8000/api/memo にアクセスして確認


#### ここまでの変更をGitでコミット

```
% git add .
% git commit -m 'Django Debug Toolbar組み込み'
```

## JWT

JWT(JSON Web Token)を利用したWebアプリケーションの認証の導入



### [djangorestframework-simplejwt](https://django-rest-framework-simplejwt.readthedocs.io/en/latest/) のインストール

```
Django4.2-DRF
└── django ← 現在の階層
```


```
% poetry add djangorestframework-simplejwt
```

### djangorestframework-jwt の有効化

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── settings.py ← これ
```

```
% vi config/settings.py
```


```diff:config/settings.py
diff --git a/django/config/settings.py b/django/config/settings.py
index 1b42fa6..e74d123 100644
--- a/django/config/settings.py
+++ b/django/config/settings.py
@@ -31,6 +31,7 @@ ALLOWED_HOSTS = []
 # Application definition

 INSTALLED_APPS = [
+    'rest_framework_simplejwt',
     'rest_framework',
     'sample_app',
     'django.contrib.admin',
@@ -124,6 +125,15 @@ STATIC_URL = 'static/'

 DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

+REST_FRAMEWORK = {
+    'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',),
+    'DEFAULT_AUTHENTICATION_CLASSES': (
+        'rest_framework_simplejwt.authentication.JWTAuthentication',
+        'rest_framework.authentication.SessionAuthentication',
+        'rest_framework.authentication.BasicAuthentication',
+    )
+}
+
 if DEBUG:
     STATIC_ROOT = None
     STATICFILES_DIRS = ['static']
```

### ルート用URLConfの編集

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── urls.py ← これ
```

```
% vi config/urls.py
```

既存ファイルとの差分

```diff:django/config/urls.py
% git diff config/urls.py

diff --git a/django/config/urls.py b/django/config/urls.py
index c759bb5..f7ff5e8 100644
--- a/django/config/urls.py
+++ b/django/config/urls.py
@@ -18,11 +18,17 @@ from django.contrib import admin
 from django.urls import path, include
 from sample_app.urls import router as sample_app_router
 from config import settings
+from rest_framework_simplejwt.views import (
+    TokenObtainPairView,
+    TokenRefreshView,
+)

 urlpatterns = [
     path('', include('sample_app.urls')),
     path('admin/', admin.site.urls),
     path('api/', include(sample_app_router.urls)),
+    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
+    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
 ]

 if settings.DEBUG:
```


#### 動作確認で起動してみる

```
% poetry run python manage.py runserver
```

#### トークンなしでGETリクエスト
```bash
 % curl -s -XGET http://localhost:8000/api/memo/ | jq .
```

##### 結果
```json
{
  "detail": "Authentication credentials were not provided."
}
```

#### アクセストークンとリフレッシュトークンを取得する
```bash
% DJANGO_USERNAME='admin'
% DJANGO_PASSWORD='userpassword'
% curl \
  -s \
  -X POST \
  -H "Content-Type: application/json" \
  -d "{\"username\": \"${DJANGO_USERNAME}\", \"password\": \"${DJANGO_PASSWORD}\"}" \
  http://localhost:8000/api/token/ | jq .
```

##### 結果
```json
{
  "refresh": "REFRESH_TOKEN",
  "access": "ACCESS_TOKEN"
}
```


#### アクセストークンを利用して情報を取得

##### トークン発行
```bash
% DJANGO_USERNAME='admin'
% DJANGO_PASSWORD='userpassword'
% ACCESS_TOKEN=$(curl \
  -s \
  -X POST \
  -H "Content-Type: application/json" \
  -d "{\"username\": \"${DJANGO_USERNAME}\", \"password\": \"${DJANGO_PASSWORD}\"}" \
  http://localhost:8000/api/token/ | jq -r .access)
```
##### 情報を取得
```bash
% curl \
  -s \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  http://localhost:8000/api/memo/ | jq .
```

##### 結果
```json
[]
```


####  POSTしてデータ登録

```bash
% curl -XPOST -H "Authorization: Bearer ${ACCESS_TOKEN}" http://127.0.0.1:8000/api/memo/ -d 'title=a&memo=a'
```

##### 結果
```json
{"id":1,"title":"a","memo":"a"}
```


####  POSTデータの確認

```bash
% curl \
  -s \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  http://localhost:8000/api/memo/ | jq .
```

##### 結果
```json
[
  {
    "id": 1,
    "title": "a",
    "memo": "a"
  }
]
```

#### アクセストークンの期限が切れたらリフレッシュトークンを利用して新しいアクセストークンを再取得する。

```bash
curl \
  -s \
  -X POST \
  -H "Content-Type: application/json" \
  -d "{\"refresh\":\"${REFRESH_TOKEN}\"}" \
  http://localhost:8000/api/token/refresh/ | jq .
```

##### 結果
```json
{
  "access": "ACCESS_TOKEN"
}
```

#### ここまでの変更をGitでコミット

```
% git add .
% git commit -m 'JWT組み込み'
```

## Swagger

Django Rest Framework のコードから Swagger ドキュメントを生成


### [drf-spectacular](https://drf-spectacular.readthedocs.io/en/latest/) のインストール

```
Django4.2-DRF
└── django ← 現在の階層
```

```
% poetry add drf-spectacular
```

### Swagger の有効化

編集するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── settings.py ← これ
```

```
% vi config/settings.py
```

```diff:django/config/settings.py
% git diff config/settings.py

diff --git a/django/config/settings.py b/django/config/settings.py
index e74d123..e52e56f 100644
--- a/django/config/settings.py
+++ b/django/config/settings.py
@@ -31,6 +31,7 @@ ALLOWED_HOSTS = []
 # Application definition

 INSTALLED_APPS = [
+    'drf_spectacular',
     'rest_framework_simplejwt',
     'rest_framework',
     'sample_app',
@@ -131,7 +132,14 @@ REST_FRAMEWORK = {
         'rest_framework_simplejwt.authentication.JWTAuthentication',
         'rest_framework.authentication.SessionAuthentication',
         'rest_framework.authentication.BasicAuthentication',
-    )
+    ),
+    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
+}
+
+SPECTACULAR_SETTINGS = {
+    'TITLE': 'Swagger Sample App',
+    'DESCRIPTION': '',
+    'VERSION': 'v2024.3.23',
 }

 if DEBUG:
```


### ルート用URLConfの編集

```
Django4.2-DRF
└── django ← 現在の階層
    └── config
        └── urls.py ← これ
```

```
% vi config/urls.py
```

既存ファイルとの差分

```diff:django/config/urls.py
% git diff config/urls.py

diff --git a/django/config/urls.py b/django/config/urls.py
index f7ff5e8..df6f3c2 100644
--- a/django/config/urls.py
+++ b/django/config/urls.py
@@ -22,6 +22,7 @@ from rest_framework_simplejwt.views import (
     TokenObtainPairView,
     TokenRefreshView,
 )
+from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView

 urlpatterns = [
     path('', include('sample_app.urls')),
@@ -29,6 +30,9 @@ urlpatterns = [
     path('api/', include(sample_app_router.urls)),
     path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
     path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
+    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
+    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'),
 ]

 if settings.DEBUG:
```

#### 動作確認で起動してみる

```
% poetry run python manage.py runserver
```

##### 確認: http://localhost:8000/api/schema/swagger-ui/ にアクセスしてswaggerが表示されているか確認
<img width="485" alt="Django REST Swagger" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24811/234bb2dd-db75-5ee4-94ff-26e3aac5a4d5.png">

#### ここまでの変更をGitでコミット

```
% git add .
% git commit -m 'swagger組み込み'
```



## 本番環境の構築

```
Django4.2-DRF
└── django ← 現在の階層
```


### [uvicorn/gunicorn](https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/uvicorn/)インストール

```
% poetry add uvicorn gunicorn
```

#### 動作確認で起動してみる


```bash
% # --chdir /path/to/Django4.2-DRF/django の部分は
% # Djangoのconfigディレクトリがあるディレクトリを指定
% poetry run python -m gunicorn config.asgi:application \
        --worker-class uvicorn.workers.UvicornWorker \
        --workers 4 \
        --bind 0.0.0.0:8000 \
        --max-requests 500 \
        --max-requests-jitter 200 \
        --timeout 300 \
        --graceful-timeout 300 \
        --reload \
        --chdir /path/to/Django4.2-DRF/django
```


http://localhost:8000/api/memo

### Django用Dockerfileの作成

新規に作成するファイル

```
Django4.2-DRF
└── django ← 現在の階層
    └── Dockerfile  ← 新規作成
```

```
% vi Dockerfile
```

```Dockerfile:Dockerfile
FROM python:3.11-slim-bookworm
ENV TZ=Asia/Tokyo \
    PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    POETRY_VIRTUALENVS_CREATE=false \
    PATH="/root/.local/bin:$PATH"

# Install Packages
RUN apt-get update && apt-get install -y curl

WORKDIR /app

# Source Copy
COPY . /app/

# Poetry Install
RUN curl -sSL https://install.python-poetry.org | python3 -
RUN poetry install --no-root

# Create DB & Static file
RUN sed -i.org 's/^DEBUG\s+=\s+True$/DEBUG = False/' config/settings.py && \
    poetry run python manage.py makemigrations && \
    poetry run python manage.py migrate && \
    poetry run python manage.py collectstatic --noinput

EXPOSE 8000

# Run Djagno
CMD ["poetry", "run", "python", "manage.py", "runserver", "0.0.0.0:8000"]
```


#### イメージが正しく作成されるかビルドしてみる

```
% docker build . -t django4.2:v$(date '+%Y.%-m.%-d')
% docker run --name django4.2 --rm -p8000:8000 django4.2:v$(date '+%Y.%-m.%-d')
```



#### 確認: http://localhost:8000/api/memo/ にアクセスして確認

cssやjsが404でもOK

##### 一旦イメージを削除しておく

```
% docker rmi django4.2:v$(date '+%Y.%-m.%-d')
```

### Nginx用Dockerfileの作成

新規に作成するファイル

```
Django4.2-DRF
├── django ← 現在の階層
└── nginx           ← 新規作成
    └── Dockerfile  ← 新規作成
```

```
% cd ../
% mkdir nginx && cd nginx
% vi Dockerfile
```

```Dockerfile:nginx/Dockerfile
FROM nginx:latest

# Remove default log
RUN rm /var/log/nginx/access.log /var/log/nginx/error.log

# Create new logging pipe and point nginx logs to it
RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stdout /var/log/nginx/error.log

COPY default.conf /etc/nginx/conf.d/default.conf

EXPOSE 8080
```

### Nginx用default.confの作成

```
Django4.2-DRF
├── django 
└── nginx  ← 現在の階層
    └── default.conf  ← 新規作成
```

```bash
% vi default.conf
```

```conf:nginx/default.conf
upstream django-upstream {
    server app:8000;
}

server {
    listen 8080;
    server_name localhost;

    access_log  /var/log/nginx/host.access.log  main;
    error_log   /var/log/nginx/error.log notice;

    location /static/ {
        alias /static/;
    }

    location / {
        proxy_pass http://django-upstream/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```


## docker-compose

新規に作成するファイル

```
Django4.2-DRF
└── nginx  ← 現在の階層
└── docker-compose.yml ← 新規作成
```

```
% cd ../
% vi docker-compose.yml
```


```docker-compose.yml
version: '3'

services:
  app:
    container_name: app
    build: ./django/
    working_dir: /app
    volumes:
      - ./django:/app
      - static_volume:/app/static
    ports:
        - "8000:8000"
    command: >
      sh -c "poetry run python -m gunicorn config.asgi:application \
              --worker-class uvicorn.workers.UvicornWorker \
              --workers 4 \
              --bind 0.0.0.0:8000 \
              --max-requests 500 \
              --max-requests-jitter 200 \
              --timeout 300 \
              --graceful-timeout 300"

  web:
    container_name: web
    build: ./nginx/
    ports:
      - "8080:8080"
    volumes:
      - static_volume:/static
    depends_on:
      - app

volumes:
  static_volume:
```

### 起動してみる

```
% docker-compose up -d
```


↓のURLにアクセスできるか確認

|Page                     | URL |
|---                      |---   |
|Django (Hello, world.)   |http://localhost:8080|
|Django administration    |http://localhost:8080/admin/|
|REST framewrk            |http://localhost:8080/api/|
|Swagger                   |http://localhost:8080/api/schema/swagger-ui|
|Django REST framework JWT| http://localhost:8080/api/token/|

#### ここまでの変更をGitでコミット
```
% git add .
% git commit -m 'docker-compose対応'
```

### docker-compose コマンド集

|アクション  |コマンド  |
|---|---|
|起動|docker-compose up -d|
|停止|docker-compose stop|
|再起動|docker-compose restart|
|ログを見る|docker-compose logs|
|コンテナ削除|docker-compose down|
|コンテナとイメージ削除|docker-compose down --rmi all|