DjangoRestFrameworkのコードからSwaggerドキュメントを生成しAPI設計を共有

## 弊社開発環境
弊社のエンジニアチームでは以下の開発環境が最近の主流です。

サーバーサイド： `Django + DjangoRestFramework(API)`
WEBクライアント： `Vue.js`
アプリクライアント： `Flutter( + Swift)( + Kotlin)`

サーバーサイドとクライアントサイドで違う人が開発する場合は多く、
そのようなときにAPI設計を共有するのって難しいですよね。
そのときにSwaggerを使ったAPI設計の共有ドキュメントを生成しています。

## イメージ図（Swaggerを書くと１つのAPIでこれだけの情報が取り出せます）
![スクリーンショット_2019-12-06_21_29_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40127/93ada318-9f97-c409-4b36-942b9193e68d.png)


## どうやって書いているのか

コードからドキュメントを自動生成しています。

```python
class AbcApiView(ApiView):
    @swagger_auto_schema(
        request_body=AbcRequestSerializer,
        responses={200: openapi.Response('OK', AbcResponseSerializer)})
    def post(self, request, *args, **kwargs):
        """
        期間限定キャンペーンで無料になっている商品を一覧表示
        """
        pass
```
DRFでSerializerを作っている人は、request_bodyとresponsesにSerializerの型を渡すだけ😍
これだけでドキュメントを生成してくれます。
`ドキュメントを追加するのに3行のコードと1行の説明文(docstring)だけで済みます!!`

ついでにSerializerも貼っておきます。

```python
class AbcRequestSerializer(serializers.Serializer):
    page = serializers.IntegerField(default=1)
    genre = serializers.CharField(default='', allow_blank=True, allow_null=True)

class AbcApiResponseSerializer(serializers.Serializer):
    items = serializers.ListField(child=ZyxSerializer(), default=[])
    page = serializers.IntegerField()
    has_next = serializers.BooleanField()
    has_prev = serializers.BooleanField()
    max_page = serializers.IntegerField()
```

シリアライザーにフィールドを追加したいとき、ドキュメントをメンテしなくて済むのでラクチンですね。

## yamlでも書ける
yamlで書いてみると以下のようになります。

```yaml
paths:
  /api/campaign/v1/:
    post:
      summary: 期間限定キャンペーンで無料になっている商品を一覧表示
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                page:
                  type: integer
                genre:
                  type: string
                  default: ""
              example:   # Sample object
                page: 2
                name: "MANGA"
      responses:
        '200':
          description: OK
```

**長い!!!!** ( responseを書くのを諦めた

設計段階の場合はこれでいいのですが、たくさんのエンドポイントを作っているとファイルが１０００行くらいすぐに増えます。またメンテもしなくなるので、古いドキュメントが完成です。

## docstringの部分には、もうちょっと複雑なことも書ける

docstringの部分には、htmlやコードも書けます。


```python
class AbcApiView(ApiView):
    def post(self, request, *args, **kwargs):
    """
    まとめ買いのリスト表示を行います。

    :param tcode: 基準となるコードを入力します。

    :return 200 ok_response

    ```
    [{
        title_code: 'tcode1', // タイトルコードが一意でない場合がある。シリーズもの。
        serial_number: 1 // tcodeとserial_numberの２つで一意
        price: 0, // 購入するための金額
        is_purchased: false,　// 購入済みの場合はグレーアウト
        is_selected: false, // 購入対象をユーザーが選択
        regular_price: 100, // 通常価格
        campaign_text: 'campaign' // 無料・半額キャンペーン情報
        thumbnail_image_path: '/favicon/favicon256.png' // サムネイル
    },]
    ```
    priceとregular_priceは違うものになります。<br>
    regular_priceは通常価格。<br>
    500円のものを途中まで購入している場合は、priceは300
    """
```

↓

![スクリーンショット_2019-12-06_21_40_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/40127/565995d6-5817-7794-e666-091c2f00d101.png)

## インストール & 設定
https://github.com/axnsan12/drf-yasg
に従ってインストールすればすぐに使えました😁

一点だけ変更したところを記載します。

```python
schema_view = get_schema_view(
    ....
    permission_classes=(permissions.AllowAny,) if settings.DEBUG else (permissions.IsAdminUser,),
)
```

permission_classesは閲覧権限です。
ローカルではログインしていなくても閲覧できますが、ローカル以外では管理者権限でないと閲覧できないようにしました。

以上です。
明日は弊社CTOの@ytyng先生のDjangoAdmin教室です！