13
7

More than 5 years have passed since last update.

Django REST Frameworkでネストされたモデルをネストされていないかのように扱い、伏見稲荷大社を宮城県に誘致する

Posted at

前置き

タイトルを見て、一体何を言っているんだ?と思われた方もいるかと思いますが他にいいのが思いつきませんでした。
やりたいこととしては、例えば↓のようなレスポンスを返すDjango REST Frameworkで作ったAPIがあったとします。

[
  {
    "id": 1,
    "name": "伏見稲荷大社",
    "postcode": "612-0882",
    "prefecture": {
      "id": 26,
      "name": "京都府"
    },
    "address": "京都市伏見区深草藪之内町68"
  },
  {
    "id": 2,
    "name": "広島平和記念資料館",
    "postcode": "730-0811",
    "prefecture": {
      "id": 34,
      "name": "広島県"
    },
    "address": "広島市中区中島町1-2"
  }
]

これを↓のようにしたいんです。

[
  {
    "id": 1,
    "name": "伏見稲荷大社",
    "postcode": "612-0882",
    "prefecture": "京都府",
    "address": "京都市伏見区深草藪之内町68"
  },
  {
    "id": 2,
    "name": "広島平和記念資料館",
    "postcode": "730-0811",
    "prefecture": "広島県",
    "address": "広島市中区中島町1-2"
  }
]

ポイントはprefectureです。beforeでは入れ子になっていますが、afterは入れ込になっていないのがわかると思います。
何故そうしたいのか、どうすればできるのか、の前にまずはbeforeのレスポンスを返すようなAPIを作るためのコードを用意したいと思います。

Django REST Frameworkを使いますので、準備としてひとまずは以下の画面がみられる状態にだけしておいて貰えればと思います。それと、django-admin startproject appというようにappという名前でプロジェクトを作った前提で話しています。

default.png

モデルの定義

観光スポットモデルと都道府県マスタの為のモデルを以下のように定義します。CharFieldmax_lengthの値とかが本当にこれが最適かと思うようなところもありますが、大体こんな感じになると思います。

app/models.py
from django.db import models

class Prefecture(models.Model):
    name = models.CharField(
        max_length=16,
    )

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


class SightseeingSpot(models.Model):
    name = models.CharField(
        max_length=255,
    )
    postcode = models.CharField(
        max_length=8,
    )
    prefecture = models.ForeignKey(
        Prefecture,
        on_delete=models.PROTECT,
    )
    address = models.CharField(
        max_length=128,
    )

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

ちなみに伏見稲荷大社と広島平和記念資料館は、2018年 外国人に人気の日本の観光スポットランキングの1位と2位らしいですよ。
モデルを作ったらマイグレーションします。

$ python manage.py makemigration
$ python manage.py migrate

なお、このSQLで都道府県とランキング1位と2位をDBに登録できます。

シリアライザーの定義

シリアライザーも以下のように定義します。

app/serializers.py
from .models import Prefecture
from .models import SightseeingSpot
from rest_framework import serializers


class PrefectureSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(
        required=False,
    )
    name = serializers.CharField(
        required=False,
    )

    class Meta(object):
        model = Prefecture
        fields = (
            'id',
            'name',
        )


class SightseeingSpotSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(
        required=False,
    )
    name = serializers.CharField(
        required=False,
    )
    postcode = serializers.CharField(
        required=False,
    )
    prefecture = PrefectureSerializer(
        read_only=True,
    )
    address = serializers.CharField(
        required=False,
    )

    class Meta(object):
        model = SightseeingSpot
        fields = (
            'id',
            'name',
            'postcode',
            'prefecture',
            'address',
        )

ViewSetの定義

ModelViewSetを定義します。

app/views.py
from .models import SightseeingSpot
from .serializers import SightseeingSpotSerializer
from rest_framework import viewsets


class SightseeingSpotViewSet(viewsets.ModelViewSet):
    queryset = SightseeingSpot.objects.all()
    serializer_class = SightseeingSpotSerializer

ルートの設定

/api/sightseeingspotsで観光スポット一覧が表示できるように設定します。

app/urls.py
from .views import SightseeingSpotViewSet
from django.conf.urls import include
from django.conf.urls import url
from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'^sightseeingspots', SightseeingSpotViewSet)
urlpatterns = [
    url(r'^api/', include(router.urls)),
]

動作確認

このように/api/sightseeingspotsで観光スポット一覧が表示できればばっちりです。

sightseeingspots.png

ネストされていないかのように扱う データの取得時

さて、prefectureがネストされています。気になります。どうしてもネストをなくしたい。どうればいいか。私はこうしました。

app/serializers.py
from .models import Prefecture
from .models import SightseeingSpot
from rest_framework import serializers


class PrefectureSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(
        required=False,
    )
    name = serializers.CharField(
        required=False,
    )

    class Meta(object):
        model = Prefecture
        fields = (
            'id',
            'name',
        )


class SightseeingSpotSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(
        required=False,
    )
    name = serializers.CharField(
        required=False,
    )
    postcode = serializers.CharField(
        required=False,
    )
    prefecture = serializers.SerializerMethodField(
        read_only=True,
    )
    address = serializers.CharField(
        required=False,
    )

    class Meta(object):
        model = SightseeingSpot
        fields = (
            'id',
            'name',
            'postcode',
            'prefecture',
            'address',
        )

    def get_prefecture(self, obj):
        return obj.prefecture.name

SightseeingSpotSerializerserializers.SerializerMethodFieldを使うようにしました。これで返ってくるレスポンスは以下のようになります。

response.json
[
  {
    "id": 1,
    "name": "伏見稲荷大社",
    "postcode": "612-0882",
    "prefecture": "京都府",
    "address": "京都市伏見区深草藪之内町68"
  },
  {
    "id": 2,
    "name": "広島平和記念資料館",
    "postcode": "730-0811",
    "prefecture": "広島県",
    "address": "広島市中区中島町1-2"
  }
]

ネストされていないかのように扱う データの登録、更新時

ところでこのままでは登録、更新ができません。なので、app/serializers.pyを以下のようにします。

app/serializers.py
from .models import Prefecture
from .models import SightseeingSpot
from rest_framework import serializers


class PrefectureSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(
        required=False,
    )
    name = serializers.CharField(
        required=False,
    )

    class Meta(object):
        model = Prefecture
        fields = (
            'id',
            'name',
        )


class SightseeingSpotSerializer(serializers.ModelSerializer):
    id = serializers.IntegerField(
        required=False,
    )
    name = serializers.CharField(
        required=False,
    )
    postcode = serializers.CharField(
        required=False,
    )
    prefecture = serializers.SerializerMethodField(
        read_only=True,
    )
    prefecture_id = serializers.IntegerField(
        write_only=True,
    )
    address = serializers.CharField(
        required=False,
    )

    class Meta(object):
        model = SightseeingSpot
        fields = (
            'id',
            'name',
            'postcode',
            'prefecture',
            'prefecture_id',
            'address',
        )

    def get_prefecture(self, obj):
        return obj.prefecture.name

prefecture_idを更新できるようにします。試しに

request.json
{
  "prefecture_id": 4
}

というリクエストを/api/sightseeingspots/1/に送り、伏見稲荷大社を宮城県に誘致してみます。すると、

response.json
{
    "id": 1,
    "name": "伏見稲荷大社",
    "postcode": "612-0882",
    "prefecture": "宮城県",
    "address": "京都市伏見区深草藪之内町68"
}

というレスポンスが返ってくるので伏見稲荷大社を宮城県京都市に誘致できました。

おわりに

無事にタイトルを回収できました。真面目な話、ネストされたモデルをネストされていないかのように扱いたい時としては、今回のようにidnameだけを持ったテーブル(所謂マスターテーブル)のモデルを扱いたい時に需要があるのではないでしょうか。
マスターデータのモデルを扱いたい->そのためにネストされたjsonが返ってくるのは些か過剰->ネスト無しでnameだけ返ってきてほしい
といった感じで。そういうケースで使える方法だと思います。

13
7
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
7