Django Rest Framework で親子関係のあるテーブルにデータを登録する

#はじめに

１つのエンドポイントで、親子関係があるテーブル構造に対してどうやってデータを登録するんだろうか。
業務中にそんな事があったので、記載します。


#目次
１．　要件
２．　リクエストAPIのデータ構造
３．　モデルの定義
４．　Viewの定義
５．　Serializerの定義
６．　URLディスパッチャの定義
７．　動作の確認
８．　まとめ


#１. 要件
今回は、「受注をする」という要件を想定して作業をしてみようと思います。
ECなどでよくある受注機能ですね。
受注ヘッダ（orderhead）、受注明細(orderdetail)という1対多の
親子関係を持ったテーブル構造に対してデータを登録することをゴールにします。

#2. リクエストAPIのデータ構造
まずDjango側で受けるPOSTデータの定義をしようと思います。
以下のように、受注ヘッダの基となるデータ群（親）と、受注明細の基となるデータ群（子）を
定義しました。

```order.json
{
    "order_number"  : "9000000",
    "customer_id" : "customer001",
    "order_detail": [
        {
            "product_code":"A001",
            "quantity":2
        },
        {
            "product_code":"A002",
            "quantity":2
        }
    ]
}
```

#3. モデルの定義
データを格納するテーブル構造を以下に記載します。
受注ヘッダ（OrderHead）と受注明細（OrderDetail）です。
受注明細側には、Foreignキーを利用して受注ヘッダと受注明細に１対多の関係を築きます。

```models.py
class OrderHead(models.Model):
    order_number = models.CharField(max_length=10)
    order_at = models.DateTimeField(auto_now_add = True)
    customer_id =  models.CharField(max_length=12)

class OrderDetail(models.Model):
    orderhead = models.ForeignKey(OrderHead, related_name='details', on_delete=models.CASCADE)
    product_code = models.CharField(max_length=10)
    quantity = models.PositiveSmallIntegerField()

```


#4. Viewの定義
リクエストデータを受けたあとに、どう処理するかを記述します。
極力Django Rest frameworkの機能を活かそうとModelViewSetsを利用してます。

```view.py

class OrderViewSet(viewsets.ModelViewSet):

    queryset = OrderHead.objects.all()
    serializer_class = OrderSerializer

    def create(self, request):

            order_serializer = OrderSerializer(data=request.data)

            if not order_serializer.is_valid():
                print(order_serializer.errors)
                return Response("validation error........")
                
            result = order_serializer.save()
            return Response(result)

```

#5. Serializerの定義
view側からの呼び出しで、リクエストデータに対しての処理を記述します。
ModelSerializerを利用することでリクエストデータと、モデルフィールドのマッピングをして
バリデーションチェックやデータ登録処理を行うようにします。
※バリデーションは、modelフィールドに基づいて実施

order_haed、order_detailへデータ登録した際のサロゲートキー（自動採番キー）を取得して
レスポンスデータとして返却する仕組みにしました。

```serializer.py


class OrderDetailSerializer(serializers.ModelSerializer):

    class Meta: 
        model = OrderDetail
        fields = ('product_code','quantity')


class OrderSerializer(serializers.ModelSerializer):

    order_detail = OrderDetailSerializer(many=True)

    class Meta:
        model = OrderHead
        fields = ('order_number','customer_id', 'order_detail')

    def create(self, validated_data):

        result_dict = {}
        detail_list = []

        order_detail = validated_data.pop('order_detail')

        created_orderhead = OrderHead.objects.create(**validated_data)
        result_dict['head_id'] = created_orderhead.id

        for order_detail_data in order_detail:

            created_orderdetail = OrderDetail.objects.create(orderhead=created_orderhead, **order_detail_data)
            detail_list.append(created_orderdetail.id)

        result_dict['detail_id'] = detail_list
        return result_dict
```

#6. URLディスパッチャの定義（URLConf）
アプリケーション毎のurls.pyの設定は以下になります。
受注の操作を可能とするAPIなので、ordersを定義して、このURLに対して
HTTPメソッドを指定することで、動作を行うようにします。

```urls.py
# coding: utf-8

from rest_framework import routers
from .views import OrderViewSet


router = routers.DefaultRouter()
router.register(r'orders', OrderViewSet)
```
#7. 動作の確認
Postmanを利用して確認します。
レスポンスが返ってきていることが分かります。

<img width="830" alt="response.png" src="https://qiita-image-store.s3.amazonaws.com/0/154145/1be6420b-643f-632d-fecd-ca70c5af8a52.png">

続いてDBに登録されているか確認します。
oderheadテーブルのidがレスポンスで返ってきた値と一緒であることが分かります。

**・orderheadテーブル確認**
<img width="543" alt="orderhead.png" src="https://qiita-image-store.s3.amazonaws.com/0/154145/b7c17ad5-331c-ad96-4d4f-6c23ccb7c699.png">


**・orderdetailテーブル確認**
<img width="548" alt="orderdetail.png" src="https://qiita-image-store.s3.amazonaws.com/0/154145/7699e2a1-a9af-85b1-e281-ad94f3b770f9.png">


#8. まとめ
受注ヘッダと受注明細にデータを登録するAPIを作成することができました。
作成して思ったことは、serializerは、OrderSerializerが１つあって、その中でOrderHeader,OrderDetailのserializerを読む形ができればもっと綺麗な形で
デザインできたんじゃないかと思います。
今日はここまで。
最後まで読んでくださった方　ありがとうございました。