APIをテストツール「Postman」を使ったDjangoとのCRUD機能実装（設計編）

## 概要

現在作成中のアプリでDjangoにAPI環境を作り、CRUD機能を実装しようとした時に挙動をミニマムで検証できる環境が欲しいと思っていたのですが、運用コストが少なく比較的簡単に実践できるツールの紹介と実装方法をシェアしようと思います。


### 実装機能

- Postmanを使ったlocalhostとの連携
- Django REST Framework（DRF）を使ったAPI連携、CRUD機能
    - 関数ベースView
    - クラスベースView



| 項番 | 記事 |
| :--- | :--- |
| 1 | [React + Django + CORSを使ったフロントエンド / バックエンドのデータ連携](https://qiita.com/xinome/items/8319a7f85424bd6e62de) |
| 2 | [Django 管理画面のカスタマイズ](https://qiita.com/xinome/items/f883ce4565cd9c56ad17) |
| 3 | [Django REST framework(DRF)を使ったAPIサーバーとReactとのデータ連携](https://qiita.com/xinome/items/a524522b6697a3d9ffa9) |
| 4 | [Django REST frameworkのserializersを使った外部キーモデルの参照](https://qiita.com/xinome/items/49fdbf5c8cac0bbbcf3a) |
| 5 | [React + Redux / Redux Toolkitを使った非同期通信の検証](https://qiita.com/xinome/items/7971ac9091d126f3861a) |
| 6 | APIをテストツール「Postman」を使ったDjangoとのCRUD機能実装(設計編)（本記事） |
| 7 | [APIをテストツール「Postman」を使ったDjangoとのCRUD機能実装(実装編)](https://qiita.com/xinome/items/9541aa8afe7d77ea0b1e) |




## フォルダ構成

今回使用しているものをメインに抜粋

- バックエンド(Python / Django)

```
.
├── backend_django
│   └── settings.py
├── django_app
│   ├── models.py
│   ├── serializers.py
│   ├── urls.py
│   └── views.py
```



## Postmanとは

APIを使用されたことのある方はご存知の方も多いと思いますが、[Postman](https://www.postman.com/)はAPIのエンドポイントを呼び出し、リクエストを送信し、レスポンスを受け取り、解析するためのテストツールの一種です。
自分は今回のバックエンドの学習にあたって初めて知り、localhostで組んでいる時点では実質無料で設定コストも抑えられていますが、それ以外のAWSや外部インフラを想定した環境設定も対応できそうなイメージです。

他にも[Swagger](https://swagger.io/)など現場で使用されているAPIテストツールがありますが、個人などミニマムな開発やコスト重視の場合はPostmanの方が適していると思いました。


## 実装方法

### Postman

Webアプリとデスクトップアプリがありますが、今回はデスクトップアプリを使用しました。
まず環境設定として `Headers` がデフォルトで設定されているのですが、JSON形式でデータ連携を行う場合は　`Content` を `application/json` に変更するだけで大丈夫です。

![スクリーンショット 2024-02-09 14.58.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76115/c1767b78-a820-3693-7eca-333a4cf291ed.png)


### バックエンド

検証も兼ねてですが、バックエンド側では関数ベース、クラスベースの２種類のView設計を実装してみました。
これまで `GET` 機能の実装がメインだったので関数ベースを使っていましたが、 `UPDATE` の実装でかなり行き詰まった感があり、クラスベースにシフトしていこうと思っています。

```python:app/urls.py
from django.urls import path, include
from . import views

urlpatterns = [
    path("postman_test/", views.postman_test, name="postman_test"),
    path("postman_class_test/<int:pk>", views.postman_class_test.as_view(), name="postman_class_test"),
]
```

関数ベースの場合は `views.関数名` 、クラスベースの場合は `views.クラス名.as_view()` と指定します。

```python:app/views.py
from django.shortcuts import render
from django.http import JsonResponse

from django.views.decorators.csrf import csrf_exempt  # 追加
from django.utils.decorators import method_decorator  # 追加

from rest_framework.views import APIView  # 追加
from rest_framework import status  # 追加
from rest_framework.decorators import api_view  # 追加

@csrf_exempt
@api_view(['GET', 'POST', 'PUT'])
def postman_test(request):
  return JsonResponse({
    "message": "postman_test",
    "request": {
      "method": request.method,
      "path": request.path,
      "data": request.data,
    }
  })

# class based view
@method_decorator(csrf_exempt, name='dispatch')
class postman_class_test(APIView):
  
  # def get(self, request, *args, **kwargs):
  def get(self, request, pk):

    # 1件のみ取得
    queryset = MypageUserProfile.objects.get(id=pk)
    serializer_class = MypageUserProfileUpdateSerializer(queryset)

    data = serializer_class.data

    return JsonResponse(data, safe=False)

  def post(self, request, pk):
    
    queryset = MypageUserProfile.objects.get(id=pk)

    serializer_class = MypageUserProfileUpdateSerializer(queryset, data=request.data)
    if serializer_class.is_valid():
      serializer_class.save()
      return JsonResponse(serializer_class.data, status=201)

    return JsonResponse(serializer_class.errors, status=400)
```
views.pyの設計でキーになるのが、 **「CSRF(クロスサイトリクエストフォージェリ)」** の設定です。
これを設定しないとPostmanでリクエストした際に403アクセスエラーが起こるため、

- settings.pyに記載されている'django.middleware.csrf.CsrfViewMiddleware'を削除
- 関数ベースViewの接頭に `@csrf_exempt` を追加
- クラスベースViewの接頭に `@method_decorator(csrf_exempt, name='dispatch')` を追加

などの方法でアクセス制限を解除することができます。
（djangoのtemplateを使う場合は `<csrf_token>` タグでも実装できますが、今回は使用しません）
ただCSRFを一括で無効化するやり方は非推奨となっているほか、セキュリティ面での脆弱性があるためあくまで検証用として意識する必要があります。

[DRF公式サイト](https://www.django-rest-framework.org/tutorial/2-requests-and-responses/#request-objects)にあるように、引数になっている `request` は Django本来の [HTTPRequest](https://docs.djangoproject.com/ja/5.0/ref/request-response/)にあたるもので、

- 関数ベース: 接頭に `@api_view` を記載
- クラスベース: 継承元クラスに `APIView` を指定
することでAPIを使ったViewを設計することができます。


>    The @api_view decorator for working with function based views.
>    The APIView class for working with class-based views.


その他、レスポンスの返り値にステータスを参照する `status` をインポートしています。


```python:app/models.py
from django.db import models
  :
class MypageUserProfile(BaseMeta):
  id = models.AutoField(primary_key=True)
  name = models.CharField(max_length=255)
  account_id = models.CharField(max_length=255)
  password = models.CharField(max_length=255)
  email = models.CharField(max_length=255)
  zip = models.CharField(max_length=7)
  address = models.CharField(max_length=255)
  phone = models.CharField(max_length=11, null=True)
  member_type = models.ForeignKey(PricingPlan, on_delete=models.PROTECT, null=True)

  class Meta:
    db_table = 'mypage_user_profile'
    verbose_name_plural = 'マイページ_ユーザープロフィール'

  def __str__(self):
    return self.name
```


```python:app/serializers.py
from rest_framework import serializers
from .models import (
    :
  MypageUserProfile,
)

class MypageUserProfileUpdateSerializer(serializers.ModelSerializer):
  
  class Meta:
    model = MypageUserProfile
    fields = ('id', 'name', 'account_id', 'password', 'email', 'zip', 'address', 'phone')
```

データ参照元となるモデルは外部キーを組み合わせた構造ですが、serializersでは一部のみ抜粋してGET, POST機能を実装する構成としています。


## 実装ビュー・まとめ

### 関数ベースビュー
![スクリーンショット 2024-02-09 16.34.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76115/4e141e46-5ab5-578a-c997-d30a836e86f1.png)
![スクリーンショット 2024-02-09 16.34.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76115/be36fe37-8d02-54af-45e4-d4bebc5138e1.png)


関数ベースの場合、引数が `request` のみです。


- `request.method` : リクエスト方法
- `request.path` : APIエンドポイント
- `request.data` : CRUD処理時の返り値

などのパラメータを取得することができるのですが、 `request` 単体で取得することはできず以下のように返ってくるため、お作法的に覚える部分もありそうです。

```bash
request:  <WSGIRequest: POST '/api/postman_test/'>
```

### クラスベースビュー

- GET
![スクリーンショット 2024-02-09 15.50.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76115/27e0c3d8-cb7f-0a91-0a1d-2f7cf7505b69.png)

`id` をキーに1件だけデータを取得し、その返り値を表示させる構成です。モデルで構成された外部キーの項目は表示されず、serializerでフィルタリングした項目だけ返ってきているのがわかります。


- POST
![スクリーンショット 2024-02-09 15.50.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76115/3ec2d751-50cc-de97-48ea-3e52dcf8bc2a.png)

POST処理をする場合はParamsから Body > Row を選び、**更新する部分のみ抜粋**したJSONデータを入力します。ここを間違えるとかなり痛手になるので注意です。
成功した場合、返り値に更新後のデータが表示されます。（画面上では `zip` を更新）
django-adminで設計しているとこの更新処理も確認でき、フロントエンドのフォーム等からリクエストデータを送信した際も同様の構成で実装できました。



### 参考文献

- Postman

https://www.postman.com/

- Django REST Framework: Requests

https://www.django-rest-framework.org/api-guide/requests/

https://www.django-rest-framework.org/tutorial/2-requests-and-responses/#request-objects

- 【Django】 csrf_tokenの仕組みとCSRF無効化・画面カスタマイズする方法

https://djangobrothers.com/blogs/django_csrf/

- [Django]クラスベースビューと関数ベースビューの使い分けを考える

https://zenn.dev/ikemo/articles/django-class-based-view-or-function-based-view